agroptima-design-system 0.27.15 → 0.27.16
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/package.json +1 -1
- package/src/atoms/Multiselect.tsx +87 -30
- package/src/atoms/Popover/Popover.tsx +2 -2
- package/src/atoms/Select.scss +15 -6
- package/src/atoms/Select.tsx +58 -12
- package/src/hooks/useSearch.ts +14 -0
- package/src/stories/Changelog.mdx +4 -0
- package/src/stories/Multiselect.stories.ts +33 -0
- package/src/stories/Select.stories.ts +47 -0
- package/src/utils/normalizeSearch.ts +4 -0
- package/tests/Multiselect.spec.tsx +45 -0
- package/tests/Select.spec.tsx +45 -0
- package/tests/utils/normalizeSearch.spec.tsx +7 -0
- /package/src/{utils → hooks}/useOpen.ts +0 -0
- /package/src/{utils → hooks}/useOutsideClick.ts +0 -0
package/package.json
CHANGED
|
@@ -4,8 +4,10 @@ import { Icon } from './Icon'
|
|
|
4
4
|
import { IconButton } from './Button'
|
|
5
5
|
import { classNames } from '../utils/classNames'
|
|
6
6
|
import { buildHelpText } from '../utils/buildHelpText'
|
|
7
|
-
import { useOutsideClick } from '../
|
|
8
|
-
import { useOpen } from '../
|
|
7
|
+
import { useOutsideClick } from '../hooks/useOutsideClick'
|
|
8
|
+
import { useOpen } from '../hooks/useOpen'
|
|
9
|
+
import { Input } from './Input'
|
|
10
|
+
import { useSearch } from '../hooks/useSearch'
|
|
9
11
|
|
|
10
12
|
export type Variant = 'primary'
|
|
11
13
|
export type Option = { id: string; label: string }
|
|
@@ -27,6 +29,8 @@ export interface MultiselectProps extends InputPropsWithoutOnChange {
|
|
|
27
29
|
hideLabel?: boolean
|
|
28
30
|
defaultValue?: string[]
|
|
29
31
|
onChange?: (value: string[]) => void
|
|
32
|
+
isSearchable?: boolean
|
|
33
|
+
searchLabel?: string
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export function Multiselect({
|
|
@@ -44,6 +48,8 @@ export function Multiselect({
|
|
|
44
48
|
selectedLabel = 'items selected',
|
|
45
49
|
hideLabel = false,
|
|
46
50
|
defaultValue = [],
|
|
51
|
+
isSearchable = false,
|
|
52
|
+
searchLabel = 'Search',
|
|
47
53
|
...props
|
|
48
54
|
}: MultiselectProps): React.JSX.Element {
|
|
49
55
|
const helpTexts = buildHelpText(helpText, errors)
|
|
@@ -108,18 +114,15 @@ export function Multiselect({
|
|
|
108
114
|
visible={hasSelectedOptions}
|
|
109
115
|
/>
|
|
110
116
|
</div>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
))}
|
|
121
|
-
</ul>
|
|
122
|
-
)}
|
|
117
|
+
|
|
118
|
+
<OptionList
|
|
119
|
+
isOpen={isOpen}
|
|
120
|
+
options={options}
|
|
121
|
+
selectedOptions={selectedOptions}
|
|
122
|
+
onSelect={selectOption}
|
|
123
|
+
isSearchable={isSearchable}
|
|
124
|
+
searchLabel={searchLabel}
|
|
125
|
+
/>
|
|
123
126
|
</div>
|
|
124
127
|
{helpTexts.map((helpText) => (
|
|
125
128
|
<span key={`${name}-${helpText}`} className="select-help-text">
|
|
@@ -136,26 +139,80 @@ export function Multiselect({
|
|
|
136
139
|
)
|
|
137
140
|
}
|
|
138
141
|
|
|
139
|
-
interface
|
|
140
|
-
|
|
142
|
+
interface OptionListProps {
|
|
143
|
+
isSearchable: boolean
|
|
144
|
+
searchLabel: string
|
|
145
|
+
options: Option[]
|
|
141
146
|
selectedOptions: string[]
|
|
142
147
|
onSelect: (id: string) => void
|
|
148
|
+
isOpen: boolean
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function OptionList({
|
|
152
|
+
options,
|
|
153
|
+
selectedOptions,
|
|
154
|
+
onSelect,
|
|
155
|
+
isSearchable,
|
|
156
|
+
searchLabel,
|
|
157
|
+
isOpen,
|
|
158
|
+
}: OptionListProps) {
|
|
159
|
+
const { findItems, search } = useSearch(options, 'label')
|
|
160
|
+
if (!isOpen) return null
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div className="select-options">
|
|
164
|
+
{isSearchable && (
|
|
165
|
+
<Input
|
|
166
|
+
label={searchLabel}
|
|
167
|
+
hideLabel
|
|
168
|
+
onChange={(e) => search(e.target.value)}
|
|
169
|
+
placeholder={searchLabel}
|
|
170
|
+
icon="Search"
|
|
171
|
+
className="search"
|
|
172
|
+
/>
|
|
173
|
+
)}
|
|
174
|
+
<ul role="listbox">
|
|
175
|
+
<Option
|
|
176
|
+
options={findItems}
|
|
177
|
+
selectedOptions={selectedOptions}
|
|
178
|
+
onSelect={onSelect}
|
|
179
|
+
/>
|
|
180
|
+
</ul>
|
|
181
|
+
</div>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface OptionProps {
|
|
186
|
+
options: Option[]
|
|
187
|
+
onSelect: (id: string) => void
|
|
188
|
+
selectedOptions: string[]
|
|
143
189
|
}
|
|
144
190
|
|
|
145
|
-
function Option({
|
|
146
|
-
|
|
147
|
-
|
|
191
|
+
function Option({ options, onSelect, selectedOptions }: OptionProps) {
|
|
192
|
+
function isSelected(id: string): boolean {
|
|
193
|
+
return selectedOptions.includes(id)
|
|
194
|
+
}
|
|
195
|
+
function getIcon(id: string) {
|
|
196
|
+
return isSelected(id) ? 'CheckboxActive' : 'CheckboxInactive'
|
|
197
|
+
}
|
|
148
198
|
return (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
199
|
+
<>
|
|
200
|
+
{options.map((option) => {
|
|
201
|
+
return (
|
|
202
|
+
<li
|
|
203
|
+
key={option.id}
|
|
204
|
+
role="option"
|
|
205
|
+
className="option"
|
|
206
|
+
tabIndex={0}
|
|
207
|
+
aria-selected={isSelected(option.id)}
|
|
208
|
+
data-option={option}
|
|
209
|
+
onClick={() => onSelect(option.id)}
|
|
210
|
+
>
|
|
211
|
+
<Icon name={getIcon(option.id)} />
|
|
212
|
+
{option.label}
|
|
213
|
+
</li>
|
|
214
|
+
)
|
|
215
|
+
})}
|
|
216
|
+
</>
|
|
160
217
|
)
|
|
161
218
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
import { useOutsideClick } from '../../
|
|
2
|
+
import { useOutsideClick } from '../../hooks/useOutsideClick'
|
|
3
3
|
import { classNames } from '../../utils/classNames'
|
|
4
|
-
import { useOpen } from '../../
|
|
4
|
+
import { useOpen } from '../../hooks/useOpen'
|
|
5
5
|
import { useRef } from 'react'
|
|
6
6
|
import './Popover.scss'
|
|
7
7
|
|
package/src/atoms/Select.scss
CHANGED
|
@@ -74,10 +74,6 @@
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
.select-options {
|
|
77
|
-
max-height: 256px;
|
|
78
|
-
overflow-y: auto;
|
|
79
|
-
overflow-anchor: none;
|
|
80
|
-
z-index: depth.$z-dropdown-options;
|
|
81
77
|
border-radius: config.$corner-radius-xxs;
|
|
82
78
|
background: color_alias.$neutral-white;
|
|
83
79
|
box-shadow:
|
|
@@ -132,8 +128,11 @@
|
|
|
132
128
|
height: config.$icon-size-3x;
|
|
133
129
|
}
|
|
134
130
|
}
|
|
135
|
-
|
|
131
|
+
|
|
136
132
|
.select-options {
|
|
133
|
+
max-height: 256px;
|
|
134
|
+
overflow-y: auto;
|
|
135
|
+
overflow-anchor: none;
|
|
137
136
|
z-index: depth.$z-dropdown-options;
|
|
138
137
|
margin: 0;
|
|
139
138
|
padding: config.$space-1x 0rem;
|
|
@@ -141,6 +140,16 @@
|
|
|
141
140
|
position: absolute;
|
|
142
141
|
width: 100%;
|
|
143
142
|
|
|
143
|
+
.search {
|
|
144
|
+
margin: config.$space-2x config.$space-3x;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
ul {
|
|
148
|
+
width: 100%;
|
|
149
|
+
margin: 0;
|
|
150
|
+
padding: 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
144
153
|
.option {
|
|
145
154
|
display: flex;
|
|
146
155
|
align-items: center;
|
|
@@ -154,5 +163,5 @@
|
|
|
154
163
|
margin-right: config.$space-1x;
|
|
155
164
|
}
|
|
156
165
|
}
|
|
157
|
-
}
|
|
166
|
+
}
|
|
158
167
|
}
|
package/src/atoms/Select.tsx
CHANGED
|
@@ -4,9 +4,11 @@ import { Icon } from './Icon'
|
|
|
4
4
|
import { IconButton } from './Button'
|
|
5
5
|
import { classNames } from '../utils/classNames'
|
|
6
6
|
import { buildHelpText } from '../utils/buildHelpText'
|
|
7
|
-
import { useOutsideClick } from '../
|
|
8
|
-
import { useOpen } from '../
|
|
7
|
+
import { useOutsideClick } from '../hooks/useOutsideClick'
|
|
8
|
+
import { useOpen } from '../hooks/useOpen'
|
|
9
9
|
import './Select.scss'
|
|
10
|
+
import { Input } from './Input'
|
|
11
|
+
import { useSearch } from '../hooks/useSearch'
|
|
10
12
|
|
|
11
13
|
export type Variant = 'primary'
|
|
12
14
|
export type Option = { id: string; label: string }
|
|
@@ -27,6 +29,8 @@ export interface SelectProps extends InputPropsWithoutOnChange {
|
|
|
27
29
|
defaultValue?: string
|
|
28
30
|
onChange?: (value: string) => void
|
|
29
31
|
required?: boolean
|
|
32
|
+
isSearchable: boolean
|
|
33
|
+
searchLabel?: string
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
const EMPTY_OPTION = { id: '', label: '' }
|
|
@@ -46,6 +50,8 @@ export function Select({
|
|
|
46
50
|
onChange = () => {},
|
|
47
51
|
defaultValue,
|
|
48
52
|
required = false,
|
|
53
|
+
isSearchable = false,
|
|
54
|
+
searchLabel = 'Search',
|
|
49
55
|
...props
|
|
50
56
|
}: SelectProps): React.JSX.Element {
|
|
51
57
|
const helpTexts = buildHelpText(helpText, errors)
|
|
@@ -110,14 +116,16 @@ export function Select({
|
|
|
110
116
|
visible={!isEmpty}
|
|
111
117
|
/>
|
|
112
118
|
</div>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
|
|
120
|
+
<OptionList
|
|
121
|
+
isOpen={isOpen}
|
|
122
|
+
options={options}
|
|
123
|
+
selectedOption={selectedOption}
|
|
124
|
+
selectOption={selectOption}
|
|
125
|
+
onClick={close}
|
|
126
|
+
isSearchable={isSearchable}
|
|
127
|
+
searchLabel={searchLabel}
|
|
128
|
+
/>
|
|
121
129
|
</div>
|
|
122
130
|
{helpTexts.map((helpText) => (
|
|
123
131
|
<span key={`${name}-${helpText}`} className="select-help-text">
|
|
@@ -140,6 +148,9 @@ interface OptionListProps {
|
|
|
140
148
|
selectedOption: Option
|
|
141
149
|
selectOption: (option: Option) => void
|
|
142
150
|
onClick: () => void
|
|
151
|
+
isSearchable: boolean
|
|
152
|
+
searchLabel: string
|
|
153
|
+
isOpen: boolean
|
|
143
154
|
}
|
|
144
155
|
|
|
145
156
|
function OptionList({
|
|
@@ -147,9 +158,44 @@ function OptionList({
|
|
|
147
158
|
selectedOption,
|
|
148
159
|
selectOption,
|
|
149
160
|
onClick,
|
|
161
|
+
isSearchable,
|
|
162
|
+
searchLabel,
|
|
163
|
+
isOpen,
|
|
150
164
|
}: OptionListProps) {
|
|
165
|
+
const { findItems, search } = useSearch(options, 'label')
|
|
166
|
+
if (!isOpen) return null
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<div className="select-options">
|
|
170
|
+
{isSearchable && (
|
|
171
|
+
<Input
|
|
172
|
+
label={searchLabel}
|
|
173
|
+
hideLabel
|
|
174
|
+
onChange={(e) => search(e.target.value)}
|
|
175
|
+
placeholder={searchLabel}
|
|
176
|
+
icon="Search"
|
|
177
|
+
className="search"
|
|
178
|
+
/>
|
|
179
|
+
)}
|
|
180
|
+
<ul role="listbox" onClick={onClick}>
|
|
181
|
+
<Option
|
|
182
|
+
options={findItems}
|
|
183
|
+
selectedOption={selectedOption}
|
|
184
|
+
selectOption={selectOption}
|
|
185
|
+
/>
|
|
186
|
+
</ul>
|
|
187
|
+
</div>
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
interface OptionProps {
|
|
191
|
+
options: Option[]
|
|
192
|
+
selectedOption: Option
|
|
193
|
+
selectOption: (option: Option) => void
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function Option({ options, selectedOption, selectOption }: OptionProps) {
|
|
151
197
|
return (
|
|
152
|
-
|
|
198
|
+
<>
|
|
153
199
|
{options.map((option) => {
|
|
154
200
|
return (
|
|
155
201
|
<li
|
|
@@ -165,6 +211,6 @@ function OptionList({
|
|
|
165
211
|
</li>
|
|
166
212
|
)
|
|
167
213
|
})}
|
|
168
|
-
|
|
214
|
+
</>
|
|
169
215
|
)
|
|
170
216
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { normalizeSearch } from '../utils/normalizeSearch'
|
|
3
|
+
|
|
4
|
+
export function useSearch(items: any[], key: string) {
|
|
5
|
+
const [findItems, setFindItems] = useState<any[]>(items)
|
|
6
|
+
|
|
7
|
+
function search(term: string) {
|
|
8
|
+
const filteredList = items.filter((option) => {
|
|
9
|
+
return normalizeSearch(option[key]).includes(normalizeSearch(term))
|
|
10
|
+
})
|
|
11
|
+
setFindItems(filteredList)
|
|
12
|
+
}
|
|
13
|
+
return { findItems, search }
|
|
14
|
+
}
|
|
@@ -41,6 +41,12 @@ const meta = {
|
|
|
41
41
|
description:
|
|
42
42
|
'Optional array of errors. If passed, the errors are listed and invalid style is applied.',
|
|
43
43
|
},
|
|
44
|
+
isSearchable: {
|
|
45
|
+
description: 'Select component with search option',
|
|
46
|
+
},
|
|
47
|
+
searchLabel: {
|
|
48
|
+
description: 'Label for the search ',
|
|
49
|
+
},
|
|
44
50
|
},
|
|
45
51
|
}
|
|
46
52
|
|
|
@@ -65,6 +71,7 @@ export const Primary: Story = {
|
|
|
65
71
|
accessibilityLabel: 'Select your favourite videogames options',
|
|
66
72
|
selectedLabel: 'videogames selected',
|
|
67
73
|
placeholder: 'Select your favourite videogames...',
|
|
74
|
+
isSearchable: false,
|
|
68
75
|
options: [
|
|
69
76
|
{ id: '1', label: 'The Legend of Zelda: Ocarina of Time' },
|
|
70
77
|
{ id: '2', label: 'Spyro the Dragon' },
|
|
@@ -87,6 +94,7 @@ export const PrimaryWithSelectedOptions: Story = {
|
|
|
87
94
|
label: 'Videogames',
|
|
88
95
|
selectedLabel: 'videogames selected',
|
|
89
96
|
placeholder: 'Select your favourite videogames...',
|
|
97
|
+
isSearchable: false,
|
|
90
98
|
options: [
|
|
91
99
|
{ id: '1', label: 'The Legend of Zelda: Ocarina of Time' },
|
|
92
100
|
{ id: '2', label: 'Spyro the Dragon' },
|
|
@@ -111,6 +119,7 @@ export const PrimaryWithErrors: Story = {
|
|
|
111
119
|
accessibilityLabel: 'Select your favourite videogames options',
|
|
112
120
|
selectedLabel: 'videogames selected',
|
|
113
121
|
placeholder: 'Select your favourite videogames...',
|
|
122
|
+
isSearchable: false,
|
|
114
123
|
options: [
|
|
115
124
|
{ id: '1', label: 'The Legend of Zelda: Ocarina of Time' },
|
|
116
125
|
{ id: '2', label: 'Spyro the Dragon' },
|
|
@@ -123,3 +132,27 @@ export const PrimaryWithErrors: Story = {
|
|
|
123
132
|
},
|
|
124
133
|
parameters: figmaPrimaryDesign,
|
|
125
134
|
}
|
|
135
|
+
export const PrimaryWithSearch: Story = {
|
|
136
|
+
args: {
|
|
137
|
+
variant: 'primary',
|
|
138
|
+
disabled: false,
|
|
139
|
+
hideLabel: false,
|
|
140
|
+
helpText: 'This text can help you',
|
|
141
|
+
name: 'videogames',
|
|
142
|
+
label: 'Videogames',
|
|
143
|
+
accessibilityLabel: 'Select your favourite videogames options',
|
|
144
|
+
selectedLabel: 'videogames selected',
|
|
145
|
+
placeholder: 'Select your favourite videogames...',
|
|
146
|
+
options: [
|
|
147
|
+
{ id: '1', label: 'The Legend of Zelda: Ocarina of Time' },
|
|
148
|
+
{ id: '2', label: 'Spyro the Dragon' },
|
|
149
|
+
{ id: '3', label: 'Halo' },
|
|
150
|
+
{ id: '4', label: 'Tetris' },
|
|
151
|
+
{ id: '5', label: 'Super Mario Bros' },
|
|
152
|
+
{ id: '6', label: 'Red Dead Redemption' },
|
|
153
|
+
],
|
|
154
|
+
isSearchable: true,
|
|
155
|
+
searchLabel: 'Search',
|
|
156
|
+
},
|
|
157
|
+
parameters: figmaPrimaryDesign,
|
|
158
|
+
}
|
|
@@ -41,6 +41,12 @@ const meta = {
|
|
|
41
41
|
description:
|
|
42
42
|
'Optional array of errors. If passed, the errors are listed and invalid style is applied.',
|
|
43
43
|
},
|
|
44
|
+
isSearchable: {
|
|
45
|
+
description: 'Select component with search option',
|
|
46
|
+
},
|
|
47
|
+
searchLabel: {
|
|
48
|
+
description: 'Label for the search ',
|
|
49
|
+
},
|
|
44
50
|
},
|
|
45
51
|
}
|
|
46
52
|
|
|
@@ -63,6 +69,7 @@ export const Primary: Story = {
|
|
|
63
69
|
label: 'Videogames',
|
|
64
70
|
accessibilityLabel: 'Select your favourite gaming system options',
|
|
65
71
|
hideLabel: false,
|
|
72
|
+
isSearchable: false,
|
|
66
73
|
placeholder: 'Select your favourite gaming system...',
|
|
67
74
|
options: [
|
|
68
75
|
{ id: '1', label: 'Nintendo Switch' },
|
|
@@ -100,6 +107,7 @@ export const PrimaryWithSelectedOptions: Story = {
|
|
|
100
107
|
label: 'Videogames',
|
|
101
108
|
hideLabel: false,
|
|
102
109
|
placeholder: 'Select your favourite gaming system...',
|
|
110
|
+
isSearchable: false,
|
|
103
111
|
options: [
|
|
104
112
|
{ id: '1', label: 'Nintendo Switch' },
|
|
105
113
|
{ id: '2', label: 'PlayStation 5' },
|
|
@@ -121,6 +129,7 @@ export const PrimaryWithErrors: Story = {
|
|
|
121
129
|
accessibilityLabel: 'Select your favourite gaming system options',
|
|
122
130
|
hideLabel: false,
|
|
123
131
|
placeholder: 'Select your favourite gaming system...',
|
|
132
|
+
isSearchable: false,
|
|
124
133
|
options: [
|
|
125
134
|
{ id: '1', label: 'Nintendo Switch' },
|
|
126
135
|
{ id: '2', label: 'PlayStation 5' },
|
|
@@ -132,3 +141,41 @@ export const PrimaryWithErrors: Story = {
|
|
|
132
141
|
},
|
|
133
142
|
parameters: figmaPrimaryDesign,
|
|
134
143
|
}
|
|
144
|
+
|
|
145
|
+
export const PrimaryWithSearch: Story = {
|
|
146
|
+
args: {
|
|
147
|
+
variant: 'primary',
|
|
148
|
+
disabled: false,
|
|
149
|
+
helpText: 'This text can help you',
|
|
150
|
+
name: 'example',
|
|
151
|
+
label: 'Videogames',
|
|
152
|
+
accessibilityLabel: 'Select your favourite gaming system options',
|
|
153
|
+
hideLabel: false,
|
|
154
|
+
placeholder: 'Select your favourite gaming system...',
|
|
155
|
+
options: [
|
|
156
|
+
{ id: '1', label: 'Nintendo Switch' },
|
|
157
|
+
{ id: '2', label: 'PlayStation 5' },
|
|
158
|
+
{ id: '3', label: 'Xbox Series S/X' },
|
|
159
|
+
{ id: '4', label: 'PC' },
|
|
160
|
+
{ id: '5', label: 'Mobile' },
|
|
161
|
+
{ id: '6', label: 'PlayStation 4' },
|
|
162
|
+
{ id: '7', label: 'Xbox One' },
|
|
163
|
+
{ id: '8', label: 'PlayStation 3' },
|
|
164
|
+
{ id: '9', label: 'Xbox 360' },
|
|
165
|
+
{ id: '10', label: 'PlayStation 2' },
|
|
166
|
+
{ id: '11', label: 'Xbox' },
|
|
167
|
+
{ id: '12', label: 'PlayStation' },
|
|
168
|
+
{ id: '13', label: 'Nintendo 64' },
|
|
169
|
+
{ id: '14', label: 'Super Nintendo' },
|
|
170
|
+
{ id: '15', label: 'Sega Genesis' },
|
|
171
|
+
{ id: '16', label: 'Sega Saturn' },
|
|
172
|
+
{ id: '17', label: 'Sega Dreamcast' },
|
|
173
|
+
{ id: '18', label: 'Atari 2600' },
|
|
174
|
+
],
|
|
175
|
+
id: 'select-videogames',
|
|
176
|
+
onChange: (optionId) => console.log('onChange optionId:', optionId),
|
|
177
|
+
isSearchable: true,
|
|
178
|
+
searchLabel: 'Search',
|
|
179
|
+
},
|
|
180
|
+
parameters: figmaPrimaryDesign,
|
|
181
|
+
}
|
|
@@ -30,6 +30,7 @@ describe('Multiselect', () => {
|
|
|
30
30
|
placeholder="Select your favourite videogames..."
|
|
31
31
|
selectedLabel="videogames selected"
|
|
32
32
|
variant="primary"
|
|
33
|
+
isSearchable={false}
|
|
33
34
|
/>,
|
|
34
35
|
)
|
|
35
36
|
|
|
@@ -70,6 +71,7 @@ describe('Multiselect', () => {
|
|
|
70
71
|
defaultValue={['2', '1']}
|
|
71
72
|
selectedLabel="videogames selected"
|
|
72
73
|
variant="primary"
|
|
74
|
+
isSearchable={false}
|
|
73
75
|
/>,
|
|
74
76
|
)
|
|
75
77
|
|
|
@@ -106,6 +108,7 @@ describe('Multiselect', () => {
|
|
|
106
108
|
placeholder="Select your favourite videogames..."
|
|
107
109
|
selectedLabel="videogames selected"
|
|
108
110
|
variant="primary"
|
|
111
|
+
isSearchable={false}
|
|
109
112
|
/>,
|
|
110
113
|
)
|
|
111
114
|
|
|
@@ -140,6 +143,7 @@ describe('Multiselect', () => {
|
|
|
140
143
|
selectedLabel="videogames selected"
|
|
141
144
|
onChange={mockChange}
|
|
142
145
|
variant="primary"
|
|
146
|
+
isSearchable={false}
|
|
143
147
|
/>,
|
|
144
148
|
)
|
|
145
149
|
|
|
@@ -148,4 +152,45 @@ describe('Multiselect', () => {
|
|
|
148
152
|
expect(getByText(placeholder)).toBeInTheDocument()
|
|
149
153
|
expect(mockChange).toHaveBeenCalledWith([])
|
|
150
154
|
})
|
|
155
|
+
|
|
156
|
+
it('return filtered options by search', async () => {
|
|
157
|
+
const user = userEvent.setup()
|
|
158
|
+
const placeholder = 'Select your favourite gaming system...'
|
|
159
|
+
const options = [
|
|
160
|
+
{
|
|
161
|
+
id: '1',
|
|
162
|
+
label: 'Nintendo Switch',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: '2',
|
|
166
|
+
label: 'PlayStation 5',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: '3',
|
|
170
|
+
label: 'Xbox Series S/X',
|
|
171
|
+
},
|
|
172
|
+
]
|
|
173
|
+
const { queryByText, getByText } = render(
|
|
174
|
+
<Multiselect
|
|
175
|
+
helpText="This text can help you"
|
|
176
|
+
id="select-videogames"
|
|
177
|
+
label="Videogames"
|
|
178
|
+
name="example"
|
|
179
|
+
isSearchable={true}
|
|
180
|
+
options={options}
|
|
181
|
+
placeholder={placeholder}
|
|
182
|
+
variant="primary"
|
|
183
|
+
/>,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
await user.click(screen.getByRole('alert'))
|
|
187
|
+
|
|
188
|
+
const input = screen.getByRole('textbox')
|
|
189
|
+
|
|
190
|
+
await user.type(input, ' PlaySta')
|
|
191
|
+
|
|
192
|
+
expect(getByText('PlayStation 5')).toBeInTheDocument()
|
|
193
|
+
expect(queryByText('Nintendo Switch')).not.toBeInTheDocument()
|
|
194
|
+
expect(queryByText('Xbox Series S/X')).not.toBeInTheDocument()
|
|
195
|
+
})
|
|
151
196
|
})
|
package/tests/Select.spec.tsx
CHANGED
|
@@ -11,6 +11,7 @@ describe('Select', () => {
|
|
|
11
11
|
helpText="This text can help you"
|
|
12
12
|
id="select-videogames"
|
|
13
13
|
label="Videogames"
|
|
14
|
+
isSearchable={false}
|
|
14
15
|
name="example"
|
|
15
16
|
options={[
|
|
16
17
|
{
|
|
@@ -54,6 +55,7 @@ describe('Select', () => {
|
|
|
54
55
|
id="select-videogames"
|
|
55
56
|
label="Videogames"
|
|
56
57
|
name="example"
|
|
58
|
+
isSearchable={false}
|
|
57
59
|
options={[
|
|
58
60
|
{
|
|
59
61
|
id: '1',
|
|
@@ -94,6 +96,7 @@ describe('Select', () => {
|
|
|
94
96
|
id="select-videogames"
|
|
95
97
|
label="Videogames"
|
|
96
98
|
name="example"
|
|
99
|
+
isSearchable={false}
|
|
97
100
|
onChange={() => {}}
|
|
98
101
|
options={[
|
|
99
102
|
{
|
|
@@ -128,6 +131,7 @@ describe('Select', () => {
|
|
|
128
131
|
id="select-videogames"
|
|
129
132
|
label="Videogames"
|
|
130
133
|
name="example"
|
|
134
|
+
isSearchable={false}
|
|
131
135
|
options={[
|
|
132
136
|
{
|
|
133
137
|
id: '1',
|
|
@@ -153,4 +157,45 @@ describe('Select', () => {
|
|
|
153
157
|
expect(getByText(placeholder)).toBeInTheDocument()
|
|
154
158
|
expect(mockChange).toHaveBeenCalledWith('')
|
|
155
159
|
})
|
|
160
|
+
|
|
161
|
+
it('return filtered options by search', async () => {
|
|
162
|
+
const user = userEvent.setup()
|
|
163
|
+
const placeholder = 'Select your favourite gaming system...'
|
|
164
|
+
const options = [
|
|
165
|
+
{
|
|
166
|
+
id: '1',
|
|
167
|
+
label: 'Nintendo Switch',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: '2',
|
|
171
|
+
label: 'PlayStation 5',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: '3',
|
|
175
|
+
label: 'Xbox Series S/X',
|
|
176
|
+
},
|
|
177
|
+
]
|
|
178
|
+
const { queryByText, getByText } = render(
|
|
179
|
+
<Select
|
|
180
|
+
helpText="This text can help you"
|
|
181
|
+
id="select-videogames"
|
|
182
|
+
label="Videogames"
|
|
183
|
+
name="example"
|
|
184
|
+
isSearchable={true}
|
|
185
|
+
options={options}
|
|
186
|
+
placeholder={placeholder}
|
|
187
|
+
variant="primary"
|
|
188
|
+
/>,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
await user.click(screen.getByRole('alert'))
|
|
192
|
+
|
|
193
|
+
const input = screen.getByRole('textbox')
|
|
194
|
+
|
|
195
|
+
await user.type(input, ' PlaySta')
|
|
196
|
+
|
|
197
|
+
expect(getByText('PlayStation 5')).toBeInTheDocument()
|
|
198
|
+
expect(queryByText('Nintendo Switch')).not.toBeInTheDocument()
|
|
199
|
+
expect(queryByText('Xbox Series S/X')).not.toBeInTheDocument()
|
|
200
|
+
})
|
|
156
201
|
})
|
|
File without changes
|
|
File without changes
|