@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.
Files changed (120) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/index.cjs.js +349 -299
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/index.esm.js +348 -298
  5. package/dist/index.esm.js.map +1 -1
  6. package/dist/src/components/Expandable/Expandable.d.ts +1 -0
  7. package/dist/src/components/Select/Select.d.ts +1 -0
  8. package/dist/src/components/Select/SelectOption.d.ts +1 -0
  9. package/dist/src/components/Select/SelectOptions.d.ts +1 -0
  10. package/dist/styles.css +1 -1
  11. package/package.json +18 -8
  12. package/src/components/AspectRatioBox/AspectRatioBox.tsx +4 -4
  13. package/src/components/AspectRatioBox/index.ts +1 -1
  14. package/src/components/Badge/Badge.stories.tsx +5 -5
  15. package/src/components/Badge/Badge.test.tsx +1 -1
  16. package/src/components/Breadcrumbs/Breadcrumb/Breadcrumb.tsx +6 -6
  17. package/src/components/Breadcrumbs/Breadcrumbs.stories.tsx +6 -6
  18. package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +2 -2
  19. package/src/components/Breadcrumbs/Breadcrumbs.tsx +1 -1
  20. package/src/components/Breakpoint/Breakpoint.stories.tsx +10 -9
  21. package/src/components/Breakpoint/Breakpoint.test.tsx +2 -2
  22. package/src/components/Breakpoint/generateMediaQuery.ts +5 -4
  23. package/src/components/Button/Button.stories.tsx +4 -4
  24. package/src/components/Checkbox/Checkbox.stories.tsx +5 -5
  25. package/src/components/Checkbox/Checkbox.test.tsx +3 -3
  26. package/src/components/Checkbox/Checkbox.tsx +4 -4
  27. package/src/components/Clickable/Clickable.stories.tsx +3 -3
  28. package/src/components/ColorSwatch/ColorSwatch.stories.tsx +11 -11
  29. package/src/components/ColorSwatch/ColorSwatch.test.tsx +1 -1
  30. package/src/components/ColorSwatch/ColorSwatch.tsx +1 -1
  31. package/src/components/Constrain/Constrain.stories.tsx +1 -1
  32. package/src/components/Constrain/ExceedConstrain.stories.tsx +2 -2
  33. package/src/components/ControlLink/ControlLink.stories.tsx +1 -1
  34. package/src/components/ControlLink/index.ts +1 -1
  35. package/src/components/CreditCardNumberInput/CreditCardNumberInput.stories.tsx +1 -1
  36. package/src/components/CreditCardNumberInput/CreditCardNumberInput.test.tsx +4 -4
  37. package/src/components/CreditCardNumberInput/CreditCardNumberInput.tsx +1 -0
  38. package/src/components/DefinitionList/DefinitionList.stories.tsx +3 -3
  39. package/src/components/DefinitionList/DefinitionList.test.tsx +1 -1
  40. package/src/components/Divider/Divider.stories.tsx +4 -4
  41. package/src/components/Divider/Divider.test.tsx +1 -1
  42. package/src/components/Divider/Divider.tsx +1 -1
  43. package/src/components/Divider/NoLineDivider.tsx +1 -1
  44. package/src/components/Divider/TwoLineDivider.tsx +1 -1
  45. package/src/components/Expandable/Expandable.stories.tsx +8 -8
  46. package/src/components/Expandable/Expandable.test.tsx +1 -1
  47. package/src/components/Expandable/Expandable.tsx +12 -5
  48. package/src/components/Field/Field.stories.tsx +4 -4
  49. package/src/components/Field/Field.test.tsx +7 -7
  50. package/src/components/Field/Field.tsx +5 -5
  51. package/src/components/Field/index.ts +1 -0
  52. package/src/components/Loading/Loading.test.tsx +1 -1
  53. package/src/components/Loading/Loading.tsx +2 -1
  54. package/src/components/Modal/Modal.test.tsx +2 -2
  55. package/src/components/ModalOverlay/ModalOverlay.test.tsx +2 -2
  56. package/src/components/ModalOverlay/ModalOverlay.tsx +2 -2
  57. package/src/components/Overlay/Overlay.stories.tsx +1 -1
  58. package/src/components/Paginator/Paginator.tsx +3 -3
  59. package/src/components/PasswordInput/PasswordInput.stories.tsx +1 -1
  60. package/src/components/PasswordInput/PasswordInput.tsx +6 -6
  61. package/src/components/Popover/Popover.stories.tsx +1 -1
  62. package/src/components/Popover/Popover.tsx +10 -7
  63. package/src/components/PromoBanner/PromoBanner.stories.tsx +9 -9
  64. package/src/components/PromoBanner/PromoBanner.tsx +1 -1
  65. package/src/components/RadioButton/RadioButton.stories.tsx +2 -2
  66. package/src/components/RadioButton/RadioButton.tsx +4 -4
  67. package/src/components/SearchInput/SearchInput.stories.tsx +2 -2
  68. package/src/components/SearchInput/SearchInput.test.tsx +1 -1
  69. package/src/components/SearchInput/SearchInput.tsx +3 -3
  70. package/src/components/Select/MultiSelect.tsx +7 -7
  71. package/src/components/Select/Select.stories.tsx +15 -14
  72. package/src/components/Select/Select.test.tsx +13 -13
  73. package/src/components/Select/Select.tsx +11 -7
  74. package/src/components/Select/SelectOption.tsx +5 -2
  75. package/src/components/Select/SelectOptions.tsx +8 -5
  76. package/src/components/SelectableButton/SelectableButton.stories.tsx +1 -1
  77. package/src/components/Shape/ArcWindow.tsx +10 -10
  78. package/src/components/Shape/Bradley.tsx +4 -4
  79. package/src/components/Shape/Brancusi.tsx +4 -4
  80. package/src/components/Shape/Circle.tsx +10 -10
  81. package/src/components/Shape/Diamond.tsx +10 -10
  82. package/src/components/Shape/Keyhole.tsx +4 -4
  83. package/src/components/Shape/Mirror.tsx +4 -4
  84. package/src/components/Shape/Oval.tsx +4 -4
  85. package/src/components/Shape/Triangle.tsx +7 -7
  86. package/src/components/Shape/Window.tsx +4 -4
  87. package/src/components/Stack/Stack.stories.tsx +3 -3
  88. package/src/components/Stack/Stack.test.tsx +1 -1
  89. package/src/components/Tabs/TabList.tsx +1 -1
  90. package/src/components/Tabs/TabPanel.tsx +1 -1
  91. package/src/components/Tabs/TabPanels.tsx +1 -1
  92. package/src/components/Tabs/Tabs.stories.tsx +2 -2
  93. package/src/components/Tabs/Tabs.test.tsx +2 -2
  94. package/src/components/Tabs/Tabs.tsx +7 -1
  95. package/src/components/Tag/Tag.tsx +6 -5
  96. package/src/components/Text/Text.stories.tsx +5 -5
  97. package/src/components/Text/Text.test.tsx +1 -1
  98. package/src/components/TextInput/TextInput.stories.tsx +1 -1
  99. package/src/components/TextInput/TextInput.test.tsx +1 -1
  100. package/src/components/Textarea/Textarea.stories.tsx +1 -1
  101. package/src/components/Textarea/Textarea.test.tsx +3 -3
  102. package/src/components/Toast/Toast.stories.tsx +1 -1
  103. package/src/components/Toast/Toast.test.tsx +1 -1
  104. package/src/components/Toast/Toast.tsx +6 -6
  105. package/src/components/VerticalDivider/VerticalDivider.stories.tsx +3 -3
  106. package/src/components/VerticalDivider/VerticalDivider.test.tsx +2 -2
  107. package/src/hooks/useKeyboardListNavigation.spec.ts +2 -1
  108. package/src/hooks/useKeyboardListNavigation.ts +2 -0
  109. package/src/hooks/useUpdateEffect.ts +1 -0
  110. package/src/index.ts +2 -2
  111. package/src/mixins/font-smoothing/font-smoothing.stories.tsx +8 -8
  112. package/src/mixins/overflow-scroll/overflow-scroll.stories.tsx +2 -2
  113. package/src/mixins/stack/stack.stories.tsx +5 -6
  114. package/src/mixins/thin-underline/thin-underline.stories.tsx +1 -1
  115. package/src/mixins/visually-hidden/visually-hidden.stories.tsx +1 -1
  116. package/src/utilities/States/State.tsx +3 -2
  117. package/src/utilities/States/States.tsx +4 -3
  118. package/src/utilities/States/StatesProps.tsx +5 -2
  119. package/src/utilities/States/isRenderProps.ts +1 -0
  120. 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='Pagination'>
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='Paginator__previous-chevron' /> Previous
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='Paginator__next-chevron' />
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='signInPassword' placeholder='Password' />
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='PasswordInput'>
18
+ <div className="PasswordInput">
19
19
  <Input
