@automattic/vip-design-system 0.9.2 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/build/system/Avatar/Avatar.stories.js +23 -8
  2. package/build/system/Avatar/Avatar.test.js +41 -22
  3. package/build/system/Badge/Badge.stories.js +25 -8
  4. package/build/system/Badge/Badge.test.js +73 -0
  5. package/build/system/BlankState/BlankState.js +12 -9
  6. package/build/system/BlankState/BlankState.stories.js +27 -11
  7. package/build/system/BlankState/BlankState.test.js +124 -0
  8. package/build/system/Box/Box.stories.js +25 -8
  9. package/build/system/Button/Button.stories.js +36 -23
  10. package/build/system/Button/Button.test.js +44 -0
  11. package/build/system/Card/Card.stories.js +25 -8
  12. package/build/system/Card/Card.test.js +78 -0
  13. package/build/system/Code/Code.js +11 -8
  14. package/build/system/Code/Code.stories.js +25 -8
  15. package/build/system/Code/Code.test.js +167 -0
  16. package/build/system/ConfirmationDialog/ConfirmationDialog.stories.js +45 -22
  17. package/build/system/Dialog/Dialog.stories.js +71 -31
  18. package/build/system/Dialog/DialogContent.js +3 -1
  19. package/build/system/Flex/Flex.stories.js +25 -8
  20. package/build/system/Form/AsyncSearchSelect.js +38 -0
  21. package/build/system/Form/Input.js +2 -2
  22. package/build/system/Form/Input.stories.js +23 -8
  23. package/build/system/Form/SearchSelect.js +47 -11
  24. package/build/system/Form/Select.js +48 -12
  25. package/build/system/Form/Select.stories.js +108 -78
  26. package/build/system/Form/Select.test.js +52 -0
  27. package/build/system/Grid/Grid.stories.js +25 -8
  28. package/build/system/Heading/Heading.stories.js +43 -20
  29. package/build/system/Link/Link.stories.js +26 -10
  30. package/build/system/Notice/Notice.stories.js +47 -81
  31. package/build/system/Notification/Notification.stories.js +23 -8
  32. package/build/system/OptionRow/OptionRow.stories.js +36 -20
  33. package/build/system/Progress/Progress.stories.js +23 -8
  34. package/build/system/Spinner/Spinner.stories.js +23 -8
  35. package/build/system/Table/Table.stories.js +64 -42
  36. package/build/system/Tabs/Tabs.stories.js +32 -10
  37. package/build/system/Text/Text.stories.js +25 -8
  38. package/build/system/Timeline/Timeline.js +69 -0
  39. package/build/system/Timeline/Timeline.stories.js +44 -21
  40. package/build/system/Timeline/index.js +2 -66
  41. package/build/system/Tooltip/Tooltip.stories.js +41 -19
  42. package/build/system/Wizard/Wizard.stories.js +65 -37
  43. package/package.json +17 -5
  44. package/src/system/Avatar/Avatar.test.js +9 -10
  45. package/src/system/Badge/Badge.test.js +30 -0
  46. package/src/system/BlankState/BlankState.js +13 -5
  47. package/src/system/BlankState/BlankState.test.js +58 -0
  48. package/src/system/Button/Button.test.js +21 -0
  49. package/src/system/Card/Card.test.js +33 -0
  50. package/src/system/Code/Code.js +12 -9
  51. package/src/system/Code/Code.stories.js +2 -0
  52. package/src/system/Code/Code.test.js +69 -0
  53. package/src/system/Dialog/DialogContent.js +2 -0
  54. package/src/system/Form/AsyncSearchSelect.js +29 -0
  55. package/src/system/Form/Input.js +2 -2
  56. package/src/system/Form/SearchSelect.js +43 -3
  57. package/src/system/Form/Select.js +29 -6
  58. package/src/system/Form/Select.stories.js +82 -24
  59. package/src/system/Form/Select.test.js +37 -0
  60. package/src/system/Timeline/Timeline.js +46 -0
  61. package/src/system/Timeline/Timeline.stories.js +34 -0
  62. package/src/system/Timeline/index.js +2 -41
  63. package/build/system/UsageChart/UsageChart.stories.js +0 -20
