@intility/bifrost-react-select 4.0.0-alpha.3

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 (38) hide show
  1. package/.swcrc +16 -0
  2. package/CHANGELOG.md +12 -0
  3. package/dist/Select.d.ts +85 -0
  4. package/dist/Select.js +246 -0
  5. package/dist/Select.test.d.ts +1 -0
  6. package/dist/assets/bfSpinner.d.ts +10 -0
  7. package/dist/assets/bfSpinner.js +22 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +1 -0
  10. package/dist/overrides/ClearIndicator.internal.d.ts +2 -0
  11. package/dist/overrides/ClearIndicator.internal.js +13 -0
  12. package/dist/overrides/CustomInput.internal.d.ts +2 -0
  13. package/dist/overrides/CustomInput.internal.js +32 -0
  14. package/dist/overrides/CustomMenuList.internal.d.ts +2 -0
  15. package/dist/overrides/CustomMenuList.internal.js +11 -0
  16. package/dist/overrides/CustomOption.internal.d.ts +2 -0
  17. package/dist/overrides/CustomOption.internal.js +29 -0
  18. package/dist/overrides/DropdownIndicator.internal.d.ts +2 -0
  19. package/dist/overrides/DropdownIndicator.internal.js +13 -0
  20. package/dist/overrides/LoadingIndicator.internal.d.ts +1 -0
  21. package/dist/overrides/LoadingIndicator.internal.js +11 -0
  22. package/dist/overrides/index.d.ts +6 -0
  23. package/dist/overrides/index.js +6 -0
  24. package/package.json +47 -0
  25. package/src/Select.md +410 -0
  26. package/src/Select.test.tsx +118 -0
  27. package/src/Select.tsx +436 -0
  28. package/src/assets/bfSpinner.ts +37 -0
  29. package/src/index.ts +8 -0
  30. package/src/overrides/ClearIndicator.internal.tsx +13 -0
  31. package/src/overrides/CustomInput.internal.tsx +32 -0
  32. package/src/overrides/CustomMenuList.internal.tsx +15 -0
  33. package/src/overrides/CustomOption.internal.tsx +29 -0
  34. package/src/overrides/DropdownIndicator.internal.tsx +13 -0
  35. package/src/overrides/LoadingIndicator.internal.tsx +9 -0
  36. package/src/overrides/index.ts +6 -0
  37. package/tsconfig.json +16 -0
  38. package/vitest.config.ts +12 -0
