@automattic/vip-design-system 2.18.1 → 2.20.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.
- package/.storybook/preview-head.html +1 -0
- package/build/system/Badge/Badge.js +2 -1
- package/build/system/DescriptionList/DescriptionList.js +0 -1
- package/build/system/Form/Label.d.ts +1 -3
- package/build/system/Form/Label.js +1 -3
- package/build/system/Form/RadioBoxGroup.jsx +12 -1
- package/build/system/Form/RadioBoxGroup.stories.jsx +6 -1
- package/build/system/Heading/Heading.js +2 -3
- package/build/system/Heading/Heading.stories.js +15 -2
- package/build/system/Nav/styles/variants/menu.js +1 -2
- package/build/system/NewForm/FormAutocompleteMultiselect.jsx +143 -12
- package/build/system/NewForm/FormAutocompleteMultiselect.stories.d.ts +22 -2
- package/build/system/NewForm/FormAutocompleteMultiselect.stories.jsx +21 -0
- package/build/system/NewForm/FormAutocompleteMultiselect.test.jsx +67 -1
- package/build/system/NewForm/FormAutocompleteMultiselectInlineChip.d.ts +7 -0
- package/build/system/NewForm/FormAutocompleteMultiselectInlineChip.js +62 -0
- package/build/system/Notice/Notice.js +1 -1
- package/build/system/Pagination/styles.js +1 -4
- package/build/system/Table/TableCell.js +1 -1
- package/build/system/Text/Text.js +0 -1
- package/build/system/Text/Text.stories.js +16 -13
- package/build/system/Toolbar/Logo.js +22 -6
- package/build/system/Wizard/Wizard.stories.js +11 -11
- package/build/system/Wizard/WizardStep.js +0 -2
- package/build/system/theme/generated/valet-theme-dark.json +224 -227
- package/build/system/theme/generated/valet-theme-light.json +224 -227
- package/build/system/theme/getPropValue.js +3 -7
- package/build/system/theme/index.d.ts +20 -12
- package/build/system/theme/index.js +27 -20
- package/docs/SETUP.md +1 -1
- package/package.json +1 -1
- package/src/system/Badge/Badge.tsx +2 -1
- package/src/system/DescriptionList/DescriptionList.tsx +0 -1
- package/src/system/Form/Label.tsx +1 -3
- package/src/system/Form/RadioBoxGroup.jsx +12 -1
- package/src/system/Form/RadioBoxGroup.stories.jsx +6 -1
- package/src/system/Heading/Heading.stories.tsx +10 -1
- package/src/system/Heading/Heading.tsx +1 -2
- package/src/system/Nav/styles/variants/menu.ts +1 -2
- package/src/system/NewForm/FormAutocompleteMultiselect.jsx +143 -12
- package/src/system/NewForm/FormAutocompleteMultiselect.stories.jsx +21 -0
- package/src/system/NewForm/FormAutocompleteMultiselect.test.jsx +67 -1
- package/src/system/NewForm/FormAutocompleteMultiselectInlineChip.tsx +72 -0
- package/src/system/Notice/Notice.tsx +1 -1
- package/src/system/Pagination/styles.ts +1 -4
- package/src/system/Table/TableCell.tsx +1 -1
- package/src/system/Text/Text.stories.tsx +7 -4
- package/src/system/Text/Text.tsx +0 -1
- package/src/system/Toolbar/Logo.tsx +19 -2
- package/src/system/Wizard/Wizard.stories.tsx +11 -11
- package/src/system/Wizard/WizardStep.tsx +0 -2
- package/src/system/theme/generated/valet-theme-dark.json +224 -227
- package/src/system/theme/generated/valet-theme-light.json +224 -227
- package/src/system/theme/getPropValue.ts +1 -8
- package/src/system/theme/index.ts +33 -18
- package/tokens/valet-core/valet-core.json +39 -9
- package/tokens/valet-core/wpvip-product-core.json +88 -125
|
@@ -16,6 +16,7 @@ var _ThemeBuilder = ThemeBuilder(Valet),
|
|
|
16
16
|
ValetTheme = _ThemeBuilder.ValetTheme,
|
|
17
17
|
getHeadingStyles = _ThemeBuilder.getHeadingStyles;
|
|
18
18
|
var light = ColorBuilder(ValetTheme);
|
|
19
|
+
var supportLabelDefaultTypography = getPropValue('support', 'label-default');
|
|
19
20
|
|
|
20
21
|
// Dark
|
|
21
22
|
var _ThemeBuilder2 = ThemeBuilder(ValetDark),
|
|
@@ -29,12 +30,6 @@ var outline = {
|
|
|
29
30
|
outlineWidth: '1px',
|
|
30
31
|
boxShadow: "0 0 0 1px " + getPropValue('focus', 'inset') + ", 0 0 0 3px " + getPropValue('focus')
|
|
31
32
|
};
|
|
32
|
-
var fonts = {
|
|
33
|
-
body: '-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',
|
|
34
|
-
heading: 'inherit',
|
|
35
|
-
monospace: '"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace',
|
|
36
|
-
serif: 'recoletaregular, Georgia, serif'
|
|
37
|
-
};
|
|
38
33
|
var getComponentColors = function getComponentColors(theme, gColor, gVariants) {
|
|
39
34
|
return {
|
|
40
35
|
// Valet Theme Colors
|
|
@@ -175,13 +170,15 @@ var getComponentColors = function getComponentColors(theme, gColor, gVariants) {
|
|
|
175
170
|
export default {
|
|
176
171
|
outline: outline,
|
|
177
172
|
space: getVariants('space'),
|
|
178
|
-
fonts:
|
|
173
|
+
fonts: {
|
|
174
|
+
monospace: getPropValue('fontFamily', 'monospace'),
|
|
175
|
+
"default": getPropValue('fontFamily', 'default')
|
|
176
|
+
},
|
|
179
177
|
fontSizes: getVariants('fontSize.static'),
|
|
180
178
|
breakpoints: generateBreakpoints(getVariants('breakpoint')),
|
|
181
179
|
fontWeights: {
|
|
182
|
-
body: getPropValue('fontWeight', 'body'),
|
|
183
|
-
heading: getPropValue('fontWeight', 'heading'),
|
|
184
180
|
regular: getPropValue('fontWeight', 'regular'),
|
|
181
|
+
semibold: getPropValue('fontWeight', 'semibold'),
|
|
185
182
|
bold: getPropValue('fontWeight', 'bold'),
|
|
186
183
|
medium: getPropValue('fontWeight', 'medium'),
|
|
187
184
|
light: getPropValue('fontWeight', 'light')
|
|
@@ -276,9 +273,13 @@ export default {
|
|
|
276
273
|
}
|
|
277
274
|
}
|
|
278
275
|
},
|
|
276
|
+
forms: {
|
|
277
|
+
label: _extends({}, supportLabelDefaultTypography)
|
|
278
|
+
},
|
|
279
279
|
buttons: {
|
|
280
|
-
primary: {
|
|
281
|
-
|
|
280
|
+
primary: _extends({}, supportLabelDefaultTypography, {
|
|
281
|
+
// Button label weight: theme `medium` (500).
|
|
282
|
+
fontWeight: 'medium',
|
|
282
283
|
color: 'button.primary.label.default',
|
|
283
284
|
bg: 'button.primary.background.default',
|
|
284
285
|
border: '1px solid transparent',
|
|
@@ -287,7 +288,6 @@ export default {
|
|
|
287
288
|
minHeight: '38px',
|
|
288
289
|
display: 'inline-flex',
|
|
289
290
|
cursor: 'pointer',
|
|
290
|
-
fontWeight: 'medium',
|
|
291
291
|
boxShadow: 'none',
|
|
292
292
|
borderRadius: 1,
|
|
293
293
|
'&:hover': {
|
|
@@ -326,7 +326,7 @@ export default {
|
|
|
326
326
|
backgroundColor: 'button.secondary.background.disabled',
|
|
327
327
|
color: 'button.secondary.label.default'
|
|
328
328
|
}
|
|
329
|
-
},
|
|
329
|
+
}),
|
|
330
330
|
secondary: {
|
|
331
331
|
variant: 'buttons.primary',
|
|
332
332
|
color: 'button.secondary.label.default',
|
|
@@ -490,7 +490,14 @@ export default {
|
|
|
490
490
|
variant: 'buttons.tertiary'
|
|
491
491
|
}
|
|
492
492
|
},
|
|
493
|
-
text: getHeadingStyles(),
|
|
493
|
+
text: _extends({}, getHeadingStyles(), getVariants('body'), {
|
|
494
|
+
'support-helper-text': getPropValue('support', 'helper-text'),
|
|
495
|
+
'support-label-xs': getPropValue('support', 'label-xs'),
|
|
496
|
+
'support-label-small': getPropValue('support', 'label-small'),
|
|
497
|
+
'support-label-default': getPropValue('support', 'label-default'),
|
|
498
|
+
'support-label-default-quiet': getPropValue('support', 'label-default-quiet'),
|
|
499
|
+
'support-label-default-loud': getPropValue('support', 'label-default-loud')
|
|
500
|
+
}),
|
|
494
501
|
dialog: {
|
|
495
502
|
modal: {
|
|
496
503
|
position: 'fixed',
|
|
@@ -569,10 +576,10 @@ export default {
|
|
|
569
576
|
}
|
|
570
577
|
},
|
|
571
578
|
styles: {
|
|
572
|
-
root:
|
|
573
|
-
fontFamily: 'body',
|
|
574
|
-
lineHeight: 'body',
|
|
575
|
-
fontWeight: 'body',
|
|
579
|
+
root: {
|
|
580
|
+
fontFamily: getPropValue('body', 'default').fontFamily,
|
|
581
|
+
lineHeight: getPropValue('body', 'default').lineHeight,
|
|
582
|
+
fontWeight: getPropValue('body', 'default').fontWeight,
|
|
576
583
|
color: 'text',
|
|
577
584
|
backgroundColor: 'backgrounds.primary',
|
|
578
585
|
webkitFontSmoothing: 'antialiased',
|
|
@@ -589,11 +596,11 @@ export default {
|
|
|
589
596
|
display: 'block'
|
|
590
597
|
},
|
|
591
598
|
pre: {
|
|
592
|
-
fontFamily: '
|
|
599
|
+
fontFamily: 'default'
|
|
593
600
|
},
|
|
594
601
|
p: {
|
|
595
602
|
color: 'text'
|
|
596
603
|
}
|
|
597
|
-
}
|
|
604
|
+
}
|
|
598
605
|
}
|
|
599
606
|
};
|
package/docs/SETUP.md
CHANGED
package/package.json
CHANGED
|
@@ -27,6 +27,7 @@ export const Badge = forwardRef< HTMLDivElement, BadgeProps >(
|
|
|
27
27
|
as="span"
|
|
28
28
|
sx={ {
|
|
29
29
|
fontSize: 0,
|
|
30
|
+
letterSpacing: '0.01em',
|
|
30
31
|
padding: 0, // do we need padding declared twice here?
|
|
31
32
|
bg: `tag.${ variant }.background`,
|
|
32
33
|
color: `tag.${ variant }.text`,
|
|
@@ -35,7 +36,7 @@ export const Badge = forwardRef< HTMLDivElement, BadgeProps >(
|
|
|
35
36
|
px: 2,
|
|
36
37
|
display: 'inline-block',
|
|
37
38
|
borderRadius: 1,
|
|
38
|
-
fontWeight: '
|
|
39
|
+
fontWeight: 'medium',
|
|
39
40
|
a: {
|
|
40
41
|
color: `tag.${ variant }.text`,
|
|
41
42
|
'&:hover, &:focus, &:active': {
|
|
@@ -13,9 +13,7 @@ import { RequiredLabel } from './RequiredLabel';
|
|
|
13
13
|
|
|
14
14
|
export const baseLabelColor = 'input.label.default';
|
|
15
15
|
export const baseLabelStyle = {
|
|
16
|
-
|
|
17
|
-
fontSize: 2,
|
|
18
|
-
lineHeight: 1.5,
|
|
16
|
+
variant: 'forms.label',
|
|
19
17
|
color: baseLabelColor,
|
|
20
18
|
};
|
|
21
19
|
|
|
@@ -72,7 +72,14 @@ const RadioOption = ( {
|
|
|
72
72
|
{ ...restOption }
|
|
73
73
|
/>
|
|
74
74
|
<div
|
|
75
|
-
sx={ {
|
|
75
|
+
sx={ {
|
|
76
|
+
mb: 0,
|
|
77
|
+
color: 'input.radio-box.label.primary.default',
|
|
78
|
+
fontWeight: 'semibold',
|
|
79
|
+
p: 3,
|
|
80
|
+
pr: 0,
|
|
81
|
+
flex: 'auto',
|
|
82
|
+
} }
|
|
76
83
|
>
|
|
77
84
|
<label htmlFor={ forLabel } { ...labelProps }>
|
|
78
85
|
{ label }
|
|
@@ -82,7 +89,11 @@ const RadioOption = ( {
|
|
|
82
89
|
sx={ {
|
|
83
90
|
color: 'input.radio-box.label.secondary.default',
|
|
84
91
|
mb: 0,
|
|
92
|
+
mt: 1,
|
|
85
93
|
fontSize: 1,
|
|
94
|
+
fontWeight: 'regular',
|
|
95
|
+
letterSpacing: '0.01em',
|
|
96
|
+
lineHeight: '140%',
|
|
86
97
|
display: 'block',
|
|
87
98
|
} }
|
|
88
99
|
id={ describedById }
|
|
@@ -60,7 +60,12 @@ export const Primary = {
|
|
|
60
60
|
args: {
|
|
61
61
|
defaultValue: 'one',
|
|
62
62
|
options: [
|
|
63
|
-
{
|
|
63
|
+
{
|
|
64
|
+
label: 'One',
|
|
65
|
+
value: 'one',
|
|
66
|
+
description:
|
|
67
|
+
'This is a longer description that allows us to see the text wrap and determine if the line height is correct',
|
|
68
|
+
},
|
|
64
69
|
{ label: 'Two', value: 'two', description: 'This is a description' },
|
|
65
70
|
{ label: 'Three', value: 'three', description: 'This is a description' },
|
|
66
71
|
],
|
|
@@ -27,7 +27,16 @@ export const Default: Story = {
|
|
|
27
27
|
<Heading variant="h3">Heading Three</Heading>
|
|
28
28
|
<Heading variant="h4">Heading Four</Heading>
|
|
29
29
|
<Heading variant="h5">Heading Five</Heading>
|
|
30
|
-
|
|
30
|
+
|
|
31
|
+
<Heading variant="h3" as="h1">
|
|
32
|
+
Heading One with Heading Three Styles
|
|
33
|
+
</Heading>
|
|
34
|
+
<Heading as="p" sx={ { variant: 'text.caps' } }>
|
|
35
|
+
Paragraph with Caps Styles
|
|
36
|
+
</Heading>
|
|
37
|
+
<Heading as="h3" sx={ { variant: 'text.caps' } }>
|
|
38
|
+
Heading Three with Caps Styles
|
|
39
|
+
</Heading>
|
|
31
40
|
</Box>
|
|
32
41
|
),
|
|
33
42
|
};
|
|
@@ -20,10 +20,9 @@ export const Heading = forwardRef< HTMLHeadingElement, HeadingProps >(
|
|
|
20
20
|
( { variant = 'h3', sx, className, ...rest }: HeadingProps, ref: Ref< HTMLHeadingElement > ) => (
|
|
21
21
|
<ThemeHeading
|
|
22
22
|
as={ variant }
|
|
23
|
+
variant={ variant }
|
|
23
24
|
sx={ {
|
|
24
25
|
color: 'heading',
|
|
25
|
-
// pass variant prop to sx
|
|
26
|
-
variant: `text.${ variant.toString() }`,
|
|
27
26
|
...sx,
|
|
28
27
|
} }
|
|
29
28
|
className={ classNames( 'vip-heading-component', className ) }
|
|
@@ -13,6 +13,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
|
13
13
|
*/
|
|
14
14
|
import { FormAutocompleteMultiselectBadge } from './FormAutocompleteMultiselectBadge';
|
|
15
15
|
import { FormAutocompleteMultiselectButton } from './FormAutocompleteMultiselectButton';
|
|
16
|
+
import { FormAutocompleteMultiselectInlineChip } from './FormAutocompleteMultiselectInlineChip';
|
|
16
17
|
import { FormSelectArrow } from './FormSelectArrow';
|
|
17
18
|
import { FormSelectContent } from './FormSelectContent';
|
|
18
19
|
import { FormSelectLoading } from './FormSelectLoading';
|
|
@@ -22,6 +23,14 @@ import { Validation } from '../Form';
|
|
|
22
23
|
import { baseControlBorderStyle, inputBaseText } from '../Form/Input.styles';
|
|
23
24
|
import { Label } from '../Form/Label';
|
|
24
25
|
|
|
26
|
+
const escapeHtml = str =>
|
|
27
|
+
String( str )
|
|
28
|
+
.replace( /&/g, '&' )
|
|
29
|
+
.replace( /</g, '<' )
|
|
30
|
+
.replace( />/g, '>' )
|
|
31
|
+
.replace( /"/g, '"' )
|
|
32
|
+
.replace( /'/g, ''' );
|
|
33
|
+
|
|
25
34
|
const baseBorderTextColors = {
|
|
26
35
|
...baseControlBorderStyle,
|
|
27
36
|
backgroundColor: 'layer.2',
|
|
@@ -96,7 +105,33 @@ const searchIconStyles = {
|
|
|
96
105
|
},
|
|
97
106
|
};
|
|
98
107
|
|
|
99
|
-
const
|
|
108
|
+
const inlineChipsContainerStyles = {
|
|
109
|
+
...defaultStyles,
|
|
110
|
+
display: 'flex',
|
|
111
|
+
flexWrap: 'wrap',
|
|
112
|
+
alignItems: 'center',
|
|
113
|
+
p: 1,
|
|
114
|
+
pr: 0,
|
|
115
|
+
position: 'relative',
|
|
116
|
+
'& .autocomplete__input': {
|
|
117
|
+
...defaultStyles[ '& .autocomplete__input' ],
|
|
118
|
+
lineHeight: '24px',
|
|
119
|
+
minHeight: '24px',
|
|
120
|
+
},
|
|
121
|
+
'&:focus-within': theme => theme.outline,
|
|
122
|
+
'& .autocomplete__wrapper': {
|
|
123
|
+
position: 'static',
|
|
124
|
+
width: '100%',
|
|
125
|
+
lineHeight: '24px',
|
|
126
|
+
minHeight: '24px',
|
|
127
|
+
'& .autocomplete__dropdown-arrow-down': {
|
|
128
|
+
top: 'unset',
|
|
129
|
+
bottom: '6px',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const DefaultArrow = config => <FormSelectArrow className={ config.className } />;
|
|
100
135
|
|
|
101
136
|
const AddSelectionStatus = ( { status } ) => {
|
|
102
137
|
return (
|
|
@@ -156,10 +191,12 @@ const FormAutocompleteMultiselect = React.forwardRef(
|
|
|
156
191
|
listType = 'button',
|
|
157
192
|
initialValue = [],
|
|
158
193
|
allowCustom = false,
|
|
194
|
+
variant,
|
|
159
195
|
...props
|
|
160
196
|
},
|
|
161
197
|
forwardRef
|
|
162
198
|
) => {
|
|
199
|
+
const isInlineChips = variant === 'inline-chips';
|
|
163
200
|
const OPTION_ACTION = {
|
|
164
201
|
ADD: 'add',
|
|
165
202
|
REMOVE: 'remove',
|
|
@@ -175,6 +212,7 @@ const FormAutocompleteMultiselect = React.forwardRef(
|
|
|
175
212
|
option: null,
|
|
176
213
|
index: -1,
|
|
177
214
|
} );
|
|
215
|
+
const justSelectedRef = React.useRef( false );
|
|
178
216
|
let debounceTimeout;
|
|
179
217
|
forwardRef = forwardRef || React.createRef();
|
|
180
218
|
|
|
@@ -283,11 +321,44 @@ const FormAutocompleteMultiselect = React.forwardRef(
|
|
|
283
321
|
data = handleTypeChange( query );
|
|
284
322
|
}
|
|
285
323
|
const optionForDisplay = data?.map( option => optionLabel( option ) );
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
324
|
+
if ( isInlineChips ) {
|
|
325
|
+
populateResults( optionForDisplay );
|
|
326
|
+
} else {
|
|
327
|
+
populateResults(
|
|
328
|
+
optionForDisplay.filter( option => ! selectedOptions.includes( option ) )
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
[ autoFilter, isDirty, isInlineChips, onInputChange, options, selectedOptions ]
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
const onValueChangeInlineChips = useCallback(
|
|
336
|
+
inputValue => {
|
|
337
|
+
if ( ! inputValue ) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
justSelectedRef.current = true;
|
|
341
|
+
if ( selectedOptions.includes( inputValue ) ) {
|
|
342
|
+
unselectValue( inputValue, selectedOptions.indexOf( inputValue ) );
|
|
343
|
+
} else {
|
|
344
|
+
setCurrentOption( { action: OPTION_ACTION.ADD, option: inputValue } );
|
|
345
|
+
setSelectedOptions( [ ...selectedOptions, inputValue ] );
|
|
346
|
+
}
|
|
289
347
|
},
|
|
290
|
-
[
|
|
348
|
+
[ selectedOptions, unselectValue ]
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const inlineChipsTemplates = useMemo(
|
|
352
|
+
() => ( {
|
|
353
|
+
suggestion: suggestion => {
|
|
354
|
+
const isSelected = selectedOptions.includes( suggestion );
|
|
355
|
+
const check = isSelected ? '✓' : '';
|
|
356
|
+
return `<span style="display:flex;align-items:center;gap:8px"><span style="width:16px;flex-shrink:0">${ check }</span>${ escapeHtml(
|
|
357
|
+
suggestion
|
|
358
|
+
) }</span>`;
|
|
359
|
+
},
|
|
360
|
+
} ),
|
|
361
|
+
[ selectedOptions ]
|
|
291
362
|
);
|
|
292
363
|
|
|
293
364
|
useEffect( () => {
|
|
@@ -332,7 +403,16 @@ const FormAutocompleteMultiselect = React.forwardRef(
|
|
|
332
403
|
selectedOptions,
|
|
333
404
|
selectedOptions.map( option => option?.label || option )
|
|
334
405
|
);
|
|
335
|
-
|
|
406
|
+
if ( isInlineChips && justSelectedRef.current && forwardRef?.current ) {
|
|
407
|
+
justSelectedRef.current = false;
|
|
408
|
+
forwardRef.current.setState( {
|
|
409
|
+
...forwardRef.current.state,
|
|
410
|
+
query: '',
|
|
411
|
+
menuOpen: true,
|
|
412
|
+
} );
|
|
413
|
+
} else {
|
|
414
|
+
resetInputState();
|
|
415
|
+
}
|
|
336
416
|
}, [ selectedOptions ] );
|
|
337
417
|
|
|
338
418
|
// Update the select status for screen readers
|
|
@@ -340,15 +420,65 @@ const FormAutocompleteMultiselect = React.forwardRef(
|
|
|
340
420
|
if ( currentOption.action === OPTION_ACTION.ADD ) {
|
|
341
421
|
setAddStatus( `${ currentOption.option } added to the list.` );
|
|
342
422
|
setCurrentOption( { action: OPTION_ACTION.NONE, option: null } );
|
|
343
|
-
} else if ( currentOption.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
423
|
+
} else if ( currentOption.action === OPTION_ACTION.REMOVE ) {
|
|
424
|
+
setAddStatus( `${ currentOption.option } removed from the list.` );
|
|
425
|
+
if ( isInlineChips ) {
|
|
426
|
+
global.document.querySelector( `#${ forLabel }` )?.focus();
|
|
427
|
+
} else if ( currentOption.index === selectedOptions.length && selectedOptions.length > 0 ) {
|
|
428
|
+
global.document.querySelector( '.vip-button-component' )?.focus();
|
|
429
|
+
} else if ( selectedOptions.length === 0 ) {
|
|
430
|
+
global.document.querySelector( '.autocomplete__input' )?.focus();
|
|
431
|
+
}
|
|
432
|
+
setCurrentOption( { action: OPTION_ACTION.NONE, option: null } );
|
|
349
433
|
}
|
|
350
434
|
}, [ currentOption ] );
|
|
351
435
|
|
|
436
|
+
if ( isInlineChips ) {
|
|
437
|
+
return (
|
|
438
|
+
<div className={ classNames( 'vip-form-autocomplete-component', className ) }>
|
|
439
|
+
{ label && <SelectLabel /> }
|
|
440
|
+
<div sx={ inlineChipsContainerStyles }>
|
|
441
|
+
{ selectedOptions.map( ( option, idx ) => (
|
|
442
|
+
<FormAutocompleteMultiselectInlineChip
|
|
443
|
+
key={ option }
|
|
444
|
+
index={ idx }
|
|
445
|
+
option={ option }
|
|
446
|
+
unselectValue={ unselectValue }
|
|
447
|
+
/>
|
|
448
|
+
) ) }
|
|
449
|
+
<div sx={ { flex: '1 1 120px', minWidth: '120px' } }>
|
|
450
|
+
<Autocomplete
|
|
451
|
+
id={ forLabel }
|
|
452
|
+
aria-busy={ loading }
|
|
453
|
+
showAllValues={ true }
|
|
454
|
+
ref={ forwardRef }
|
|
455
|
+
source={ source || suggest }
|
|
456
|
+
defaultValue={ value }
|
|
457
|
+
displayMenu={ displayMenu }
|
|
458
|
+
onConfirm={ onValueChangeInlineChips }
|
|
459
|
+
tNoResults={ noOptionsMessage }
|
|
460
|
+
required={ required }
|
|
461
|
+
dropdownArrow={ dropdownArrow }
|
|
462
|
+
confirmOnBlur={ false }
|
|
463
|
+
templates={ inlineChipsTemplates }
|
|
464
|
+
{ ...props }
|
|
465
|
+
placeholder={ selectedOptions.length > 0 ? '' : props.placeholder || '' }
|
|
466
|
+
/>
|
|
467
|
+
</div>
|
|
468
|
+
{ addStatus && <AddSelectionStatus status={ addStatus } /> }
|
|
469
|
+
{ loading && <FormSelectLoading sx={ { right: 7 } } /> }
|
|
470
|
+
</div>
|
|
471
|
+
{ hasError && errorMessage && (
|
|
472
|
+
<Flex sx={ { mt: 2 } }>
|
|
473
|
+
<Validation isValid={ false } describedId={ forLabel }>
|
|
474
|
+
{ errorMessage }
|
|
475
|
+
</Validation>
|
|
476
|
+
</Flex>
|
|
477
|
+
) }
|
|
478
|
+
</div>
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
352
482
|
return (
|
|
353
483
|
<div className={ classNames( 'vip-form-autocomplete-component', className ) }>
|
|
354
484
|
{ label && ! isInline && <SelectLabel /> }
|
|
@@ -435,6 +565,7 @@ FormAutocompleteMultiselect.propTypes = {
|
|
|
435
565
|
dropdownArrow: PropTypes.node,
|
|
436
566
|
initialValue: PropTypes.array,
|
|
437
567
|
allowCustom: PropTypes.bool,
|
|
568
|
+
variant: PropTypes.string,
|
|
438
569
|
};
|
|
439
570
|
|
|
440
571
|
FormAutocompleteMultiselect.displayName = 'FormAutocompleteMultiselect';
|
|
@@ -134,6 +134,27 @@ export const WithStaticData = {
|
|
|
134
134
|
},
|
|
135
135
|
};
|
|
136
136
|
|
|
137
|
+
export const InlineChips = {
|
|
138
|
+
render: props => <DefaultComponent { ...props } width={ 500 } />,
|
|
139
|
+
args: {
|
|
140
|
+
label: 'Post Categories',
|
|
141
|
+
options: [
|
|
142
|
+
{ value: 'breaking-news', label: 'Breaking News' },
|
|
143
|
+
{ value: 'world-news', label: 'World News' },
|
|
144
|
+
{ value: 'us-news', label: 'U.S. News' },
|
|
145
|
+
{ value: 'climate-environment', label: 'Climate & Environment' },
|
|
146
|
+
{ value: 'obituaries', label: 'Obituaries' },
|
|
147
|
+
{ value: 'technology', label: 'Technology' },
|
|
148
|
+
{ value: 'entertainment', label: 'Entertainment' },
|
|
149
|
+
{ value: 'real-estate', label: 'Real Estate' },
|
|
150
|
+
],
|
|
151
|
+
variant: 'inline-chips',
|
|
152
|
+
showAllValues: true,
|
|
153
|
+
placeholder: 'Search categories...',
|
|
154
|
+
initialValue: [ 'Breaking News' ],
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
137
158
|
export const WithDynamicData = {
|
|
138
159
|
render: () => {
|
|
139
160
|
const [ selectedValues, setSelectedValues ] = useState( [] );
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
5
5
|
import { axe } from 'jest-axe';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -37,3 +37,69 @@ describe( '<FormAutocompleteMultiselect />', () => {
|
|
|
37
37
|
await expect( await axe( container ) ).toHaveNoViolations();
|
|
38
38
|
} );
|
|
39
39
|
} );
|
|
40
|
+
|
|
41
|
+
describe( '<FormAutocompleteMultiselect variant="inline-chips" />', () => {
|
|
42
|
+
it( 'renders the inline-chips variant', async () => {
|
|
43
|
+
const { container } = render(
|
|
44
|
+
<FormAutocompleteMultiselect
|
|
45
|
+
forLabel="my_inline_chips"
|
|
46
|
+
label="Categories"
|
|
47
|
+
options={ options }
|
|
48
|
+
variant="inline-chips"
|
|
49
|
+
showAllValues
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
expect( screen.getByLabelText( 'Categories' ) ).toBeInTheDocument();
|
|
53
|
+
await expect( await axe( container ) ).toHaveNoViolations();
|
|
54
|
+
} );
|
|
55
|
+
|
|
56
|
+
it( 'renders initial values as inline chips', () => {
|
|
57
|
+
render(
|
|
58
|
+
<FormAutocompleteMultiselect
|
|
59
|
+
forLabel="my_inline_chips_init"
|
|
60
|
+
label="Categories"
|
|
61
|
+
options={ options }
|
|
62
|
+
variant="inline-chips"
|
|
63
|
+
showAllValues
|
|
64
|
+
initialValue={ [ 'Chocolate', 'Vanilla' ] }
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
expect( screen.getByText( 'Chocolate' ) ).toBeInTheDocument();
|
|
68
|
+
expect( screen.getByText( 'Vanilla' ) ).toBeInTheDocument();
|
|
69
|
+
expect( screen.getByRole( 'button', { name: 'Remove Chocolate' } ) ).toBeInTheDocument();
|
|
70
|
+
expect( screen.getByRole( 'button', { name: 'Remove Vanilla' } ) ).toBeInTheDocument();
|
|
71
|
+
} );
|
|
72
|
+
|
|
73
|
+
it( 'removes a chip when the close button is clicked', () => {
|
|
74
|
+
render(
|
|
75
|
+
<FormAutocompleteMultiselect
|
|
76
|
+
forLabel="my_inline_chips_remove"
|
|
77
|
+
label="Categories"
|
|
78
|
+
options={ options }
|
|
79
|
+
variant="inline-chips"
|
|
80
|
+
showAllValues
|
|
81
|
+
initialValue={ [ 'Chocolate', 'Vanilla' ] }
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
expect( screen.getByText( 'Chocolate' ) ).toBeInTheDocument();
|
|
85
|
+
fireEvent.click( screen.getByRole( 'button', { name: 'Remove Chocolate' } ) );
|
|
86
|
+
expect( screen.queryByText( 'Chocolate' ) ).not.toBeInTheDocument();
|
|
87
|
+
expect( screen.getByText( 'Vanilla' ) ).toBeInTheDocument();
|
|
88
|
+
} );
|
|
89
|
+
|
|
90
|
+
it( 'announces removal to screen readers', () => {
|
|
91
|
+
const { container } = render(
|
|
92
|
+
<FormAutocompleteMultiselect
|
|
93
|
+
forLabel="my_inline_chips_a11y"
|
|
94
|
+
label="Categories"
|
|
95
|
+
options={ options }
|
|
96
|
+
variant="inline-chips"
|
|
97
|
+
showAllValues
|
|
98
|
+
initialValue={ [ 'Chocolate', 'Vanilla' ] }
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
fireEvent.click( screen.getByRole( 'button', { name: 'Remove Chocolate' } ) );
|
|
102
|
+
const statusEl = container.querySelector( '#vip-autocompletemultiselect-status' );
|
|
103
|
+
expect( statusEl ).toHaveTextContent( 'Chocolate removed from the list.' );
|
|
104
|
+
} );
|
|
105
|
+
} );
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** @jsxImportSource theme-ui */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* External dependencies
|
|
5
|
+
*/
|
|
6
|
+
import { MdClose } from 'react-icons/md';
|
|
7
|
+
|
|
8
|
+
const FormAutocompleteMultiselectInlineChip = ( {
|
|
9
|
+
index,
|
|
10
|
+
option,
|
|
11
|
+
unselectValue,
|
|
12
|
+
}: {
|
|
13
|
+
index: number;
|
|
14
|
+
option: string;
|
|
15
|
+
unselectValue: ( option: string, index: number ) => void;
|
|
16
|
+
} ) => {
|
|
17
|
+
return (
|
|
18
|
+
<span
|
|
19
|
+
sx={ {
|
|
20
|
+
display: 'inline-flex',
|
|
21
|
+
alignItems: 'center',
|
|
22
|
+
gap: 1,
|
|
23
|
+
px: 2,
|
|
24
|
+
py: '2px',
|
|
25
|
+
m: 1,
|
|
26
|
+
bg: 'layer.1',
|
|
27
|
+
borderRadius: 1,
|
|
28
|
+
fontSize: 1,
|
|
29
|
+
lineHeight: '16px',
|
|
30
|
+
whiteSpace: 'nowrap',
|
|
31
|
+
maxWidth: '100%',
|
|
32
|
+
} }
|
|
33
|
+
>
|
|
34
|
+
<span
|
|
35
|
+
sx={ {
|
|
36
|
+
overflow: 'hidden',
|
|
37
|
+
textOverflow: 'ellipsis',
|
|
38
|
+
whiteSpace: 'nowrap',
|
|
39
|
+
} }
|
|
40
|
+
>
|
|
41
|
+
{ option }
|
|
42
|
+
</span>
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
aria-label={ `Remove ${ option }` }
|
|
46
|
+
onClick={ e => {
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
e.stopPropagation();
|
|
49
|
+
unselectValue( option, index );
|
|
50
|
+
} }
|
|
51
|
+
sx={ {
|
|
52
|
+
display: 'inline-flex',
|
|
53
|
+
alignItems: 'center',
|
|
54
|
+
justifyContent: 'center',
|
|
55
|
+
p: 0,
|
|
56
|
+
border: 'none',
|
|
57
|
+
bg: 'transparent',
|
|
58
|
+
cursor: 'pointer',
|
|
59
|
+
color: 'text',
|
|
60
|
+
lineHeight: 0,
|
|
61
|
+
'&:hover': {
|
|
62
|
+
opacity: 0.7,
|
|
63
|
+
},
|
|
64
|
+
} }
|
|
65
|
+
>
|
|
66
|
+
<MdClose size={ 14 } />
|
|
67
|
+
</button>
|
|
68
|
+
</span>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export { FormAutocompleteMultiselectInlineChip };
|