@@ -0,0 +1,69 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
5
+ import { axe } from 'jest-axe';
6
+ import { MdContentCopy } from 'react-icons/md';
7
+
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import { Code } from './Code';
12
+
13
+ const defaultProps = {
14
+ showCopy: false,
15
+ };
16
+
17
+ // Mock navigator.clipboard because jsdom doesn't support it
18
+ Object.defineProperty( window.navigator, 'clipboard', {
19
+ value: {
20
+ writeText: () => {},
21
+ },
22
+ } );
23
+
24
+ describe( '<Code />', () => {
25
+ it( 'renders the Code component', async () => {
26
+ const { container } = render( <Code { ...defaultProps }>This is a code</Code> );
27
+
28
+ expect( screen.getByText( 'This is a code' ) ).toBeInTheDocument();
29
+
30
+ // Check for accessibility issues
31
+ await expect( await axe( container ) ).toHaveNoViolations();
32
+ } );
33
+
34
+ // jsdom currently doesn't support pseudo-elements with getComputedStyle
35
+ it.skip( 'renders "$" in front of the code when in prompt mode', async () => {
36
+ const props = { ...defaultProps, prompt: true };
37
+ const { container } = render( <Code { ...props }>This is a code</Code> );
38
+ const codeElement = screen.getByText( 'This is a code' );
39
+
40
+ expect( window.getComputedStyle( codeElement, ':before' ).content ).toEqual( '$' );
41
+
42
+ // Check for accessibility issues
43
+ await expect( await axe( container ) ).toHaveNoViolations();
44
+ } );
45
+
46
+ it( 'renders the Code component with a copy button', async () => {
47
+ const props = { ...defaultProps, showCopy: true };
48
+ const { container } = render( <Code { ...props }>This is a code</Code> );
49
+
50
+ expect( screen.getByRole( 'button', { name: 'Copy' } ) ).toBeInTheDocument();
51
+
52
+ // Check for accessibility issues
53
+ await expect( await axe( container ) ).toHaveNoViolations();
54
+ } );
55
+
56
+ it( 'updates the the UI after clicking on "Copy"', async () => {
57
+ const props = { ...defaultProps, showCopy: true };
58
+ const { container } = render( <Code { ...props }>This is a code</Code> );
59
+
60
+ fireEvent.click( screen.getByRole( 'button', { name: 'Copy' } ) );
61
+
62
+ await waitFor( () => new Promise( res => setTimeout( res, 0 ) ) );
63
+
64
+ expect( screen.getByText( 'Copied!' ) ).toBeInTheDocument();
65
+
66
+ // Check for accessibility issues
67
+ await expect( await axe( container ) ).toHaveNoViolations();
68
+ } );
69
+ } );
@@ -133,6 +133,8 @@ const DialogMotion = ( { variant, position, ...props } ) => {
133
133
  padding: 0,
134
134
  display: 'inline-block',
135
135
  variant: `dialog.${ variant }`,
136
+ overflow: 'auto',
137
+ maxHeight: variant === 'modal' ? '90%' : '',
136
138
  } }
137
139
  />
138
140
  );
