From 65f139ea54d0e1948deba133a26171f70667e07f Mon Sep 17 00:00:00 2001 From: Jonathon Blok Date: Fri, 9 Apr 2021 17:24:01 +0100 Subject: [PATCH] add PersistantNotification component, stories and tests (#3921) Co-authored-by: Will McVay --- .../__styles__/index.tsx | 100 ++++++++++++++++ .../__tests__/__snapshots__/index.tsx.snap | 61 ++++++++++ .../__tests__/index.tsx | 32 ++++++ .../PersistantNotification/index.tsx | 36 ++++++ .../persistantNotification.stories.mdx | 107 ++++++++++++++++++ .../elements/src/styles-v3/base/variables.ts | 2 +- 6 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 packages/elements/src/components-v3/PersistantNotification/__styles__/index.tsx create mode 100644 packages/elements/src/components-v3/PersistantNotification/__tests__/__snapshots__/index.tsx.snap create mode 100644 packages/elements/src/components-v3/PersistantNotification/__tests__/index.tsx create mode 100644 packages/elements/src/components-v3/PersistantNotification/index.tsx create mode 100644 packages/elements/src/components-v3/PersistantNotification/persistantNotification.stories.mdx diff --git a/packages/elements/src/components-v3/PersistantNotification/__styles__/index.tsx b/packages/elements/src/components-v3/PersistantNotification/__styles__/index.tsx new file mode 100644 index 0000000000..4695f9fc04 --- /dev/null +++ b/packages/elements/src/components-v3/PersistantNotification/__styles__/index.tsx @@ -0,0 +1,100 @@ +import { css } from 'linaria' +import { styled } from 'linaria/react' +import { ElIcon } from '../../Icon/__styles__' +import { elIsActive } from '../../../styles-v3/base/states' +import { + elIntentPrimary, + elIntentSecondary, + elIntentCritical, + elIntentSuccess, + elIntentDanger, +} from '../../../styles-v3/base/intent' + +export const elPnIcon = css` + padding: 0 0.5rem; + display: flex; + align-items: center; + border-radius var(--default-border-radius) 0 0 var(--default-border-radius); + cursor: pointer; + + ${ElIcon} { + color: var(--color-white); + } +` + +export const elPnContent = css` + padding: 0.75rem 1.25rem; + opacity: 0; + transition: 0.5s; +` + +export const ElPersistantNotification = styled.div` + display: flex; + position: fixed; + top: 1rem; + right: 2rem; // should be the width of the elPnIcon element (icon is 1rem and padding is 0.5rem each side) + max-width: 50%; + transform: translateX(100%); + transition: 0.5s; + z-index: 10; + + &${elIsActive} { + right: 0; + transform: translateX(0); + + .${elPnContent} { + opacity: 1; + } + } + + &${elIntentPrimary} { + .${elPnContent} { + background: var(--intent-primary-light); + color: var(--intent-primary-light-text); + } + .${elPnIcon} { + background: var(--intent-primary); + color: var(--intent-primary-text); + } + } + &${elIntentSecondary} { + .${elPnContent} { + background: var(--intent-secondary-light); + color: var(--intent-secondary-light-text); + } + .${elPnIcon} { + background: var(--intent-secondary); + color: var(--intent-secondary-text); + } + } + &${elIntentCritical} { + .${elPnContent} { + background: var(--intent-critical-light); + color: var(--intent-critical-light-text); + } + .${elPnIcon} { + background: var(--intent-critical); + color: var(--intent-critical-text); + } + } + &${elIntentSuccess} { + .${elPnContent} { + background: var(--intent-success-light); + color: var(--intent-success-light-text); + } + .${elPnIcon} { + background: var(--intent-success); + color: var(--intent-success-text); + } + } + &${elIntentDanger} { + .${elPnContent} { + background: var(--intent-danger-light); + color: var(--intent-danger-light-text); + } + .${elPnIcon} { + background: var(--intent-danger); + color: var(--intent-danger-text); + } + } +` diff --git a/packages/elements/src/components-v3/PersistantNotification/__tests__/__snapshots__/index.tsx.snap b/packages/elements/src/components-v3/PersistantNotification/__tests__/__snapshots__/index.tsx.snap new file mode 100644 index 0000000000..15b6388490 --- /dev/null +++ b/packages/elements/src/components-v3/PersistantNotification/__tests__/__snapshots__/index.tsx.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PersistantNotification component should match a snapshot 1`] = ` + +
+ +
+
+ I am notification +
+
+`; + +exports[`PersistantNotification component should match a snapshot when expanded 1`] = ` + +
+ +
+
+ I am notification +
+
+`; + +exports[`PersistantNotification component should match a snapshot when given an intent 1`] = ` + +
+ +
+
+ I am notification +
+
+`; diff --git a/packages/elements/src/components-v3/PersistantNotification/__tests__/index.tsx b/packages/elements/src/components-v3/PersistantNotification/__tests__/index.tsx new file mode 100644 index 0000000000..ff7e6bbaec --- /dev/null +++ b/packages/elements/src/components-v3/PersistantNotification/__tests__/index.tsx @@ -0,0 +1,32 @@ +import * as React from 'react' +import { shallow } from 'enzyme' +import { PersistantNotification } from '../' +import { elPnIcon } from '../__styles__' + +describe('PersistantNotification component', () => { + it('should match a snapshot', () => { + const wrapper = shallow(I am notification) + expect(wrapper).toMatchSnapshot() + }) + + it('should match a snapshot when given an intent', () => { + const wrapper = shallow(I am notification) + expect(wrapper).toMatchSnapshot() + }) + + it('should match a snapshot when expanded', () => { + const wrapper = shallow(I am notification) + expect(wrapper).toMatchSnapshot() + }) + + it('should fire the onStepClick event correctly', () => { + const spy = jest.fn() + const wrapper = shallow( + + I am notification + , + ) + wrapper.find(`.${elPnIcon}`).first().simulate('click') + expect(spy).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/elements/src/components-v3/PersistantNotification/index.tsx b/packages/elements/src/components-v3/PersistantNotification/index.tsx new file mode 100644 index 0000000000..f2a2fb3b4e --- /dev/null +++ b/packages/elements/src/components-v3/PersistantNotification/index.tsx @@ -0,0 +1,36 @@ +import { cx } from 'linaria' +import * as React from 'react' +import { ElPersistantNotification, elPnIcon, elPnContent } from './__styles__' +import { Icon, IconNames } from '../Icon' +import { elIsActive } from '../../styles-v3/base/states' +import { Intent, getIntentClassName } from '../../helpers/v3/intent' + +export interface IPersistantNotification extends React.HTMLAttributes { + icon?: IconNames + intent?: Intent + className?: string + isExpanded?: boolean + onExpansionToggle?: (newState: boolean) => void +} + +export const PersistantNotification: React.FC = ({ + icon = 'info', + intent = 'secondary', + className, + isExpanded = false, + onExpansionToggle, + children, + ...rest +}) => { + const intentClassName = getIntentClassName(intent) + const combinedClassName = cx(className, intentClassName, isExpanded && elIsActive) + + return ( + +
onExpansionToggle && onExpansionToggle(!isExpanded)}> + +
+
{children}
+
+ ) +} diff --git a/packages/elements/src/components-v3/PersistantNotification/persistantNotification.stories.mdx b/packages/elements/src/components-v3/PersistantNotification/persistantNotification.stories.mdx new file mode 100644 index 0000000000..8b6e045c88 --- /dev/null +++ b/packages/elements/src/components-v3/PersistantNotification/persistantNotification.stories.mdx @@ -0,0 +1,107 @@ +import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks' +import { useState } from 'react' +import { PersistantNotification } from './index' + + + +# PersistantNotification + +Underlying tag: `
` + +## Full React example using `useState` and the default CSS position: fixed + + + + {() => { + const [isExpanded, setIsExpanded] = useState(false) + return ( + <> + The element uses CSS position fixed by default, which means the component "floats" on top of all other + content. This example can be seen in the top right corner of the screen. Only one PersistantNotification per + page is supported. + + Here is a persistant notification. Here is more content. Here is more content. Here is more content. Here is + more content. Here is more content. + + + ) + }} + + + + + +## Inline examples + +These stories have been given a custom CSS positon (`position: relative`) so that they appear inline. This makes it easier to preview these examples, but in normal usage they will pin to the top right of the screen as in the above example. + +### Default usage + + + + + Here is a persistant notification + + + + +### With longer content + +The notification is set to a max-width of 50% of the parent element (in normal usage this is the entire window). If the content is longer the box just grows downwards. + + + + + Here is a persistant notification. Here is more content. Here is more content. Here is more content. Here is more + content. Here is more content. + + + + +### With a different icon + + + + + Here's some infomation about the thing you should be warned about, so bad things don't happen. + + + + +### With different intents + + + + + Here's a notification with a primary intent. + + + + + Here's a notification with a secondary intent. + + + + + Here's a notification with a critical intent. + + + + + Here's a notification with a success intent. + + + + + Here's a notification with a danger intent. + + + + +### Collapsed/Expanded state + +Set the prop `isExpanded` to expand/collapse the component. In the collapsed state the whole component is moved to the right. In normal usage when the component is fixed to the right of the screen this means the content is out of view. + +## Other attributes + +All other standard HTML attributes for `
` are supported and are passed through to the React component. diff --git a/packages/elements/src/styles-v3/base/variables.ts b/packages/elements/src/styles-v3/base/variables.ts index b1749285d9..b068c1b8ab 100644 --- a/packages/elements/src/styles-v3/base/variables.ts +++ b/packages/elements/src/styles-v3/base/variables.ts @@ -60,7 +60,7 @@ export const elVariables = css` --intent-secondary-dark-text: var(--color-white); --intent-critical-dark-text: var(--color-white); --intent-success-dark-text: var(--color-white); - --intent-danger-light-text: var(--color-white); + --intent-danger-dark-text: var(--color-white); /** font variables */ --font-sans-serif: 'PT Sans', Helvetica, Arial, sans-serif;