20
- className='PasswordInput__editor'
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='PasswordInput__password-action'
27
+ className="PasswordInput__password-action"
28
28
  onClick={togglePasswordVisiblity}
29
- type='button'
29
+ type="button"
30
30
  >
31
31
  {passwordShown ? (
32
- <Text treatment='eyebrow'>HIDE</Text>
32
+ <Text treatment="eyebrow">HIDE</Text>
33
33
  ) : (
34
- <Text treatment='eyebrow'>SHOW</Text>
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='https://www.modaoperandi.com/women'>Hover on Link</ControlLink>
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), 750);
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), 1000);
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), 750);
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, 5000);
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='Popover__trigger'>
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='Popover__caret'
140
+ className="Popover__caret"
138
141
  style={{ zIndex: zIndex != null ? zIndex + 1 : undefined }}
139
142
  />
140
143
 
141
- <div className='Popover__content' style={{ zIndex }}>
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='left'>Left</span>, <span key='right'>Right</span>]
12
+ children: [<span key="left">Left</span>, <span key="right">Right</span>]
13
13
  },
14
14
  {
15
15
  children: [
16
- <span key='left'>Left</span>,
17
- <span key='center'>Center</span>,
18
- <span key='right'>Right</span>
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='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>
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='right'>Hi, Friend</span>
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='#'>LEARN MORE</a>
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='PromoBanner__constrain'>
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='burnt-orange'>{selected}</Text>
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='RadioButton__input'
40
- type='radio'
41
- role='radio'
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='RadioButton__label' tabIndex={-1}>
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='Search'
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='Search' onChangeValue={setQuery} value={query} autoFocus />
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='Hello' />);
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='SearchInput__icon'>
53
+ <div className="SearchInput__icon">
54
54
  <SearchIcon />
55
55
  </div>
56
56
 
57
57
  <TextInput
58
- className='SearchInput__input'
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='SearchInput__clear' onClick={handleClick} type='reset'>
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 { SelectableOption } from '.';
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='MultiSelect__search' disabled={disabled} onClick={handleToggle}>
89
- <div className='MultiSelect__selected-items'>
88
+ <Clickable className="MultiSelect__search" disabled={disabled} onClick={handleToggle}>
89
+ <div className="MultiSelect__selected-items">
90
90
  {values.map((value, index) => (
91
- <span className='MultiSelect__selected-item' key={index}>
92
- <span className='MultiSelect__selected-value'>
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='MultiSelect__exit'
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='Sort by' options={OPTIONS} onChange={action('onChange')} />
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
- }, 500);
55
+ }, HALF_SECOND_INTERVAL);
55
56
  return () => clearInterval(interval);