@@ -0,0 +1,29 @@
1
+ /** @jsxImportSource theme-ui */
2
+
3
+ /**
4
+ * External dependencies
5
+ */
6
+ import PropTypes from 'prop-types';
7
+ import { withAsyncPaginate } from 'react-select-async-paginate';
8
+
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ import { SearchSelect } from './SearchSelect';
13
+
14
+ // Asynchronous search select to load paginated results asynchronously
15
+ const CustomAsyncPaginate = withAsyncPaginate( SearchSelect );
16
+
17
+ const AsyncSearchSelect = ( { options, ...props } ) => (
18
+ <CustomAsyncPaginate
19
+ SelectComponent={ SearchSelect }
20
+ loadOptions={ options }
21
+ { ...props }
22
+ />
23
+ );
24
+
25
+ AsyncSearchSelect.propTypes = {
26
+ options: PropTypes.array,
27
+ };
28
+
29
+ export { AsyncSearchSelect };
@@ -28,7 +28,7 @@ const Input = React.forwardRef( ( { variant, label, forLabel, hasError, required
28
28
  required={ required }
29
29
  sx={ {
30
30
  border: '1px solid',
31
- borderColor: 'grey.60',
31
+ borderColor: 'border',
32
32
  backgroundColor: 'card',
33
33
  borderRadius: 1,
34
34
  lineHeight: 'inherit',
@@ -47,7 +47,7 @@ const Input = React.forwardRef( ( { variant, label, forLabel, hasError, required
47
47
  bg: 'backgroundSecondary',
48
48
  },
49
49
  '&::placeholder': {
50
- color: 'grey.30',
50
+ color: 'placeholder',
51
51
  },
52
52
  } }
53
53
  />
@@ -12,6 +12,7 @@ import PropTypes from 'prop-types';
12
12
  */
13
13
  import { Flex, Text } from '..';
14
14
 
15
+ // Option component
15
16
  export const Option = ( { label, isSelected, ...props } ) => (
16
17
  <components.Option {...props}>
17
18
  <Flex sx={{ alignItems: 'center' }}>
@@ -32,7 +33,45 @@ Option.propTypes = {
32
33
  isSelected: PropTypes.bool,
33
34
  };
34
35
 
35
- export const DropdownIndicator = props => <MdExpandMore {...props} sx={{ color: 'text', mr: 2 }} />;
36
+ // DropdownIndicator component
37
+ export const DropdownIndicator = ( {
38
+ innerProps,
39
+ isFocused,
40
+ isDisabled,
41
+ clearValue,
42
+ cx,
43
+ getStyles,
44
+ getValue,
45
+ hasValue,
46
+ isMulti,
47
+ isRtl,
48
+ options,
49
+ selectProps,
50
+ setValue,
51
+ selectOption,
52
+ theme,
53
+ ...props
54
+ } ) => <MdExpandMore { ...props } sx={ { color: 'text', mr: 2 } } />;
55
+
56
+ DropdownIndicator.propTypes = {
57
+ innerProps: PropTypes.object,
58
+ isFocused: PropTypes.bool,
59
+ isDisabled: PropTypes.bool,
60
+ clearValue: PropTypes.func,
61
+ cx: PropTypes.func,
62
+ getStyles: PropTypes.func,
63
+ getValue: PropTypes.func,
64
+ hasValue: PropTypes.bool,
65
+ isMulti: PropTypes.bool,
66
+ isRtl: PropTypes.bool,
67
+ options: PropTypes.array,
68
+ selectProps: PropTypes.object,
69
+ setValue: PropTypes.func,
70
+ selectOption: PropTypes.func,
71
+ theme: PropTypes.object,
72
+ };
73
+
74
+ // ClearIndicator component
36
75
  const ClearIndicator = ( { innerProps: { ref, ...restInnerProps }, ...props } ) => (
37
76
  <MdClose ref={ref} {...restInnerProps} {...props} sx={{ color: 'text', mr: 2 }} />
38
77
  );
@@ -41,11 +80,12 @@ ClearIndicator.propTypes = {
41
80
  innerProps: PropTypes.object,
42
81
  };
43
82
 
83
+ // Parent SearchSelect component
44
84
  const SearchSelect = props => (
45
85
  <Select
46
- {...props}
86
+ { ...props }
47
87
  classNamePrefix={ 'select' }
48
- components={{ Option, DropdownIndicator, ClearIndicator }}
88
+ components={ { Option, DropdownIndicator, ClearIndicator } }
49
89
  sx={ {
50
90
  '.select__control': {
51
91
  background: 'none',
@@ -3,27 +3,50 @@
3
3
  /**
4
4
  * External dependencies
5
5
  */
6
+ import React from 'react';
6
7
  import PropTypes from 'prop-types';
7
8
 
8
9
  /**
9
- * Internal dependencies
10
- */
10
+ * Internal dependencies
11
+ */
11
12
  import { SearchSelect } from './SearchSelect';
12
13
  import { InlineSelect } from './InlineSelect';
14
+ import { AsyncSearchSelect } from './AsyncSearchSelect';
15
+
16
+ const Select = ( { isMulti = false, isInline, isAsync, options, label, isSearch, usePortal, ...props } ) => {
17
+ let Component;
18
+ const selectRef = React.useRef();
19
+ const portalProps = {};
13
20
 
14
- const Select = ( { isMulti = false, isInline, options, label, isSearch, ...props } ) => {
15
- if ( isInline ) {
16
- return <InlineSelect isMulti={ isMulti } label={ label } options={ options } { ...props } />;
21
+ if ( usePortal !== undefined ) {
22
+ portalProps.menuPortalTarget =
23
+ selectRef?.current?.querySelector( '.select__control' ).parentElement;
24
+ portalProps.styles = { menuPortal: base => ( { ...base, position: 'fixed' } ) };
17
25
  }
18
- return <SearchSelect isMulti={ isMulti } label={ label } options={ options } { ...props } />;
26
+
27
+ switch ( true ) {
28
+ case isInline:
29
+ Component = InlineSelect;
30
+ break;
31
+ case isAsync:
32
+ Component = AsyncSearchSelect;
33
+ break;
34
+ default:
35
+ Component = SearchSelect;
36
+ break;
37
+ }
38
+
39
+ return <div ref={selectRef}><Component isMulti={isMulti} label={label} options={options} {...portalProps} {...props} /></div>;
19
40
  };
20
41
 
21
42
  Select.propTypes = {
22
43
  isInline: PropTypes.bool,
23
44
  isMulti: PropTypes.bool,
45
+ isAsync: PropTypes.bool,
24
46
  isSearch: PropTypes.bool,
25
47
  label: PropTypes.string,
26
48
  options: PropTypes.array,
49
+ usePortal: PropTypes.bool,
27
50
  };
28
51
 
29
52
  export { Select };
@@ -6,15 +6,7 @@ import { useState } from 'react';
6
6
  /**
7
7
  * Internal dependencies
8
8
  */
9
- import {
10
- Box,
11
- Dialog,
12
- DialogMenu,
13
- DialogMenuItem,
14
- DialogDivider,
15
- Select,
16
- Button,
17
- } from '..';
9
+ import { Box, Dialog, DialogMenu, DialogMenuItem, DialogDivider, Select, Button } from '..';
18
10
 
19
11
  export default {
20
12
  title: 'Select',
@@ -44,43 +36,109 @@ const DropdownContent = (
44
36
  const DropdownTrigger = <Button variant="secondary">Trigger Dropdown</Button>;
45
37
 
46
38
  export const Multi = () => {
47
- const [ value, setValue ] = useState( [ ] );
39
+ const [ value, setValue ] = useState( [] );
48
40
 
49
41
  return (
50
- <Box sx={ { mr: 2, width: 400 } }>
51
- <Select label="Type" value={ value } isMulti placeholder="Select domains..." options={ options } onChange={ newValue => setValue( newValue ) } />
42
+ <Box sx={{ mr: 2, width: 400 }}>
43
+ <Select
44
+ label="Type"
45
+ value={value}
46
+ isMulti
47
+ placeholder="Select domains..."
48
+ options={options}
49
+ onChange={newValue => setValue( newValue )}
50
+ />
51
+ </Box>
52
+ );
53
+ };
54
+
55
+ export const usePortal = () => {
56
+ const [ value, setValue ] = useState( [] );
57
+
58
+ return (
59
+ <Box sx={{ mr: 2, width: 400 }}>
60
+ <Select
61
+ label="Type"
62
+ value={value}
63
+ isMulti
64
+ placeholder="Select domains..."
65
+ usePortal
66
+ options={options}
67
+ onChange={newValue => setValue( newValue )}
68
+ />
52
69
  </Box>
53
70
  );
54
71
  };
55
72
 
56
73
  export const Single = () => {
57
- const [ value, setValue ] = useState( [ ] );
74
+ const [ value, setValue ] = useState( [] );
58
75
 
59
76
  return (
60
- <Box sx={ { mr: 2, width: 200 } }>
61
- <Select label="User" value={ value } placeholder="Select a domain..." options={ options } onChange={ newValue => setValue( newValue ) } />
77
+ <Box sx={{ mr: 2, width: 200 }}>
78
+ <Select
79
+ label="User"
80
+ value={value}
81
+ placeholder="Select a domain..."
82
+ options={options}
83
+ onChange={newValue => setValue( newValue )}
84
+ />
62
85
  </Box>
63
86
  );
64
87
  };
65
88
 
66
89
  export const Inline = () => {
67
- const [ value, setValue ] = useState( [ ] );
90
+ const [ value, setValue ] = useState( [] );
68
91
 
69
92
  return (
70
- <Box sx={ { mr: 2, width: 200 } }>
71
- <Select label="User" value={ value } isInline isMulti noneLabel="Everyone" placeholder="Filter by user..." options={ options } onChange={ newValue => setValue( newValue ) } />
93
+ <Box sx={{ mr: 2, width: 200 }}>
94
+ <Select
95
+ label="User"
96
+ value={value}
97
+ isInline
98
+ isMulti
99
+ noneLabel="Everyone"
100
+ placeholder="Filter by user..."
101
+ options={options}
102
+ onChange={newValue => setValue( newValue )}
103
+ />
72
104
  </Box>
73
105
  );
74
106
  };
75
107
 
76
- export const DropdownMenu = () => {
108
+ export const Async = () => {
109
+ const [ value, setValue ] = useState( [] );
110
+ const loadOptions = async () => new Promise( resolve => {
111
+ setTimeout( () => {
112
+ resolve( {
113
+ options: [
114
+ ...options,
115
+ { value: 'newvanilla', label: 'New Vanilla' },
116
+ ],
117
+ } );
118
+ }, 2000 );
119
+ } );
120
+
77
121
  return (
78
- <Box sx={ { mr: 2, width: 200 } }>
79
- <Dialog
80
- trigger={DropdownTrigger}
81
- content={DropdownContent}
82
- sx={{ width: 200 }}
122
+ <Box sx={{ mr: 2, width: 200 }}>
123
+ <Select
124
+ label="Async Select"
125
+ value={value}
126
+ isAsync
127
+ usePortal
128
+ loadOptions={ loadOptions }
129
+ noneLabel="Everyone"
130
+ placeholder="Load async..."
131
+ options={options}
132
+ onChange={newValue => setValue( newValue )}
83
133
  />
84
134
  </Box>
85
135
  );
86
136
  };
137
+
138
+ export const DropdownMenu = () => {
139
+ return (
140
+ <Box sx={{ mr: 2, width: 200 }}>
141
+ <Dialog trigger={DropdownTrigger} content={DropdownContent} sx={{ width: 200 }} />
142
+ </Box>
143
+ );
144
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+ import { axe } from 'jest-axe';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { Select } from './Select';
11
+
12
+ describe( '<Select />', () => {
13
+ it( 'renders the Select component with the specified placeholder', () => {
14
+ render(
15
+ <Select
16
+ inputId={ 'search-select' }
17
+ placeholder={ 'Search...' }
18
+ />
19
+ );
20
+
21
+ // Can't use `getByPlaceholderText` here since it's not actually being rendered as a placeholder element
22
+ const placeholder = screen.getByText( 'Search...' );
23
+
24
+ expect( placeholder ).toBeInTheDocument();
25
+ } );
26
+
27
+ it( 'renders the Select component with accessibility props', async () => {
28
+ const { container } = render(
29
+ <Select
30
+ inputId={ 'search-select' }
31
+ aria-label={ 'Search or select from the dropdown list' }
32
+ />
33
+ );
34
+
35
+ expect( await axe( container ) ).toHaveNoViolations();
36
+ } );
37
+ } );
@@ -0,0 +1,46 @@
1
+ /** @jsxImportSource theme-ui */
2
+
3
+ /**
4
+ * External dependencies
5
+ */
6
+ import { Flex } from 'theme-ui';
7
+ import { MdWatchLater } from 'react-icons/md';
8
+ import PropTypes from 'prop-types';
9
+
10
+ const VerticalLine = () => {
11
+ return (
12
+ <div
13
+ sx={ {
14
+ borderLeft: '2px solid',
15
+ borderColor: 'border',
16
+ height: 'calc( 50% - 16px )',
17
+ borderRadius: '2px',
18
+ } }>
19
+ </div>
20
+ );
21
+ };
22
+
23
+ const Timeline = ( { time, first = false, last = false, ...props } ) => (
24
+ <Flex { ...props }>
25
+ <Flex sx={ { flexDirection: 'column', justifyContent: 'space-evenly', alignItems: 'center' } }>
26
+ { ! first && (
27
+ <VerticalLine />
28
+ ) }
29
+ <MdWatchLater sx={ { color: 'border' } } size={ 18 }/>
30
+ { ! last && (
31
+ <VerticalLine />
32
+ ) }
33
+ </Flex>
34
+ <Flex sx={ { alignItems: 'center', ml: 2 } }>
35
+ { time }
36
+ </Flex>
37
+ </Flex>
38
+ );
39
+
40
+ Timeline.propTypes = {
41
+ first: PropTypes.bool,
42
+ time: PropTypes.node,
43
+ last: PropTypes.bool,
44
+ };
45
+
46
+ export { Timeline };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import React from 'react';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import { Timeline } from '..';
10
+ import { Link } from '../Link';
11
+
12
+ export default {
13
+ title: 'Timeline',
14
+ component: Timeline,
15
+ };
16
+
17
+ export const Default = () => {
18
+ return (
19
+ <React.Fragment>
20
+ <Timeline
21
+ time="13:00"
22
+ title="21:00 UTC"
23
+ />
24
+ <Timeline
25
+ time={ <Link>14:00</Link> }
26
+ title="22:00 UTC"
27
+ />
28
+ <Timeline
29
+ time="15:00"
30
+ title="23:00 UTC"
31
+ />
32
+ </React.Fragment>
33
+ );
34
+ };
@@ -1,46 +1,7 @@
1
- /** @jsxImportSource theme-ui */
2
1
 
3
2
  /**
4
- * External dependencies
3
+ * Internal dependencies
5
4
  */
6
- import { Flex } from 'theme-ui';
7
- import { MdWatchLater } from 'react-icons/md';
8
- import PropTypes from 'prop-types';
9
-
10
- const VerticalLine = () => {
11
- return (
12
- <div
13
- sx={ {
14
- borderLeft: '2px solid',
15
- borderColor: 'border',
16
- height: 'calc( 50% - 16px )',
17
- borderRadius: '2px',
18
- } }>
19
- </div>
20
- );
21
- };
22
-
23
- const Timeline = ( { time, first = false, last = false, ...props } ) => (
24
- <Flex { ...props }>
25
- <Flex sx={ { flexDirection: 'column', justifyContent: 'space-evenly', alignItems: 'center' } }>
26
- { ! first && (
27
- <VerticalLine />
28
- ) }
29
- <MdWatchLater sx={ { color: 'border' } } size={ 18 }/>
30
- { ! last && (
31
- <VerticalLine />
32
- ) }
33
- </Flex>
34
- <Flex sx={ { alignItems: 'center', ml: 2 } }>
35
- <span>{ time }</span>
36
- </Flex>
37
- </Flex>
38
- );
39
-
40
- Timeline.propTypes = {
41
- first: PropTypes.bool,
42
- time: PropTypes.string,
43
- last: PropTypes.bool,
44
- };
5
+ import { Timeline } from './Timeline';
45
6
 
46
7
  export { Timeline };
@@ -1,20 +0,0 @@
1
- /**
2
- * External dependencies
3
- */
4
- import React from 'react';
5
- /**
6
- * Internal dependencies
7
- */
8
-
9
- import { UsageChart } from '..';
10
- export default {
11
- title: 'UsageChart',
12
- component: UsageChart
13
- };
14
- export var Default = function Default() {
15
- return /*#__PURE__*/React.createElement(UsageChart, {
16
- total: 50,
17
- max: 75,
18
- variant: "primary"
19
- });
20
- };