@automattic/vip-design-system 0.23.7 → 0.25.0

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.
@@ -0,0 +1,166 @@
1
+ .autocomplete__wrapper {
2
+ position: relative;
3
+ }
4
+
5
+ .autocomplete__hint,
6
+ .autocomplete__input {
7
+ -webkit-appearance: none;
8
+ border: 2px solid #0b0c0c;
9
+ border-radius: 0; /* Safari 10 on iOS adds implicit border rounding. */
10
+ box-sizing: border-box;
11
+ -moz-box-sizing: border-box;
12
+ -webkit-box-sizing: border-box;
13
+ margin-bottom: 0; /* BUG: Safari 10 on macOS seems to add an implicit margin. */
14
+ width: 100%;
15
+ }
16
+
17
+ .autocomplete__input {
18
+ background-color: transparent;
19
+ position: relative;
20
+ }
21
+
22
+ .autocomplete__hint {
23
+ color: #b1b4b6;
24
+ position: absolute;
25
+ }
26
+
27
+ .autocomplete__input--default {
28
+ padding: 5px;
29
+ }
30
+ .autocomplete__input--focused {
31
+ outline: 3px solid #fd0;
32
+ outline-offset: 0;
33
+ box-shadow: inset 0 0 0 2px;
34
+ }
35
+
36
+ .autocomplete__input--show-all-values {
37
+ padding: 5px 34px 5px 5px; /* Space for arrow. Other padding should match .autocomplete__input--default. */
38
+ cursor: pointer;
39
+ }
40
+
41
+ .autocomplete__dropdown-arrow-down {
42
+ z-index: -1;
43
+ display: inline-block;
44
+ position: absolute;
45
+ right: 8px;
46
+ width: 24px;
47
+ height: 24px;
48
+ top: 10px;
49
+ }
50
+
51
+ .autocomplete__menu {
52
+ background-color: #fff;
53
+ border: 2px solid #0B0C0C;
54
+ border-top: 0;
55
+ color: #0B0C0C;
56
+ margin: 0;
57
+ max-height: 342px;
58
+ overflow-x: hidden;
59
+ padding: 0;
60
+ width: 100%;
61
+ width: calc(100% - 4px);
62
+ }
63
+
64
+ .autocomplete__menu--visible {
65
+ display: block;
66
+ }
67
+
68
+ .autocomplete__menu--hidden {
69
+ display: none;
70
+ }
71
+
72
+ .autocomplete__menu--overlay {
73
+ box-shadow: rgba(0, 0, 0, 0.256863) 0px 2px 6px;
74
+ left: 0;
75
+ position: absolute;
76
+ top: 100%;
77
+ z-index: 100;
78
+ }
79
+
80
+ .autocomplete__menu--inline {
81
+ position: relative;
82
+ }
83
+
84
+ .autocomplete__option {
85
+ border-bottom: solid #b1b4b6;
86
+ border-width: 1px 0;
87
+ cursor: pointer;
88
+ display: block;
89
+ position: relative;
90
+ }
91
+
92
+ .autocomplete__option > * {
93
+ pointer-events: none;
94
+ }
95
+
96
+ .autocomplete__option:first-of-type {
97
+ border-top-width: 0;
98
+ }
99
+
100
+ .autocomplete__option:last-of-type {
101
+ border-bottom-width: 0;
102
+ }
103
+
104
+ .autocomplete__option--odd {
105
+ background-color: #FAFAFA;
106
+ }
107
+
108
+ .autocomplete__option--focused,
109
+ .autocomplete__option:hover {
110
+ background-color: #1d70b8;
111
+ border-color: #1d70b8;
112
+ color: white;
113
+ outline: none;
114
+ }
115
+
116
+ @media (-ms-high-contrast: active), (forced-colors: active) {
117
+ .autocomplete__menu {
118
+ border-color: FieldText;
119
+ }
120
+
121
+ .autocomplete__option {
122
+ background-color: Field;
123
+ color: FieldText;
124
+ }
125
+
126
+ .autocomplete__option--focused,
127
+ .autocomplete__option:hover {
128
+ forced-color-adjust: none; /* prevent backplate from obscuring text */
129
+ background-color: Highlight;
130
+ border-color: Highlight;
131
+ color: HighlightText;
132
+
133
+ /* Prefer SelectedItem / SelectedItemText in browsers that support it */
134
+ background-color: SelectedItem;
135
+ border-color: SelectedItem;
136
+ color: SelectedItemText;
137
+ outline-color: SelectedItemText;
138
+ }
139
+ }
140
+
141
+ .autocomplete__option--no-results {
142
+ background-color: #FAFAFA;
143
+ color: #646b6f;
144
+ cursor: not-allowed;
145
+ }
146
+
147
+ .autocomplete__hint,
148
+ .autocomplete__input,
149
+ .autocomplete__option {
150
+ font-size: 16px;
151
+ line-height: 1.25;
152
+ }
153
+
154
+ .autocomplete__hint,
155
+ .autocomplete__option {
156
+ padding: 5px;
157
+ }
158
+
159
+ @media (min-width: 641px) {
160
+ .autocomplete__hint,
161
+ .autocomplete__input,
162
+ .autocomplete__option {
163
+ font-size: 19px;
164
+ line-height: 1.31579;
165
+ }
166
+ }
@@ -0,0 +1,182 @@
1
+ /** @jsxImportSource theme-ui */
2
+
3
+ /**
4
+ * External dependencies
5
+ */
6
+ import React, { useCallback, useEffect, useMemo } from 'react';
7
+ import PropTypes from 'prop-types';
8
+ import { Label } from '../Form/Label';
9
+ import Autocomplete from 'accessible-autocomplete/react';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import css from './FormAutocomplete.css';
15
+ import { FormSelectContent } from './FormSelectContent';
16
+ import { FormSelectArrow } from './FormSelectArrow';
17
+
18
+ const defaultStyles = {
19
+ width: '100%',
20
+ py: 0,
21
+ borderWidth: '1px',
22
+ borderStyle: 'solid',
23
+ borderColor: 'border',
24
+ borderRadius: 1,
25
+ backgroundColor: 'background',
26
+ color: 'text',
27
+ minHeight: '36px',
28
+ lineHeight: '36px',
29
+ '&:focus': theme => theme.outline,
30
+ '&:focus-visible': theme => theme.outline,
31
+ '&:focus-within': theme => theme.outline,
32
+ '&.autocomplete__input--focused': theme => theme.outline,
33
+ '& .autocomplete__input': {
34
+ width: '100%',
35
+ paddingLeft: 3,
36
+ py: 0,
37
+ borderWidth: 0,
38
+ color: 'text',
39
+ minHeight: '36px',
40
+ lineHeight: '36px',
41
+ '&:focus': { outlineWidth: 0, boxShadow: 'none' },
42
+ '&:focus-visible': { outlineWidth: 0, boxShadow: 'none' },
43
+ '&:focus-within': { outlineWidth: 0, boxShadow: 'none' },
44
+ '&.autocomplete__input--focused': { outlineWidth: 0, boxShadow: 'none' },
45
+ },
46
+ '& .autocomplete__menu': {
47
+ borderWidth: '1px',
48
+ borderStyle: 'solid',
49
+ borderColor: 'border',
50
+ borderRadius: 1,
51
+ backgroundColor: 'background',
52
+ color: 'text',
53
+ },
54
+ '& .autocomplete__hint, & .autocomplete__input, & .autocomplete__option': {
55
+ fontSize: 'inherit',
56
+ },
57
+ '& .autocomplete__wrapper': {
58
+ width: '100%',
59
+ paddingRight: '40px',
60
+ },
61
+ '& .autocomplete__input--show-all-values': {
62
+ paddingRight: 0,
63
+ },
64
+ };
65
+
66
+ const inlineStyles = {
67
+ borderWidth: 0,
68
+ };
69
+
70
+ const FormAutocomplete = React.forwardRef(
71
+ (
72
+ {
73
+ isInline,
74
+ forLabel,
75
+ options,
76
+ label,
77
+ getOptionValue,
78
+ onChange,
79
+ value,
80
+ showAllValues = true,
81
+ displayMenu = 'overlay',
82
+ id = 'vip-autocomplete',
83
+ },
84
+ forwardRef
85
+ ) => {
86
+ const SelectLabel = () => <Label htmlFor={ forLabel || id }>{ label }</Label>;
87
+
88
+ const inlineLabel = !! ( isInline && label );
89
+
90
+ const optionValue = useCallback(
91
+ option => ( getOptionValue ? getOptionValue( option ) : option.value ),
92
+ [ getOptionValue ]
93
+ );
94
+
95
+ const getAllOptions = useMemo(
96
+ () =>
97
+ [
98
+ ...options.filter( option => ! option.options ),
99
+ ...options.filter( option => option.options ).map( option => option.options ),
100
+ ].reduce( ( a, b ) => a.concat( b ), [] ),
101
+ [ options ]
102
+ );
103
+
104
+ const getOptionByValue = useCallback(
105
+ inputValue =>
106
+ getAllOptions.find( option => `${ optionValue( option ) }` === `${ inputValue }` ),
107
+ [ getAllOptions, optionValue ]
108
+ );
109
+
110
+ const onValueChange = useCallback(
111
+ inputValue => {
112
+ if ( onChange ) {
113
+ onChange( getOptionByValue( inputValue ) );
114
+ }
115
+ },
116
+ [ onChange, getOptionByValue ]
117
+ );
118
+
119
+ const suggest = useCallback(
120
+ ( query, populateResults ) => {
121
+ const data = options.filter(
122
+ option => option.label.toLowerCase().indexOf( query.toLowerCase() ) >= 0
123
+ );
124
+ populateResults( data.map( option => option.label ) );
125
+ },
126
+ [ options ]
127
+ );
128
+
129
+ useEffect( () => {
130
+ global.document
131
+ .querySelector( '.autocomplete__input' )
132
+ .setAttribute( 'aria-activedescendant', '' );
133
+ }, [] );
134
+
135
+ useEffect( () => {
136
+ global.document
137
+ .querySelector( '.autocomplete__menu' )
138
+ .setAttribute( 'aria-label', `${ label } list` );
139
+ }, [ label ] );
140
+
141
+ return (
142
+ <>
143
+ { label && ! isInline && <SelectLabel /> }
144
+
145
+ <div sx={ { ...defaultStyles, ...( isInline && inlineStyles ) } }>
146
+ <FormSelectContent
147
+ isInline={ inlineLabel }
148
+ label={ inlineLabel ? <SelectLabel /> : null }
149
+ >
150
+ <Autocomplete
151
+ id={ id }
152
+ showAllValues={ showAllValues }
153
+ ref={ forwardRef }
154
+ source={ suggest }
155
+ defaultValue={ value }
156
+ displayMenu={ displayMenu }
157
+ onConfirm={ onValueChange }
158
+ />
159
+ <FormSelectArrow />
160
+ </FormSelectContent>
161
+ </div>
162
+ </>
163
+ );
164
+ }
165
+ );
166
+
167
+ FormAutocomplete.propTypes = {
168
+ id: PropTypes.string,
169
+ showAllValues: PropTypes.bool,
170
+ isInline: PropTypes.bool,
171
+ forLabel: PropTypes.string,
172
+ value: PropTypes.string,
173
+ displayMenu: PropTypes.string,
174
+ label: PropTypes.string,
175
+ options: PropTypes.array,
176
+ getOptionValue: PropTypes.func,
177
+ onChange: PropTypes.func,
178
+ };
179
+
180
+ FormAutocomplete.displayName = 'FormAutocomplete';
181
+
182
+ export { FormAutocomplete, css };
@@ -0,0 +1,89 @@
1
+ /** @jsxImportSource theme-ui */
2
+
3
+ /**
4
+ * Internal dependencies
5
+ */
6
+ import { useCallback, useState } from 'react';
7
+ import * as Form from '.';
8
+
9
+ export default {
10
+ title: 'Form/Autocomplete',
11
+ argTypes: {
12
+ placeholder: {
13
+ type: { name: 'string', required: false },
14
+ control: { type: 'text' },
15
+ },
16
+ label: {
17
+ type: { name: 'string', required: false },
18
+ control: { type: 'text' },
19
+ },
20
+ },
21
+ };
22
+
23
+ const defaultOptions = [
24
+ { value: 'chocolate', label: 'Chocolate' },
25
+ { value: 'strawberry', label: 'Strawberry Chocolate Vanilla Chocolate Vanilla' },
26
+ { value: 'vanilla', label: 'Vanilla' },
27
+ ];
28
+
29
+ // eslint-disable-next-line react/prop-types
30
+ const DefaultComponent = ( { label = 'Label', width = 250, onChange, ...rest } ) => (
31
+ <>
32
+ <Form.Root>
33
+ <div sx={ { width } }>
34
+ <Form.Autocomplete
35
+ id="form-autocomplete"
36
+ label={ label }
37
+ onChange={ onChange }
38
+ { ...rest }
39
+ />
40
+ </div>
41
+ </Form.Root>
42
+ </>
43
+ );
44
+
45
+ export const Default = () => {
46
+ const [ options, setOptions ] = useState( defaultOptions );
47
+
48
+ const onChange = useCallback( value => {
49
+ setOptions(
50
+ defaultOptions.filter(
51
+ option => ! value || option.label.toLowerCase().indexOf( value.toLowerCase() ) >= 0
52
+ )
53
+ );
54
+ } );
55
+
56
+ const args = {
57
+ label: 'Label',
58
+ options,
59
+ };
60
+
61
+ return (
62
+ <>
63
+ <DefaultComponent onChange={ onChange } { ...args } />
64
+ </>
65
+ );
66
+ };
67
+
68
+ export const Inline = () => {
69
+ const [ options, setOptions ] = useState( defaultOptions );
70
+
71
+ const onChange = useCallback( value => {
72
+ setOptions(
73
+ defaultOptions.filter(
74
+ option => ! value || option.label.toLowerCase().indexOf( value.toLowerCase() ) >= 0
75
+ )
76
+ );
77
+ } );
78
+
79
+ const args = {
80
+ label: 'Label',
81
+ options,
82
+ };
83
+
84
+ return (
85
+ <>
86
+ <DefaultComponent isInline={ true } onChange={ onChange } { ...args } />
87
+ </>
88
+ );
89
+ };
@@ -0,0 +1,32 @@
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 { FormAutocomplete } from './FormAutocomplete';
11
+
12
+ const options = [
13
+ { value: 'chocolate', label: 'Chocolate' },
14
+ { value: 'strawberry', label: 'Strawberry Chocolate Vanilla Chocolate Vanilla' },
15
+ { value: 'vanilla', label: 'Vanilla' },
16
+ ];
17
+
18
+ const defaultProps = {
19
+ label: 'This is a label',
20
+ options,
21
+ };
22
+
23
+ describe( '<FormAutocomplete />', () => {
24
+ it( 'renders the FormAutocomplete component', async () => {
25
+ const { container } = render( <FormAutocomplete id="my_desert_list" { ...defaultProps } /> );
26
+
27
+ expect( screen.getByLabelText( defaultProps.label ) ).toBeInTheDocument();
28
+
29
+ // Check for accessibility issues
30
+ await expect( await axe( container ) ).toHaveNoViolations();
31
+ } );
32
+ } );
@@ -70,7 +70,7 @@ const DefaultComponent = ( { label = 'Label', width = 250, onChange, ...rest } )
70
70
  export const Default = DefaultComponent.bind( {} );