package/src/Select.md ADDED
@@ -0,0 +1,410 @@
1
+ <Message header='More props available'>
2
+ This component is a wrapper for react-select. Visit [React Select](https://react-select.com/home) for full documentation.
3
+
4
+ To see all available props, click [here](https://react-select.com/props).
5
+ </Message>
6
+
7
+ ### Basic Select
8
+
9
+ ```jsx
10
+ const options = [
11
+ { value: 'Norway', label: 'Norway' },
12
+ { value: 'Sweden', label: 'Sweden' },
13
+ { value: 'Denmark', label: 'Denmark' },
14
+ { value: 'Germany', label: 'Germany' }
15
+ ]
16
+
17
+ ;<Select
18
+ label='Country'
19
+ placeholder='- Select country -'
20
+ options={options}
21
+ description='Select your country of residence'
22
+ />
23
+ ```
24
+
25
+ You can get and set the currently selected item using `value` and `onChange`.
26
+
27
+ ```jsx
28
+ import { useState } from 'react'
29
+ import { Select, Button } from '@intility/bifrost-react'
30
+
31
+ const options = [
32
+ { value: 'Norway', label: 'Norway' },
33
+ { value: 'Sweden', label: 'Sweden' },
34
+ { value: 'Denmark', label: 'Denmark' },
35
+ { value: 'Germany', label: 'Germany' }
36
+ ]
37
+ const [selected, setSelected] = useState(options[2])
38
+
39
+ ;<div>
40
+ <Select
41
+ label='Country'
42
+ placeholder='- Select country -'
43
+ description='Select your country of residence'
44
+ options={options}
45
+ value={selected}
46
+ onChange={item => setSelected(item)}
47
+ />
48
+ <p>Selected item: {selected.label}</p>
49
+ <Button onClick={() => setSelected(options[0])}>Select first option</Button>
50
+ </div>
51
+ ```
52
+
53
+ ### Clearable
54
+
55
+ Use `isClearable` prop for possibility to remove selected option with button.
56
+
57
+ ```jsx
58
+ import { useState } from 'react'
59
+ import { Select } from '@intility/bifrost-react'
60
+
61
+ const options = [
62
+ { value: 'Norway', label: 'Norway' },
63
+ { value: 'Sweden', label: 'Sweden' },
64
+ { value: 'Denmark', label: 'Denmark' },
65
+ { value: 'Germany', label: 'Germany' }
66
+ ]
67
+ const [selected, setSelected] = useState()
68
+
69
+ ;<Select
70
+ label='Country'
71
+ placeholder='- Select country -'
72
+ options={options}
73
+ onChange={item => setSelected(item)}
74
+ isClearable
75
+ />
76
+ ```
77
+
78
+ ### Validation
79
+
80
+ `Select` can indicate an alert state based on input. Set `state` prop to `alert` to style it accordingly. Meanwhile, use `feedback` to provide additional information.
81
+
82
+ Use the `required` prop to render `Select` as required. This will add `(required)` text after the label. You can hide this label with `requiredNoLabel`.
83
+
84
+ <Message header='Read more'>
85
+ See usage demo at [Forms example](#/Welcome%20to%20Bifrost/Examples/Forms)
86
+ </Message>
87
+
88
+ ```jsx
89
+ import { useState } from 'react'
90
+ import { Select } from '@intility/bifrost-react'
91
+
92
+ const options = [
93
+ { value: 'Norway', label: 'Norway' },
94
+ { value: 'Sweden', label: 'Sweden' },
95
+ { value: 'Denmark', label: 'Denmark' },
96
+ { value: 'Germany', label: 'Germany' }
97
+ ]
98
+
99
+ const [selected, setSelected] = useState()
100
+
101
+ ;<Select
102
+ label='Country'
103
+ placeholder='- Select country -'
104
+ description='Select your country of residence'
105
+ options={options}
106
+ onChange={item => setSelected(item)}
107
+ state={selected ? 'default' : 'alert'}
108
+ feedback={selected ? '' : 'Missing value'}
109
+ isClearable
110
+ required
111
+ />
112
+ ```
113
+
114
+ ### Multi Select
115
+
116
+ Using the `isMulti` prop makes it possible to select multiple options.
117
+
118
+ ```jsx
119
+ import { useState } from 'react'
120
+ import { Select } from '@intility/bifrost-react'
121
+
122
+ const options = [
123
+ { value: 'Norway', label: 'Norway' },
124
+ { value: 'Sweden', label: 'Sweden' },
125
+ { value: 'Denmark', label: 'Denmark' },
126
+ { value: 'Finland', label: 'Finland' },
127
+ { value: 'France', label: 'France' },
128
+ { value: 'Spain', label: 'Spain' },
129
+ { value: 'Italy', label: 'Italy' },
130
+ { value: 'Germany', label: 'Germany' }
131
+ ]
132
+
133
+ const [selected, setSelected] = useState([])
134
+
135
+ ;<Select
136
+ label='Countries'
137
+ placeholder='- Select countries -'
138
+ description='Select countries you have visited'
139
+ options={options}
140
+ value={selected}
141
+ onChange={items => setSelected(items)}
142
+ isMulti
143
+ />
144
+ ```
145
+
146
+ ### Grouped options
147
+
148
+ To make grouped options with labels you simply need to structure the `options` value a bit different.
149
+
150
+ ```jsx
151
+ const options = [
152
+ {
153
+ label: 'Marvel',
154
+ options: [
155
+ { value: 'Iron Man', label: 'Iron Man' },
156
+ { value: 'Spider-man', label: 'Spider-man' },
157
+ { value: 'Star Lord', label: 'Star Lord' }
158
+ ]
159
+ },
160
+ {
161
+ label: 'DC',
162
+ options: [
163
+ { value: 'Superman', label: 'Superman' },
164
+ { value: 'Batman', label: 'Batman' },
165
+ { value: 'The Flash', label: 'The Flash' }
166
+ ]
167
+ }
168
+ ]
169
+
170
+ ;<div className='bfl-autocol'>
171
+ <Select
172
+ label='Superhero'
173
+ placeholder='- Select your hero -'
174
+ options={options}
175
+ />
176
+ <Select
177
+ label='Superheroes'
178
+ placeholder='- Select multiple heroes -'
179
+ options={options}
180
+ isMulti
181
+ />
182
+ </div>
183
+ ```
184
+
185
+ ### Disabled
186
+
187
+ Disable a `<Select>` with `isDisabled` prop.
188
+
189
+ You can also disable an option with `isDisabled` property on the option object.
190
+
191
+ ```jsx
192
+ const options = [
193
+ { value: 'Norway', label: 'Norway' },
194
+ { value: 'Sweden', label: 'Sweden' },
195
+ { value: 'Denmark', label: 'Denmark', isDisabled: true },
196
+ { value: 'Germany', label: 'Germany' }
197
+ ]
198
+
199
+ ;<div className='bfl-autocol'>
200
+ <Select
201
+ label='Disabled select'
202
+ placeholder='- Select country -'
203
+ description='This is a disabled component'
204
+ isDisabled
205
+ />
206
+ <Select
207
+ label='Disabled option'
208
+ placeholder='- Select country -'
209
+ description='Denmark option is disabled'
210
+ options={options}
211
+ />
212
+ </div>
213
+ ```
214
+
215
+ ### Loading spinner
216
+
217
+ The `loading` prop displays a loading spinner on the right hand side of the input.
218
+
219
+ ```jsx
220
+ const options = [
221
+ { value: 'Norway', label: 'Norway' },
222
+ { value: 'Sweden', label: 'Sweden' },
223
+ { value: 'Denmark', label: 'Denmark' },
224
+ { value: 'Germany', label: 'Germany' }
225
+ ]
226
+
227
+ ;<Select
228
+ label='Country'
229
+ placeholder='- Select country -'
230
+ options={options}
231
+ loading
232
+ />
233
+ ```
234
+
235
+ ### Hide label
236
+
237
+ The `label` prop is required, but you can hide it with `hideLabel`.
238
+
239
+ ```jsx
240
+ const options = [
241
+ { value: 'Norway', label: 'Norway' },
242
+ { value: 'Sweden', label: 'Sweden' },
243
+ { value: 'Denmark', label: 'Denmark' },
244
+ { value: 'Germany', label: 'Germany' }
245
+ ]
246
+
247
+ ;<Select
248
+ label='Country'
249
+ hideLabel
250
+ placeholder='- Select country -'
251
+ options={options}
252
+ />
253
+ ```
254
+
255
+ ### Async
256
+
257
+ AsyncSelect can be used to load options from a source as the user types.
258
+
259
+ Try type 'No' in the demo below
260
+
261
+ ```jsx
262
+ import { useState } from 'react'
263
+ import { AsyncSelect } from '@intility/bifrost-react'
264
+
265
+ const countries = [
266
+ { value: 'Norway', label: 'Norway' },
267
+ { value: 'Sweden', label: 'Sweden' },
268
+ { value: 'Denmark', label: 'Denmark' },
269
+ { value: 'Germany', label: 'Germany' }
270
+ ]
271
+
272
+ const [selected, setSelected] = useState()
273
+
274
+ function getAsyncOptions(inputValue) {
275
+ return new Promise((resolve, reject) => {
276
+ const filtered = countries.filter(c =>
277
+ String(c.label.toLowerCase()).startsWith(inputValue.toLowerCase())
278
+ )
279
+
280
+ resolve(filtered.slice(0, 3))
281
+ })
282
+ }
283
+
284
+ ;<AsyncSelect
285
+ label='Country'
286
+ placeholder='- Select country -'
287
+ loadOptions={inputValue => getAsyncOptions(inputValue)}
288
+ onChange={item => setSelected(item)}
289
+ isClearable
290
+ />
291
+ ```
292
+
293
+ ### Creatable
294
+
295
+ CreatableSelect can be used to create new options.
296
+
297
+ Try to type 'England' and add the new option to the component
298
+
299
+ ```jsx
300
+ import { useState } from 'react'
301
+ import { CreatableSelect } from '@intility/bifrost-react'
302
+
303
+ const countries = [
304
+ { value: 'Norway', label: 'Norway' },
305
+ { value: 'Sweden', label: 'Sweden' },
306
+ { value: 'Denmark', label: 'Denmark' },
307
+ { value: 'Germany', label: 'Germany' }
308
+ ]
309
+
310
+ const [selected, setSelected] = useState()
311
+ const [options, setOptions] = useState(countries)
312
+
313
+ const handleCreate = option => {
314
+ const newOption = {
315
+ value: option,
316
+ label: option
317
+ }
318
+
319
+ setOptions(options => [...options, newOption])
320
+ setSelected(newOption)
321
+ }
322
+
323
+ ;<CreatableSelect
324
+ label='Country'
325
+ placeholder='- Select country -'
326
+ options={options}
327
+ value={selected}
328
+ onChange={item => setSelected(item)}
329
+ onCreateOption={option => handleCreate(option)}
330
+ isClearable
331
+ />
332
+ ```
333
+
334
+ ### Replace components
335
+
336
+ `react-select` allows you to [replace internal components](https://react-select.com/components) which allows for more advanced use cases.
337
+
338
+ Let's say you have a multi-select, but don't want to display a badge for each selected item, but something like "Selected (4)" instead, you can replace the `ValueContainer` component:
339
+
340
+ ```jsx
341
+ import { useState } from 'react'
342
+ import { components } from 'react-select'
343
+ import { Select } from '@intility/bifrost-react'
344
+
345
+ const ValueContainer = ({ children, ...props }) => {
346
+ let [values, input] = children
347
+
348
+ if (Array.isArray(values)) {
349
+ // convert array of values to a string that will get displayed
350
+ values = `Selected (${values.length})`
351
+ }
352
+
353
+ return (
354
+ <components.ValueContainer {...props}>
355
+ {values}
356
+ {input}
357
+ </components.ValueContainer>
358
+ )
359
+ }
360
+
361
+ const options = [
362
+ { value: 'Norway', label: 'Norway' },
363
+ { value: 'Sweden', label: 'Sweden' },
364
+ { value: 'Denmark', label: 'Denmark' },
365
+ { value: 'Finland', label: 'Finland' },
366
+ { value: 'France', label: 'France' },
367
+ { value: 'Spain', label: 'Spain' },
368
+ { value: 'Italy', label: 'Italy' },
369
+ { value: 'Germany', label: 'Germany' }
370
+ ]
371
+
372
+ const SelectDemo = () => {
373
+ const [selected, setSelected] = useState([])
374
+
375
+ return (
376
+ <Select
377
+ label='Countries'
378
+ placeholder='- Select countries -'
379
+ description='Select countries you have visited'
380
+ options={options}
381
+ value={selected}
382
+ onChange={items => setSelected(items)}
383
+ isMulti
384
+ // pass in the custom override
385
+ components={{
386
+ ValueContainer
387
+ }}
388
+ />
389
+ )
390
+ }
391
+
392
+ ;<SelectDemo />
393
+ ```
394
+
395
+ Bifrost replaces the following components internally:
396
+
397
+ - ClearIndicator
398
+ - DropdownIndicator
399
+ - Input
400
+ - LoadingIndicator
401
+ - MenuList
402
+ - Option
403
+
404
+ To use or wrap our components in your own overrides, you can import them from `selectComponents` <Copy text="import { selectComponents } from '@intility/bifrost-react'" />
405
+
406
+ ```jsx static
407
+ import { selectComponents } from '@intility/bifrost-react'
408
+ // ...
409
+ ;<selectComponents.Option>...</selectComponents.Option>
410
+ ```
@@ -0,0 +1,118 @@
1
+ import Select from './Select'
2
+ import { render } from '@testing-library/react'
3
+
4
+ let getByTestId, getByText, queryByText, element, customInput, rerender
5
+
6
+ beforeEach(() => {
7
+ const options = [
8
+ { value: 'Norway', label: 'Norway' },
9
+ { value: 'Sweden', label: 'Sweden' },
10
+ { value: 'Denmark', label: 'Denmark' },
11
+ { value: 'Germany', label: 'Germany' }
12
+ ]
13
+
14
+ const select = render(
15
+ <Select
16
+ label='Select country'
17
+ placeholder='Select country here'
18
+ options={options}
19
+ />
20
+ )
21
+
22
+ getByTestId = select.getByTestId
23
+ getByText = select.getByText
24
+ queryByText = select.queryByText
25
+ element = getByTestId('bf-select-container')
26
+ customInput = getByTestId('bf-select-input')
27
+ rerender = select.rerender
28
+ // debug = select.debug
29
+ })
30
+
31
+ describe('select component', () => {
32
+ it('renders select component', () => {
33
+ expect(element)
34
+ })
35
+ })
36
+
37
+ describe('select with required label prop', () => {
38
+ it('renders label text', () => {
39
+ expect(getByText('Select country'))
40
+ })
41
+ })
42
+
43
+ describe('select with className and style', () => {
44
+ it('renders with custom classname and custom styles', () => {
45
+ rerender(
46
+ <Select
47
+ label='Custom classname'
48
+ className='custom-classname'
49
+ style={{ width: '100%' }}
50
+ />
51
+ )
52
+
53
+ expect(element).toHaveClass('custom-classname')
54
+ expect(element).toHaveStyle('width: 100%')
55
+ })
56
+ })
57
+
58
+ describe('select with description prop', () => {
59
+ it('renders with text as description', () => {
60
+ rerender(
61
+ <Select label='Select country' description='Please select one country' />
62
+ )
63
+
64
+ expect(getByText('Please select one country'))
65
+ })
66
+ })
67
+
68
+ describe('select with state and feedback props', () => {
69
+ it('renders with alert state ', () => {
70
+ rerender(
71
+ <Select label='Select country' state='alert' feedback='Missing value ' />
72
+ )
73
+
74
+ expect(element).toHaveClass('bf-select-alert')
75
+ })
76
+
77
+ it('renders with feedback text', () => {
78
+ rerender(
79
+ <Select label='Select country' state='alert' feedback='Missing value ' />
80
+ )
81
+ const feedback = getByText('Missing value')
82
+
83
+ expect(feedback)
84
+ expect(feedback).toHaveClass('bf-label-feedback')
85
+ expect(feedback.tagName.toLowerCase()).toBe('div')
86
+ })
87
+ })
88
+
89
+ describe('select disabled', () => {
90
+ it('renders as disabled', () => {
91
+ rerender(<Select label='Disabled' isDisabled />)
92
+ // can only test classname, hence isDisabled is a prop for react-select
93
+ expect(element).toHaveClass('bf-select-disabled')
94
+ })
95
+ })
96
+
97
+ describe('select with required/optional props', () => {
98
+ it('renders as required', () => {
99
+ rerender(<Select label='This is required' required />)
100
+
101
+ expect(customInput).toBeRequired()
102
+ expect(getByText('(required)'))
103
+ })
104
+
105
+ it('renders as required, but hiding the (required) text', () => {
106
+ rerender(<Select label='This is required' requiredNoLabel />)
107
+
108
+ expect(customInput).toBeRequired()
109
+ expect(queryByText('(required)')).toBeNull()
110
+ })
111
+
112
+ it('renders as optional with (optional) text', () => {
113
+ rerender(<Select label='This is optional' optional />)
114
+
115
+ expect(customInput).not.toBeRequired()
116
+ expect(getByText('(optional)'))
117
+ })
118
+ })