56
57
  }, []);
57
58
 
58
- return <Select label='Sort by' options={OPTIONS} onChange={action('onChange')} value={value} />;
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='Choose country'
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='Address*' name='addressLine1' />
270
- <Input label='City' name='city' />
271
- <Input label='Choose country' name='country'>
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}></Select>
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='United States'
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='United States'
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={true}
336
- placeholder='United States'
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='1' name='name-1' label='Sort by' options={options} />
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='1' name='name-1' label='Sort by' options={options} />
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='1'
40
- name='name-1'
41
- label='Sort by'
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='1'
58
- label='Sort by'
59
- name='name-1'
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='1'
77
- label='Sort by'
78
- name='name-1'
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='1' name='name-1' label='Sort by' options={options} />
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='1' name='name-1' options={options} />);
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='hidden' value={state.value} />
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='Select__value'
113
+ className="Select__value"
112
114
  disabled={disabled}
113
115
  onClick={handleToggle}
114
- aria-haspopup='listbox'
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='button'
118
- title='Select'
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='Select__hidden-field'
157
+ className="Select__hidden-field"
154
158
  name={name}
155
159
  onChange={({ target }) => handleAutofill(target.value)}
156
160
  value={value}
157
- aria-hidden='true'
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='option'
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='SelectOptions__search'
65
- placeholder='Search'
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='SelectOptions__list'
74
+ className="SelectOptions__list"
73
75
  tabIndex={-1}
74
- role='listbox'
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='SelectOptions__none' color='cement'>
97
+ <Text className="SelectOptions__none" color="cement">
95
98
  Nothing found
96
99
  </Text>
97
100
  )}