71
71
  Default.args = {
72
72
  placeholder: '- Select -',
73
- options: options,
73
+ options,
74
74
  };
75
75
 
76
76
  export const WithGroup = DefaultComponent.bind( {} );
@@ -3,11 +3,13 @@
3
3
  */
4
4
 
5
5
  import { FormSelect } from './FormSelect';
6
+ import { FormAutocomplete } from './FormAutocomplete';
6
7
  import { Form } from './Form';
7
8
 
8
9
  const Select = FormSelect;
10
+ const Autocomplete = FormAutocomplete;
9
11
  const Root = Form;
10
12
 
11
- export { Root, Select };
13
+ export { Root, Select, Autocomplete };
12
14
 
13
15
  export default Root;
@@ -38,6 +38,7 @@ import {
38
38
  Textarea,
39
39
  Checkbox,
40
40
  } from './Form';
41
+ import { Accordion } from './Accordion';
41
42
  import { Grid } from './Grid';
42
43
  import { Heading } from './Heading';
43
44
  import { Link } from './Link';
@@ -56,6 +57,7 @@ import theme from './theme';
56
57
  import { Wizard, WizardStep, WizardStepHorizontal } from './Wizard';
57
58
 
58
59
  export {
60
+ Accordion,
59
61
  Avatar,
60
62
  Badge,
61
63
  Box,
@@ -11,3 +11,17 @@ export const getColor = ( color, variant = 'default' ) => {
11
11
 
12
12
  return Valet[ color ][ variant ].value;
13
13
  };
14
+
15
+ const resolvePath = ( object, path, defaultValue ) => {
16
+ return path.split( '.' ).reduce( ( acc, property ) => {
17
+ return acc ? acc[ property ] : defaultValue;
18
+ }, object );
19
+ };
20
+
21
+ export const getVariants = color => {
22
+ const property = resolvePath( Valet, color, {} );
23
+
24
+ return Object.keys( property ).reduce( ( variants, variant ) => {
25
+ return { ...variants, [ variant ]: property[ variant ].value };
26
+ }, {} );
27
+ };
@@ -2,7 +2,7 @@
2
2
  * Internal dependencies
3
3
  */
4
4
  import { light, dark } from './colors';
5
- import { getColor } from './getColor';
5
+ import { getColor, getVariants } from './getColor';
6
6
 
7
7
  const textStyles = {
8
8
  h1: {
@@ -90,6 +90,8 @@ export default {
90
90
  text: getColor( 'text', 'secondary' ),
91
91
  heading: getColor( 'text', 'primary' ),
92
92
  background: getColor( 'background', 'primary' ),
93
+ gold: getVariants( 'color.gold' ),
94
+ gray: getVariants( 'color.gray' ),
93
95
  backgroundSecondary: light.grey[ '10' ],
94
96
  primary: light.brand[ '70' ],
95
97
  secondary: '#30c',
@@ -160,6 +162,10 @@ export default {
160
162
  '0px 2.76726px 2.21381px rgba(0, 0, 0, 0.0196802), 0px 6.6501px 5.32008px rgba(0, 0, 0, 0.0282725), 0px 12.5216px 10.0172px rgba(0, 0, 0, 0.035), 0px 22.3363px 17.869px rgba(0, 0, 0, 0.0417275), 0px 41.7776px 33.4221px rgba(0, 0, 0, 0.0503198), 0px 100px 80px rgba(0, 0, 0, 0.07)',
161
163
  },
162
164
 
165
+ tag: {
166
+ gold: getVariants( 'tag.gold' ),
167
+ },
168
+
163
169
  cards: {
164
170
  primary: {
165
171
  padding: 3,
@@ -0,0 +1 @@
1
+ module.exports = {};