@moda/om 16.2.0 → 17.0.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/CHANGELOG.md +26 -0
- package/dist/index.cjs.js +349 -299
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +348 -298
- package/dist/index.esm.js.map +1 -1
- package/dist/src/components/Expandable/Expandable.d.ts +1 -0
- package/dist/src/components/Select/Select.d.ts +1 -0
- package/dist/src/components/Select/SelectOption.d.ts +1 -0
- package/dist/src/components/Select/SelectOptions.d.ts +1 -0
- package/dist/styles.css +1 -1
- package/package.json +18 -8
- package/src/components/AspectRatioBox/AspectRatioBox.tsx +4 -4
- package/src/components/AspectRatioBox/index.ts +1 -1
- package/src/components/Badge/Badge.stories.tsx +5 -5
- package/src/components/Badge/Badge.test.tsx +1 -1
- package/src/components/Breadcrumbs/Breadcrumb/Breadcrumb.tsx +6 -6
- package/src/components/Breadcrumbs/Breadcrumbs.stories.tsx +6 -6
- package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +2 -2
- package/src/components/Breadcrumbs/Breadcrumbs.tsx +1 -1
- package/src/components/Breakpoint/Breakpoint.stories.tsx +10 -9
- package/src/components/Breakpoint/Breakpoint.test.tsx +2 -2
- package/src/components/Breakpoint/generateMediaQuery.ts +5 -4
- package/src/components/Button/Button.stories.tsx +4 -4
- package/src/components/Checkbox/Checkbox.stories.tsx +5 -5
- package/src/components/Checkbox/Checkbox.test.tsx +3 -3
- package/src/components/Checkbox/Checkbox.tsx +4 -4
- package/src/components/Clickable/Clickable.stories.tsx +3 -3
- package/src/components/ColorSwatch/ColorSwatch.stories.tsx +11 -11
- package/src/components/ColorSwatch/ColorSwatch.test.tsx +1 -1
- package/src/components/ColorSwatch/ColorSwatch.tsx +1 -1
- package/src/components/Constrain/Constrain.stories.tsx +1 -1
- package/src/components/Constrain/ExceedConstrain.stories.tsx +2 -2
- package/src/components/ControlLink/ControlLink.stories.tsx +1 -1
- package/src/components/ControlLink/index.ts +1 -1
- package/src/components/CreditCardNumberInput/CreditCardNumberInput.stories.tsx +1 -1
- package/src/components/CreditCardNumberInput/CreditCardNumberInput.test.tsx +4 -4
- package/src/components/CreditCardNumberInput/CreditCardNumberInput.tsx +1 -0
- package/src/components/DefinitionList/DefinitionList.stories.tsx +3 -3
- package/src/components/DefinitionList/DefinitionList.test.tsx +1 -1
- package/src/components/Divider/Divider.stories.tsx +4 -4
- package/src/components/Divider/Divider.test.tsx +1 -1
- package/src/components/Divider/Divider.tsx +1 -1
- package/src/components/Divider/NoLineDivider.tsx +1 -1
- package/src/components/Divider/TwoLineDivider.tsx +1 -1
- package/src/components/Expandable/Expandable.stories.tsx +8 -8
- package/src/components/Expandable/Expandable.test.tsx +1 -1
- package/src/components/Expandable/Expandable.tsx +12 -5
- package/src/components/Field/Field.stories.tsx +4 -4
- package/src/components/Field/Field.test.tsx +7 -7
- package/src/components/Field/Field.tsx +5 -5
- package/src/components/Field/index.ts +1 -0
- package/src/components/Loading/Loading.test.tsx +1 -1
- package/src/components/Loading/Loading.tsx +2 -1
- package/src/components/Modal/Modal.test.tsx +2 -2
- package/src/components/ModalOverlay/ModalOverlay.test.tsx +2 -2
- package/src/components/ModalOverlay/ModalOverlay.tsx +2 -2
- package/src/components/Overlay/Overlay.stories.tsx +1 -1
- package/src/components/Paginator/Paginator.tsx +3 -3
- package/src/components/PasswordInput/PasswordInput.stories.tsx +1 -1
- package/src/components/PasswordInput/PasswordInput.tsx +6 -6
- package/src/components/Popover/Popover.stories.tsx +1 -1
- package/src/components/Popover/Popover.tsx +10 -7
- package/src/components/PromoBanner/PromoBanner.stories.tsx +9 -9
- package/src/components/PromoBanner/PromoBanner.tsx +1 -1
- package/src/components/RadioButton/RadioButton.stories.tsx +2 -2
- package/src/components/RadioButton/RadioButton.tsx +4 -4
- package/src/components/SearchInput/SearchInput.stories.tsx +2 -2
- package/src/components/SearchInput/SearchInput.test.tsx +1 -1
- package/src/components/SearchInput/SearchInput.tsx +3 -3
- package/src/components/Select/MultiSelect.tsx +7 -7
- package/src/components/Select/Select.stories.tsx +15 -14
- package/src/components/Select/Select.test.tsx +13 -13
- package/src/components/Select/Select.tsx +11 -7
- package/src/components/Select/SelectOption.tsx +5 -2
- package/src/components/Select/SelectOptions.tsx +8 -5
- package/src/components/SelectableButton/SelectableButton.stories.tsx +1 -1
- package/src/components/Shape/ArcWindow.tsx +10 -10
- package/src/components/Shape/Bradley.tsx +4 -4
- package/src/components/Shape/Brancusi.tsx +4 -4
- package/src/components/Shape/Circle.tsx +10 -10
- package/src/components/Shape/Diamond.tsx +10 -10
- package/src/components/Shape/Keyhole.tsx +4 -4
- package/src/components/Shape/Mirror.tsx +4 -4
- package/src/components/Shape/Oval.tsx +4 -4
- package/src/components/Shape/Triangle.tsx +7 -7
- package/src/components/Shape/Window.tsx +4 -4
- package/src/components/Stack/Stack.stories.tsx +3 -3
- package/src/components/Stack/Stack.test.tsx +1 -1
- package/src/components/Tabs/TabList.tsx +1 -1
- package/src/components/Tabs/TabPanel.tsx +1 -1
- package/src/components/Tabs/TabPanels.tsx +1 -1
- package/src/components/Tabs/Tabs.stories.tsx +2 -2
- package/src/components/Tabs/Tabs.test.tsx +2 -2
- package/src/components/Tabs/Tabs.tsx +7 -1
- package/src/components/Tag/Tag.tsx +6 -5
- package/src/components/Text/Text.stories.tsx +5 -5
- package/src/components/Text/Text.test.tsx +1 -1
- package/src/components/TextInput/TextInput.stories.tsx +1 -1
- package/src/components/TextInput/TextInput.test.tsx +1 -1
- package/src/components/Textarea/Textarea.stories.tsx +1 -1
- package/src/components/Textarea/Textarea.test.tsx +3 -3
- package/src/components/Toast/Toast.stories.tsx +1 -1
- package/src/components/Toast/Toast.test.tsx +1 -1
- package/src/components/Toast/Toast.tsx +6 -6
- package/src/components/VerticalDivider/VerticalDivider.stories.tsx +3 -3
- package/src/components/VerticalDivider/VerticalDivider.test.tsx +2 -2
- package/src/hooks/useKeyboardListNavigation.spec.ts +2 -1
- package/src/hooks/useKeyboardListNavigation.ts +2 -0
- package/src/hooks/useUpdateEffect.ts +1 -0
- package/src/index.ts +2 -2
- package/src/mixins/font-smoothing/font-smoothing.stories.tsx +8 -8
- package/src/mixins/overflow-scroll/overflow-scroll.stories.tsx +2 -2
- package/src/mixins/stack/stack.stories.tsx +5 -6
- package/src/mixins/thin-underline/thin-underline.stories.tsx +1 -1
- package/src/mixins/visually-hidden/visually-hidden.stories.tsx +1 -1
- package/src/utilities/States/State.tsx +3 -2
- package/src/utilities/States/States.tsx +4 -3
- package/src/utilities/States/StatesProps.tsx +5 -2
- package/src/utilities/States/isRenderProps.ts +1 -0
- package/src/utilities/States/stringifyProps.tsx +1 -0
|
@@ -66,7 +66,7 @@ export const Paginator: React.FC<Props> = ({
|
|
|
66
66
|
const prevPage = (page: number) => onGoToPage(page - 1);
|
|
67
67
|
|
|
68
68
|
return (
|
|
69
|
-
<section className={classNames('Paginator', className)} aria-label=
|
|
69
|
+
<section className={classNames('Paginator', className)} aria-label="Pagination">
|
|
70
70
|
<button
|
|
71
71
|
disabled={!canDoPrev(selectedPage)}
|
|
72
72
|
onClick={() => {
|
|
@@ -75,7 +75,7 @@ export const Paginator: React.FC<Props> = ({
|
|
|
75
75
|
}}
|
|
76
76
|
className={classNames('Paginator__button', 'Paginator__button--previous')}
|
|
77
77
|
>
|
|
78
|
-
<ChevronLeftIcon className=
|
|
78
|
+
<ChevronLeftIcon className="Paginator__previous-chevron" /> Previous
|
|
79
79
|
</button>
|
|
80
80
|
|
|
81
81
|
{pages.map((page, index) => (
|
|
@@ -108,7 +108,7 @@ export const Paginator: React.FC<Props> = ({
|
|
|
108
108
|
}}
|
|
109
109
|
className={classNames('Paginator__button', 'Paginator__button--next')}
|
|
110
110
|
>
|
|
111
|
-
Next <ChevronRightIcon className=
|
|
111
|
+
Next <ChevronRightIcon className="Paginator__next-chevron" />
|
|
112
112
|
</button>
|
|
113
113
|
</section>
|
|
114
114
|
);
|
|
@@ -6,6 +6,6 @@ export default { title: 'Components/PasswordInput' };
|
|
|
6
6
|
|
|
7
7
|
export const Default = () => (
|
|
8
8
|
<States<Partial<PasswordInputProps>> states={[{}, { error: 'Password cannot be empty.' }]}>
|
|
9
|
-
<PasswordInput id=
|
|
9
|
+
<PasswordInput id="signInPassword" placeholder="Password" />
|
|
10
10
|
</States>
|
|
11
11
|
);
|
|
@@ -15,23 +15,23 @@ export const PasswordInput = forwardRef(
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
|
-
<div className=
|
|
18
|
+
<div className="PasswordInput">
|
|
19
19
|
<Input
|
|
20
|
-
className=
|
|
20
|
+
className="PasswordInput__editor"
|
|
21
21
|
ref={ref}
|
|
22
22
|
type={passwordShown ? 'text' : 'password'}
|
|
23
23
|
{...rest}
|
|
24
24
|
/>
|
|
25
25
|
|
|
26
26
|
<Clickable
|
|
27
|
-
className=
|
|
27
|
+
className="PasswordInput__password-action"
|
|
28
28
|
onClick={togglePasswordVisiblity}
|
|
29
|
-
type=
|
|
29
|
+
type="button"
|
|
30
30
|
>
|
|
31
31
|
{passwordShown ? (
|
|
32
|
-
<Text treatment=
|
|
32
|
+
<Text treatment="eyebrow">HIDE</Text>
|
|
33
33
|
) : (
|
|
34
|
-
<Text treatment=
|
|
34
|
+
<Text treatment="eyebrow">SHOW</Text>
|
|
35
35
|
)}
|
|
36
36
|
</Clickable>
|
|
37
37
|
</div>
|
|
@@ -33,7 +33,7 @@ export const Default = () => (
|
|
|
33
33
|
>
|
|
34
34
|
<Popover content={Content} style={{ display: 'inline-block' }}>
|
|
35
35
|
<Text>
|
|
36
|
-
<ControlLink href=
|
|
36
|
+
<ControlLink href="https://www.modaoperandi.com/women">Hover on Link</ControlLink>
|
|
37
37
|
</Text>
|
|
38
38
|
</Popover>
|
|
39
39
|
</States>
|
|
@@ -13,6 +13,9 @@ export type PopoverProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export const POPOVER_MOUSEOUT_DELAY_MS = 200;
|
|
16
|
+
const THREE_QUARTER_SECOND_DELAY = 750;
|
|
17
|
+
const ONE_SECOND_DELAY = 1000;
|
|
18
|
+
const FIVE_SECOND_DELAY = 5000;
|
|
16
19
|
|
|
17
20
|
enum Mode {
|
|
18
21
|
Opening = 'opening',
|
|
@@ -80,7 +83,7 @@ export const Popover: React.FC<PopoverProps> = ({
|
|
|
80
83
|
useEffect(() => {
|
|
81
84
|
if (mode !== Mode.Closing) return;
|
|
82
85
|
|
|
83
|
-
const closingTimeout = setTimeout(() => setMode(Mode.Closed),
|
|
86
|
+
const closingTimeout = setTimeout(() => setMode(Mode.Closed), THREE_QUARTER_SECOND_DELAY);
|
|
84
87
|
|
|
85
88
|
return () => clearTimeout(closingTimeout);
|
|
86
89
|
}, [mode]);
|
|
@@ -88,7 +91,7 @@ export const Popover: React.FC<PopoverProps> = ({
|
|
|
88
91
|
useEffect(() => {
|
|
89
92
|
if (mode !== Mode.AutoOpening) return;
|
|
90
93
|
|
|
91
|
-
const openingTimeout = setTimeout(() => setMode(Mode.AutoOpen),
|
|
94
|
+
const openingTimeout = setTimeout(() => setMode(Mode.AutoOpen), ONE_SECOND_DELAY);
|
|
92
95
|
|
|
93
96
|
return () => clearTimeout(openingTimeout);
|
|
94
97
|
}, [mode]);
|
|
@@ -96,7 +99,7 @@ export const Popover: React.FC<PopoverProps> = ({
|
|
|
96
99
|
useEffect(() => {
|
|
97
100
|
if (mode !== Mode.Opening) return;
|
|
98
101
|
|
|
99
|
-
const openingTimeout = setTimeout(() => setMode(Mode.Open),
|
|
102
|
+
const openingTimeout = setTimeout(() => setMode(Mode.Open), THREE_QUARTER_SECOND_DELAY);
|
|
100
103
|
|
|
101
104
|
return () => clearTimeout(openingTimeout);
|
|
102
105
|
}, [mode]);
|
|
@@ -104,7 +107,7 @@ export const Popover: React.FC<PopoverProps> = ({
|
|
|
104
107
|
useEffect(() => {
|
|
105
108
|
if (mode !== Mode.AutoOpen) return;
|
|
106
109
|
|
|
107
|
-
const stayingTimeout = setTimeout(handleClose,
|
|
110
|
+
const stayingTimeout = setTimeout(handleClose, FIVE_SECOND_DELAY);
|
|
108
111
|
|
|
109
112
|
return () => clearTimeout(stayingTimeout);
|
|
110
113
|
}, [handleClose, mode, smoothTransitioning]);
|
|
@@ -125,7 +128,7 @@ export const Popover: React.FC<PopoverProps> = ({
|
|
|
125
128
|
onMouseLeave={handleMouseLeave}
|
|
126
129
|
{...rest}
|
|
127
130
|
>
|
|
128
|
-
<span className=
|
|
131
|
+
<span className="Popover__trigger">
|
|
129
132
|
{isOpen && (
|
|
130
133
|
<div
|
|
131
134
|
className={classNames('Popover__box', {
|
|
@@ -134,11 +137,11 @@ export const Popover: React.FC<PopoverProps> = ({
|
|
|
134
137
|
})}
|
|
135
138
|
>
|
|
136
139
|
<div
|
|
137
|
-
className=
|
|
140
|
+
className="Popover__caret"
|
|
138
141
|
style={{ zIndex: zIndex != null ? zIndex + 1 : undefined }}
|
|
139
142
|
/>
|
|
140
143
|
|
|
141
|
-
<div className=
|
|
144
|
+
<div className="Popover__content" style={{ zIndex }}>
|
|
142
145
|
{content}
|
|
143
146
|
</div>
|
|
144
147
|
</div>
|
|
@@ -9,22 +9,22 @@ export const Default = () => (
|
|
|
9
9
|
states={[
|
|
10
10
|
{ children: <span>Center</span> },
|
|
11
11
|
{
|
|
12
|
-
children: [<span key=
|
|
12
|
+
children: [<span key="left">Left</span>, <span key="right">Right</span>]
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
children: [
|
|
16
|
-
<span key=
|
|
17
|
-
<span key=
|
|
18
|
-
<span key=
|
|
16
|
+
<span key="left">Left</span>,
|
|
17
|
+
<span key="center">Center</span>,
|
|
18
|
+
<span key="right">Right</span>
|
|
19
19
|
]
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
children: [
|
|
23
|
-
<span key=
|
|
24
|
-
<span key=
|
|
25
|
-
Order by Dec. 12 for delivery by Dec. 25 — <a href=
|
|
23
|
+
<span key="left">Currency: USA (USD $)</span>,
|
|
24
|
+
<span key="center" className="FOO">
|
|
25
|
+
Order by Dec. 12 for delivery by Dec. 25 — <a href="#">LEARN MORE</a>
|
|
26
26
|
</span>,
|
|
27
|
-
<span key=
|
|
27
|
+
<span key="right">Hi, Friend</span>
|
|
28
28
|
]
|
|
29
29
|
}
|
|
30
30
|
]}
|
|
@@ -38,7 +38,7 @@ export const Mobile = () => (
|
|
|
38
38
|
<PromoBanner>
|
|
39
39
|
<span>Currency: USA (USD $)</span>
|
|
40
40
|
<span>
|
|
41
|
-
Order by Dec. 12 for delivery by Dec. 25 — <a href=
|
|
41
|
+
Order by Dec. 12 for delivery by Dec. 25 — <a href="#">LEARN MORE</a>
|
|
42
42
|
</span>
|
|
43
43
|
<span>Hi, Friend</span>
|
|
44
44
|
</PromoBanner>
|
|
@@ -11,7 +11,7 @@ export const PromoBanner: React.FC<PromoBannerProps> = ({ className, children, .
|
|
|
11
11
|
const length = Children.count(children);
|
|
12
12
|
return (
|
|
13
13
|
<div className={classNames('PromoBanner', className)} {...rest}>
|
|
14
|
-
<Constrain className=
|
|
14
|
+
<Constrain className="PromoBanner__constrain">
|
|
15
15
|
{Children.map(children, (child: JSX.Element, index) => {
|
|
16
16
|
const isCenter = length % 2 !== 0 && Math.floor(length / 2) === index;
|
|
17
17
|
return cloneElement(child, {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { States } from 'storybook-states';
|
|
3
3
|
import { Text } from '../Text';
|
|
4
|
-
import { RadioButton, RadioButtonProps } from './RadioButton';
|
|
5
4
|
import { Stack } from '../Stack';
|
|
5
|
+
import { RadioButton, RadioButtonProps } from './RadioButton';
|
|
6
6
|
|
|
7
7
|
export default { title: 'Components/RadioButton' };
|
|
8
8
|
|
|
@@ -29,7 +29,7 @@ export const Select = () => {
|
|
|
29
29
|
return (
|
|
30
30
|
<States>
|
|
31
31
|
<Stack space={3}>
|
|
32
|
-
<Text color=
|
|
32
|
+
<Text color="burnt-orange">{selected}</Text>
|
|
33
33
|
|
|
34
34
|
{EXAMPLES.map(value => (
|
|
35
35
|
<RadioButton
|
|
@@ -36,16 +36,16 @@ export const RadioButton: React.FC<RadioButtonProps> = ({
|
|
|
36
36
|
<input
|
|
37
37
|
ref={inputRef}
|
|
38
38
|
tabIndex={-1}
|
|
39
|
-
className=
|
|
40
|
-
type=
|
|
41
|
-
role=
|
|
39
|
+
className="RadioButton__input"
|
|
40
|
+
type="radio"
|
|
41
|
+
role="radio"
|
|
42
42
|
checked={checked}
|
|
43
43
|
value={value}
|
|
44
44
|
disabled={disabled}
|
|
45
45
|
{...rest}
|
|
46
46
|
/>
|
|
47
47
|
{children && (
|
|
48
|
-
<span className=
|
|
48
|
+
<span className="RadioButton__label" tabIndex={-1}>
|
|
49
49
|
{children}
|
|
50
50
|
</span>
|
|
51
51
|
)}
|
|
@@ -9,7 +9,7 @@ export default { title: 'Components/SearchInput' };
|
|
|
9
9
|
export const Default = () => (
|
|
10
10
|
<States<SearchInputProps> states={[{}, { value: 'prada' }, { autoFocus: true }]}>
|
|
11
11
|
<SearchInput
|
|
12
|
-
placeholder=
|
|
12
|
+
placeholder="Search"
|
|
13
13
|
onChange={action('onChange')}
|
|
14
14
|
onChangeValue={action('onChangeValue')}
|
|
15
15
|
/>
|
|
@@ -34,7 +34,7 @@ export const DropdownExample = () => {
|
|
|
34
34
|
|
|
35
35
|
return (
|
|
36
36
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
37
|
-
<SearchInput placeholder=
|
|
37
|
+
<SearchInput placeholder="Search" onChangeValue={setQuery} value={query} autoFocus />
|
|
38
38
|
|
|
39
39
|
{query && (
|
|
40
40
|
<Text style={{ border: '1px solid gray', marginTop: '-1px', padding: '1rem' }}>
|
|
@@ -6,7 +6,7 @@ import { SearchInput } from './SearchInput';
|
|
|
6
6
|
|
|
7
7
|
describe('SearchInput', () => {
|
|
8
8
|
it('clears the input when the button is clicked', async () => {
|
|
9
|
-
render(<SearchInput value=
|
|
9
|
+
render(<SearchInput value="Hello" />);
|
|
10
10
|
|
|
11
11
|
expect(screen.getByRole('textbox')).toHaveValue('Hello');
|
|
12
12
|
|
|
@@ -50,12 +50,12 @@ export const SearchInput: React.FC<SearchInputProps> = ({
|
|
|
50
50
|
|
|
51
51
|
return (
|
|
52
52
|
<div className={classNames('SearchInput', className)}>
|
|
53
|
-
<div className=
|
|
53
|
+
<div className="SearchInput__icon">
|
|
54
54
|
<SearchIcon />
|
|
55
55
|
</div>
|
|
56
56
|
|
|
57
57
|
<TextInput
|
|
58
|
-
className=
|
|
58
|
+
className="SearchInput__input"
|
|
59
59
|
onChange={handleChange}
|
|
60
60
|
value={controlledValue}
|
|
61
61
|
ref={ref}
|
|
@@ -63,7 +63,7 @@ export const SearchInput: React.FC<SearchInputProps> = ({
|
|
|
63
63
|
/>
|
|
64
64
|
|
|
65
65
|
{controlledValue && (
|
|
66
|
-
<Clickable className=
|
|
66
|
+
<Clickable className="SearchInput__clear" onClick={handleClick} type="reset">
|
|
67
67
|
<ExitIcon />
|
|
68
68
|
</Clickable>
|
|
69
69
|
)}
|
|
@@ -4,11 +4,11 @@ import { uniq } from 'ramda';
|
|
|
4
4
|
import ChevronDownIcon from '@moda/icons/chevron-down-12';
|
|
5
5
|
import ChevronUpIcon from '@moda/icons/chevron-up-12';
|
|
6
6
|
import ExitIcon from '@moda/icons/exit-16';
|
|
7
|
-
import {
|
|
7
|
+
import { Clickable } from '../Clickable';
|
|
8
8
|
import { SelectOptions } from './SelectOptions';
|
|
9
9
|
import { useSelect } from './useSelect';
|
|
10
|
+
import { SelectableOption } from '.';
|
|
10
11
|
import './MultiSelect.scss';
|
|
11
|
-
import { Clickable } from '../Clickable';
|
|
12
12
|
|
|
13
13
|
type Props = Omit<React.HTMLAttributes<HTMLDivElement>, 'defaultValue' | 'value' | 'onChange'> & {
|
|
14
14
|
defaultValue?: string[] | undefined;
|
|
@@ -85,16 +85,16 @@ export const MultiSelect: React.FC<Props> = ({
|
|
|
85
85
|
ref={selectRef}
|
|
86
86
|
{...rest}
|
|
87
87
|
>
|
|
88
|
-
<Clickable className=
|
|
89
|
-
<div className=
|
|
88
|
+
<Clickable className="MultiSelect__search" disabled={disabled} onClick={handleToggle}>
|
|
89
|
+
<div className="MultiSelect__selected-items">
|
|
90
90
|
{values.map((value, index) => (
|
|
91
|
-
<span className=
|
|
92
|
-
<span className=
|
|
91
|
+
<span className="MultiSelect__selected-item" key={index}>
|
|
92
|
+
<span className="MultiSelect__selected-value">
|
|
93
93
|
{options.find(option => option.value === value)?.label || value}
|
|
94
94
|
</span>
|
|
95
95
|
<ExitIcon
|
|
96
96
|
style={{ width: '8px', height: '8px', position: 'relative' }}
|
|
97
|
-
className=
|
|
97
|
+
className="MultiSelect__exit"
|
|
98
98
|
onClick={() => handleRemove(value)}
|
|
99
99
|
/>
|
|
100
100
|
</span>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import { action } from '@storybook/addon-actions';
|
|
3
3
|
import { States } from 'storybook-states';
|
|
4
|
+
import { useCallback } from 'react';
|
|
5
|
+
import { Input } from '../Field';
|
|
4
6
|
import { Select, SelectProps } from './Select';
|
|
5
7
|
import { MultiSelect } from './MultiSelect';
|
|
6
|
-
import { Input } from '../Field';
|
|
7
|
-
import { useCallback } from 'react';
|
|
8
8
|
|
|
9
9
|
export default { title: 'Components/Select' };
|
|
10
10
|
|
|
@@ -38,7 +38,7 @@ export const Default = () => (
|
|
|
38
38
|
{ idRef: '7', label: undefined, placeholder: 'Placeholder' }
|
|
39
39
|
]}
|
|
40
40
|
>
|
|
41
|
-
<Select label=
|
|
41
|
+
<Select label="Sort by" options={OPTIONS} onChange={action('onChange')} />
|
|
42
42
|
</States>
|
|
43
43
|
);
|
|
44
44
|
|
|
@@ -47,15 +47,16 @@ export const ChangingValue = () => {
|
|
|
47
47
|
|
|
48
48
|
const values = OPTIONS.map(({ value }) => value);
|
|
49
49
|
const value = values[cursor % values.length];
|
|
50
|
+
const HALF_SECOND_INTERVAL = 500;
|
|
50
51
|
|
|
51
52
|
useEffect(() => {
|
|
52
53
|
const interval = setInterval(() => {
|
|
53
54
|
setCursor(prevCursor => prevCursor + 1);
|
|
54
|
-
},
|
|
55
|
+
}, HALF_SECOND_INTERVAL);
|
|
55
56
|
return () => clearInterval(interval);
|
|
56
57
|
}, []);
|
|
57
58
|
|
|
58
|
-
return <Select label=
|
|
59
|
+
return <Select label="Sort by" options={OPTIONS} onChange={action('onChange')} value={value} />;
|
|
59
60
|
};
|
|
60
61
|
|
|
61
62
|
const COUNTRIES = [
|
|
@@ -259,16 +260,16 @@ const COUNTRIES = [
|
|
|
259
260
|
|
|
260
261
|
export const Long = () => (
|
|
261
262
|
<Select
|
|
262
|
-
label=
|
|
263
|
+
label="Choose country"
|
|
263
264
|
options={COUNTRIES.map(country => ({ label: country, value: country }))}
|
|
264
265
|
/>
|
|
265
266
|
);
|
|
266
267
|
|
|
267
268
|
export const AutoFill = () => (
|
|
268
269
|
<>
|
|
269
|
-
<Input label=
|
|
270
|
-
<Input label=
|
|
271
|
-
<Input label=
|
|
270
|
+
<Input label="Address*" name="addressLine1" />
|
|
271
|
+
<Input label="City" name="city" />
|
|
272
|
+
<Input label="Choose country" name="country">
|
|
272
273
|
<Select
|
|
273
274
|
options={COUNTRIES.map(country => ({ label: country, value: country }))}
|
|
274
275
|
allowAutoFill
|
|
@@ -279,7 +280,7 @@ export const AutoFill = () => (
|
|
|
279
280
|
|
|
280
281
|
export const InsideInputComponent = () => (
|
|
281
282
|
<Input error>
|
|
282
|
-
<Select options={OPTIONS}
|
|
283
|
+
<Select options={OPTIONS} />
|
|
283
284
|
</Input>
|
|
284
285
|
);
|
|
285
286
|
|
|
@@ -312,7 +313,7 @@ export const MultiSelectWithDefaultsComponent = () => {
|
|
|
312
313
|
<MultiSelect
|
|
313
314
|
onChange={handleSetItems}
|
|
314
315
|
value={selectedItems}
|
|
315
|
-
placeholder=
|
|
316
|
+
placeholder="United States"
|
|
316
317
|
defaultValue={['Canada', 'Albania', 'Greece']}
|
|
317
318
|
options={COUNTRIES.map(country => ({ label: country, value: country }))}
|
|
318
319
|
/>
|
|
@@ -322,7 +323,7 @@ export const MultiSelectWithDefaultsComponent = () => {
|
|
|
322
323
|
export const MultiSelectUncontrolledComponent = () => {
|
|
323
324
|
return (
|
|
324
325
|
<MultiSelect
|
|
325
|
-
placeholder=
|
|
326
|
+
placeholder="United States"
|
|
326
327
|
defaultValue={['Canada', 'Albania', 'Greece']}
|
|
327
328
|
options={COUNTRIES.map(country => ({ label: country, value: country }))}
|
|
328
329
|
/>
|
|
@@ -332,8 +333,8 @@ export const MultiSelectUncontrolledComponent = () => {
|
|
|
332
333
|
export const MultiSelectUncontrolledSearchComponent = () => {
|
|
333
334
|
return (
|
|
334
335
|
<MultiSelect
|
|
335
|
-
searchable
|
|
336
|
-
placeholder=
|
|
336
|
+
searchable
|
|
337
|
+
placeholder="United States"
|
|
337
338
|
defaultValue={['Canada', 'Albania', 'Greece']}
|
|
338
339
|
options={COUNTRIES.map(country => ({ label: country, value: country }))}
|
|
339
340
|
/>
|
|
@@ -13,7 +13,7 @@ describe('Select', () => {
|
|
|
13
13
|
|
|
14
14
|
it('renders correctly', () => {
|
|
15
15
|
const { container } = render(
|
|
16
|
-
<Select idRef=
|
|
16
|
+
<Select idRef="1" name="name-1" label="Sort by" options={options} />
|
|
17
17
|
);
|
|
18
18
|
expect(container.innerHTML).toEqual(
|
|
19
19
|
'<div id="Select--1" class="Select"><input id="1" name="name-1" type="hidden" value=""><button id="Select__value--1" aria-haspopup="listbox" aria-expanded="false" aria-labelledby="Select__label--1 Select__value--1" type="button" title="Select" class="Clickable Select__value"><label id="Select__label--1">Sort by</label><span class="Select__icon"></span></button></div>'
|
|
@@ -22,7 +22,7 @@ describe('Select', () => {
|
|
|
22
22
|
|
|
23
23
|
it('expands when clicked', async () => {
|
|
24
24
|
const { container } = render(
|
|
25
|
-
<Select idRef=
|
|
25
|
+
<Select idRef="1" name="name-1" label="Sort by" options={options} />
|
|
26
26
|
);
|
|
27
27
|
const button = screen.getByRole('button', { name: /Sort by/ });
|
|
28
28
|
await userEvent.click(button);
|
|
@@ -36,9 +36,9 @@ describe('Select', () => {
|
|
|
36
36
|
<Select
|
|
37
37
|
defaultValue={undefined}
|
|
38
38
|
disabled
|
|
39
|
-
idRef=
|
|
40
|
-
name=
|
|
41
|
-
label=
|
|
39
|
+
idRef="1"
|
|
40
|
+
name="name-1"
|
|
41
|
+
label="Sort by"
|
|
42
42
|
options={options}
|
|
43
43
|
/>
|
|
44
44
|
);
|
|
@@ -54,9 +54,9 @@ describe('Select', () => {
|
|
|
54
54
|
render(
|
|
55
55
|
<Select
|
|
56
56
|
defaultValue={undefined}
|
|
57
|
-
idRef=
|
|
58
|
-
label=
|
|
59
|
-
name=
|
|
57
|
+
idRef="1"
|
|
58
|
+
label="Sort by"
|
|
59
|
+
name="name-1"
|
|
60
60
|
options={options}
|
|
61
61
|
onChange={onChange}
|
|
62
62
|
/>
|
|
@@ -73,9 +73,9 @@ describe('Select', () => {
|
|
|
73
73
|
render(
|
|
74
74
|
<Select
|
|
75
75
|
defaultValue={undefined}
|
|
76
|
-
idRef=
|
|
77
|
-
label=
|
|
78
|
-
name=
|
|
76
|
+
idRef="1"
|
|
77
|
+
label="Sort by"
|
|
78
|
+
name="name-1"
|
|
79
79
|
options={options}
|
|
80
80
|
onChange={onChange}
|
|
81
81
|
/>
|
|
@@ -89,7 +89,7 @@ describe('Select', () => {
|
|
|
89
89
|
|
|
90
90
|
it('renders correctly with ":" between label and value if value is not undefined', async () => {
|
|
91
91
|
const { container } = render(
|
|
92
|
-
<Select idRef=
|
|
92
|
+
<Select idRef="1" name="name-1" label="Sort by" options={options} />
|
|
93
93
|
);
|
|
94
94
|
const button = screen.getByRole('button', { name: /Sort by/ });
|
|
95
95
|
await userEvent.click(button);
|
|
@@ -101,7 +101,7 @@ describe('Select', () => {
|
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
it('renders correctly when there is no label', async () => {
|
|
104
|
-
const { container } = render(<Select idRef=
|
|
104
|
+
const { container } = render(<Select idRef="1" name="name-1" options={options} />);
|
|
105
105
|
const button = screen.getByRole('button', { name: 'Select' });
|
|
106
106
|
await userEvent.click(button);
|
|
107
107
|
const option = screen.getByLabelText('Bar');
|
|
@@ -29,6 +29,7 @@ export type SelectProps = Omit<
|
|
|
29
29
|
searchable?: boolean;
|
|
30
30
|
shiftIconLeftwards?: boolean;
|
|
31
31
|
value?: string | undefined;
|
|
32
|
+
dataTestId?: string;
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
export const Select: React.FC<SelectProps> = ({
|
|
@@ -48,6 +49,7 @@ export const Select: React.FC<SelectProps> = ({
|
|
|
48
49
|
searchable,
|
|
49
50
|
shiftIconLeftwards = false,
|
|
50
51
|
value,
|
|
52
|
+
dataTestId,
|
|
51
53
|
...rest
|
|
52
54
|
}) => {
|
|
53
55
|
const { state, dispatch, Mode, selectRef } = useSelect({ value, defaultValue });
|
|
@@ -103,19 +105,20 @@ export const Select: React.FC<SelectProps> = ({
|
|
|
103
105
|
{...rest}
|
|
104
106
|
>
|
|
105
107
|
{!allowAutoFill && name && (
|
|
106
|
-
<input id={idRef} name={name} type=
|
|
108
|
+
<input id={idRef} name={name} type="hidden" value={state.value} />
|
|
107
109
|
)}
|
|
108
110
|
|
|
109
111
|
<Clickable
|
|
110
112
|
id={`Select__value--${idRef}`}
|
|
111
|
-
className=
|
|
113
|
+
className="Select__value"
|
|
112
114
|
disabled={disabled}
|
|
113
115
|
onClick={handleToggle}
|
|
114
|
-
|
|
116
|
+
data-test-id={dataTestId}
|
|
117
|
+
aria-haspopup="listbox"
|
|
115
118
|
aria-expanded={state.mode === Mode.Open}
|
|
116
119
|
aria-labelledby={`Select__label--${idRef} Select__value--${idRef}`}
|
|
117
|
-
type=
|
|
118
|
-
title=
|
|
120
|
+
type="button"
|
|
121
|
+
title="Select"
|
|
119
122
|
>
|
|
120
123
|
<SelectLabel
|
|
121
124
|
idRef={idRef}
|
|
@@ -139,6 +142,7 @@ export const Select: React.FC<SelectProps> = ({
|
|
|
139
142
|
'Select__options--up': dropDirection === 'up',
|
|
140
143
|
'Select__options--down': dropDirection === 'down'
|
|
141
144
|
})}
|
|
145
|
+
dataTestId={dataTestId}
|
|
142
146
|
searchable={searchable}
|
|
143
147
|
options={options}
|
|
144
148
|
onSelect={handleSelect}
|
|
@@ -150,11 +154,11 @@ export const Select: React.FC<SelectProps> = ({
|
|
|
150
154
|
|
|
151
155
|
{allowAutoFill && (
|
|
152
156
|
<input
|
|
153
|
-
className=
|
|
157
|
+
className="Select__hidden-field"
|
|
154
158
|
name={name}
|
|
155
159
|
onChange={({ target }) => handleAutofill(target.value)}
|
|
156
160
|
value={value}
|
|
157
|
-
aria-hidden=
|
|
161
|
+
aria-hidden="true"
|
|
158
162
|
tabIndex={-1}
|
|
159
163
|
/>
|
|
160
164
|
)}
|
|
@@ -9,6 +9,7 @@ export type SelectOptionProps = Omit<React.HTMLAttributes<HTMLLIElement>, 'onCli
|
|
|
9
9
|
selected: boolean;
|
|
10
10
|
option: SelectableOption;
|
|
11
11
|
onClick: (option: SelectableOption) => void;
|
|
12
|
+
dataTestId?: string;
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
export const SelectOption: React.FC<SelectOptionProps> = ({
|
|
@@ -16,7 +17,8 @@ export const SelectOption: React.FC<SelectOptionProps> = ({
|
|
|
16
17
|
selected,
|
|
17
18
|
option,
|
|
18
19
|
onClick,
|
|
19
|
-
className
|
|
20
|
+
className,
|
|
21
|
+
dataTestId
|
|
20
22
|
}) => {
|
|
21
23
|
const ref = useRef<HTMLLIElement>(null);
|
|
22
24
|
|
|
@@ -43,9 +45,10 @@ export const SelectOption: React.FC<SelectOptionProps> = ({
|
|
|
43
45
|
'SelectOption--disabled': option.disabled
|
|
44
46
|
})}
|
|
45
47
|
onClick={handleClick}
|
|
48
|
+
data-test-id={dataTestId ? `${dataTestId}--${option.label.replace(' ', '-')}` : undefined}
|
|
46
49
|
aria-label={option.label}
|
|
47
50
|
aria-selected={selected}
|
|
48
|
-
role=
|
|
51
|
+
role="option"
|
|
49
52
|
>
|
|
50
53
|
{option.label}
|
|
51
54
|
</li>
|
|
@@ -19,6 +19,7 @@ export type SelectOptionsProps = Omit<
|
|
|
19
19
|
selectedOption?: SelectableOption | undefined;
|
|
20
20
|
onFocus?(option: SelectableOption): void;
|
|
21
21
|
onSelect(option: SelectableOption): void;
|
|
22
|
+
dataTestId?: string;
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
export const SelectOptions: React.FC<SelectOptionsProps> = ({
|
|
@@ -30,6 +31,7 @@ export const SelectOptions: React.FC<SelectOptionsProps> = ({
|
|
|
30
31
|
onSelect,
|
|
31
32
|
onFocus,
|
|
32
33
|
className,
|
|
34
|
+
dataTestId,
|
|
33
35
|
...rest
|
|
34
36
|
}) => {
|
|
35
37
|
const [searchPhrase, setSearchPhrase] = useState('');
|
|
@@ -61,17 +63,17 @@ export const SelectOptions: React.FC<SelectOptionsProps> = ({
|
|
|
61
63
|
<div className={classNames('SelectOptions', className)}>
|
|
62
64
|
{searchable && (
|
|
63
65
|
<SearchInput
|
|
64
|
-
className=
|
|
65
|
-
placeholder=
|
|
66
|
+
className="SelectOptions__search"
|
|
67
|
+
placeholder="Search"
|
|
66
68
|
value={searchPhrase}
|
|
67
69
|
onChange={setSearchPhrase}
|
|
68
70
|
onClear={() => setSearchPhrase('')}
|
|
69
71
|
/>
|
|
70
72
|
)}
|
|
71
73
|
<ul
|
|
72
|
-
className=
|
|
74
|
+
className="SelectOptions__list"
|
|
73
75
|
tabIndex={-1}
|
|
74
|
-
role=
|
|
76
|
+
role="listbox"
|
|
75
77
|
ref={ref}
|
|
76
78
|
aria-labelledby={`Select__label--${idRef}`}
|
|
77
79
|
aria-activedescendant={`SelectOption--${activeOption?.value}-${idRef}`}
|
|
@@ -82,6 +84,7 @@ export const SelectOptions: React.FC<SelectOptionsProps> = ({
|
|
|
82
84
|
id={`SelectOption--${option.value}-${idRef}`}
|
|
83
85
|
key={option.value}
|
|
84
86
|
option={option}
|
|
87
|
+
dataTestId={dataTestId}
|
|
85
88
|
active={activeOption?.value === option.value}
|
|
86
89
|
selected={selectedOption?.value === option.value}
|
|
87
90
|
onClick={onSelect}
|
|
@@ -91,7 +94,7 @@ export const SelectOptions: React.FC<SelectOptionsProps> = ({
|
|
|
91
94
|
))}
|
|
92
95
|
</ul>
|
|
93
96
|
{options.length === 0 && (
|
|
94
|
-
<Text className=
|
|
97
|
+
<Text className="SelectOptions__none" color="cement">
|
|
95
98
|
Nothing found
|
|
96
99
|
</Text>
|
|
97
100
|
)}
|