@leafygreen-ui/combobox 1.0.3 → 1.2.1

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 (49) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +2 -2
  3. package/dist/Chip.d.ts.map +1 -1
  4. package/dist/Combobox.d.ts +7 -1
  5. package/dist/Combobox.d.ts.map +1 -1
  6. package/dist/Combobox.styles.d.ts +7 -3
  7. package/dist/Combobox.styles.d.ts.map +1 -1
  8. package/dist/Combobox.types.d.ts +33 -6
  9. package/dist/Combobox.types.d.ts.map +1 -1
  10. package/dist/ComboboxContext.d.ts +1 -1
  11. package/dist/ComboboxContext.d.ts.map +1 -1
  12. package/dist/ComboboxOption.d.ts.map +1 -1
  13. package/dist/ComboboxTestUtils.d.ts +1 -2
  14. package/dist/ComboboxTestUtils.d.ts.map +1 -1
  15. package/dist/esm/index.js +1 -1
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/utils/OptionObjectUtils.d.ts +5 -0
  20. package/dist/utils/OptionObjectUtils.d.ts.map +1 -0
  21. package/dist/utils/flattenChildren.d.ts +11 -0
  22. package/dist/utils/flattenChildren.d.ts.map +1 -0
  23. package/dist/utils/getNameAndValue.d.ts +14 -0
  24. package/dist/utils/getNameAndValue.d.ts.map +1 -0
  25. package/dist/utils/index.d.ts +5 -0
  26. package/dist/utils/index.d.ts.map +1 -0
  27. package/dist/utils/wrapJSX.d.ts +14 -0
  28. package/dist/utils/wrapJSX.d.ts.map +1 -0
  29. package/package.json +20 -10
  30. package/src/Chip.tsx +16 -9
  31. package/src/Combobox.spec.tsx +322 -139
  32. package/src/Combobox.story.tsx +274 -248
  33. package/src/Combobox.styles.ts +94 -24
  34. package/src/Combobox.tsx +446 -266
  35. package/src/Combobox.types.ts +43 -6
  36. package/src/ComboboxContext.tsx +2 -2
  37. package/src/ComboboxOption.tsx +34 -8
  38. package/src/ComboboxTestUtils.tsx +22 -8
  39. package/src/utils/ComboboxUtils.spec.tsx +227 -0
  40. package/src/utils/OptionObjectUtils.ts +26 -0
  41. package/src/utils/flattenChildren.tsx +47 -0
  42. package/src/utils/getNameAndValue.ts +23 -0
  43. package/src/utils/index.ts +8 -0
  44. package/src/utils/wrapJSX.tsx +54 -0
  45. package/tsconfig.json +3 -0
  46. package/tsconfig.tsbuildinfo +1 -1
  47. package/dist/util.d.ts +0 -53
  48. package/dist/util.d.ts.map +0 -1
  49. package/src/util.tsx +0 -117
@@ -1,254 +1,280 @@
1
- import React from 'react';
2
- import { boolean, select } from '@storybook/addon-knobs';
3
- import { storiesOf } from '@storybook/react';
4
- import LeafygreenProvider from '@leafygreen-ui/leafygreen-provider';
1
+ import React, { useState } from 'react';
5
2
  import Icon from '@leafygreen-ui/icon';
6
3
  import Button from '@leafygreen-ui/button';
7
- import { css } from '@leafygreen-ui/emotion';
8
4
  import { Combobox, ComboboxOption, ComboboxGroup } from '.';
9
- import { useState } from '@storybook/client-api';
10
-
11
- const Wrapper = ({ children }: any) => (
12
- <div
13
- className={css`
14
- width: 384px;
15
- height: 100vh;
16
- `}
17
- >
18
- <LeafygreenProvider>{children}</LeafygreenProvider>
5
+ import { ComponentStory, ComponentMeta } from '@storybook/react';
6
+ import { css } from '@leafygreen-ui/emotion';
7
+ import {
8
+ ComboboxSize,
9
+ Overflow,
10
+ SearchState,
11
+ State,
12
+ TrunctationLocation,
13
+ } from './Combobox.types';
14
+
15
+ const wrapperStyle = css`
16
+ width: 256px;
17
+ `;
18
+
19
+ export default {
20
+ title: 'Packages/Combobox',
21
+ component: Combobox,
22
+ parameters: {
23
+ controls: {
24
+ exclude: [
25
+ 'children',
26
+ 'aria-label',
27
+ 'setError',
28
+ 'onFilter',
29
+ 'onClear',
30
+ 'onChange',
31
+ 'filteredOptions',
32
+ 'className',
33
+ 'usePortal',
34
+ 'portalClassName',
35
+ 'portalContainer',
36
+ 'scrollContainer',
37
+ 'popoverZIndex',
38
+ 'initialValue',
39
+ 'value',
40
+ ],
41
+ },
42
+ },
43
+ argTypes: {
44
+ multiselect: {
45
+ control: 'boolean',
46
+ },
47
+ disabled: {
48
+ control: 'boolean',
49
+ },
50
+ darkMode: {
51
+ control: 'boolean',
52
+ },
53
+ clearable: {
54
+ control: 'boolean',
55
+ },
56
+ label: {
57
+ control: 'text',
58
+ },
59
+ description: {
60
+ control: 'text',
61
+ },
62
+ placeholder: {
63
+ control: 'text',
64
+ },
65
+ size: {
66
+ options: Object.values(ComboboxSize),
67
+ control: 'select',
68
+ },
69
+ state: {
70
+ options: Object.values(State),
71
+ control: 'select',
72
+ },
73
+ errorMessage: {
74
+ control: 'text',
75
+ if: { arg: 'state', eq: State.error },
76
+ },
77
+ searchEmptyMessage: {
78
+ control: 'text',
79
+ },
80
+ searchState: {
81
+ options: Object.values(SearchState),
82
+ control: 'select',
83
+ },
84
+ searchErrorMessage: {
85
+ control: 'text',
86
+ if: { arg: 'searchState', eq: SearchState.error },
87
+ },
88
+ searchLoadingMessage: {
89
+ control: 'text',
90
+ if: { arg: 'searchState', eq: SearchState.loading },
91
+ },
92
+ chipTruncationLocation: {
93
+ options: Object.values(TrunctationLocation),
94
+ control: 'select',
95
+ if: { arg: 'multiselect' },
96
+ },
97
+ chipCharacterLimit: {
98
+ control: 'number',
99
+ if: { arg: 'multiselect' },
100
+ },
101
+ overflow: {
102
+ options: Object.values(Overflow),
103
+ control: 'select',
104
+ if: { arg: 'multiselect' },
105
+ },
106
+ },
107
+ args: {
108
+ multiselect: false,
109
+ darkMode: false,
110
+ disabled: false,
111
+ clearable: true,
112
+ },
113
+ } as ComponentMeta<typeof Combobox>;
114
+
115
+ const ComboboxOptions = [
116
+ <ComboboxOption key="apple" value="apple" displayName="Apple" />,
117
+ <ComboboxOption key="banana" value="banana" displayName="Banana" />,
118
+ <ComboboxOption key="carrot" value="carrot" displayName="Carrot" disabled />,
119
+ <ComboboxOption
120
+ key="paragraph"
121
+ value="paragraph"
122
+ displayName="Nullam quis risus eget urna mollis ornare vel eu leo. Vestibulum id ligula porta felis euismod semper."
123
+ />,
124
+ <ComboboxOption
125
+ key="hash"
126
+ value="hash"
127
+ displayName="5f4dcc3b5aa765d61d8327deb882cf995f4dcc3b5aa765d61d8327deb882cf99"
128
+ />,
129
+ <ComboboxOption
130
+ key="dragonfruit"
131
+ value="dragonfruit"
132
+ displayName="Dragonfruit"
133
+ />,
134
+ <ComboboxOption key="eggplant" value="eggplant" displayName="Eggplant" />,
135
+ <ComboboxOption key="fig" value="fig" displayName="Fig" />,
136
+ <ComboboxOption key="grape" value="grape" displayName="Grape" />,
137
+ <ComboboxOption key="honeydew" value="honeydew" displayName="Honeydew" />,
138
+ <ComboboxOption
139
+ key="iceberg-lettuce"
140
+ value="iceberg-lettuce"
141
+ displayName="Iceberg lettuce"
142
+ />,
143
+ <ComboboxOption
144
+ key="pomegranate"
145
+ value="pomegranate"
146
+ displayName="Pomegranate"
147
+ glyph={<Icon glyph="Warning" />}
148
+ />,
149
+ <ComboboxGroup key="peppers" label="Peppers">
150
+ <ComboboxOption key="cayenne" value="cayenne" displayName="Cayenne" />
151
+ <ComboboxOption
152
+ key="ghost-pepper"
153
+ value="ghost-pepper"
154
+ displayName="Ghost pepper"
155
+ />
156
+ <ComboboxOption key="habanero" value="habanero" displayName="Habanero" />
157
+ <ComboboxOption key="jalapeno" value="jalapeno" displayName="Jalapeño" />
158
+ <ComboboxOption
159
+ key="red-pepper"
160
+ value="red-pepper"
161
+ displayName="Red pepper"
162
+ />
163
+ <ComboboxOption
164
+ key="scotch-bonnet"
165
+ value="scotch-bonnet"
166
+ displayName="Scotch bonnet"
167
+ />
168
+ </ComboboxGroup>,
169
+ ];
170
+
171
+ const Template: ComponentStory<typeof Combobox> = args => (
172
+ <div className={wrapperStyle}>
173
+ <Combobox {...args} />
19
174
  </div>
20
175
  );
21
176
 
22
- storiesOf('Packages/Combobox', module)
23
- .add('Single Select', () => {
24
- const [isError, setIsError] = useState(false);
25
-
26
- const handleChange = (value: string | null) => {
27
- if (value === 'pomegranate') {
28
- setIsError(true);
29
- } else {
30
- setIsError(false);
31
- }
32
- };
33
-
34
- return (
35
- <Wrapper>
36
- <Combobox
37
- label="Choose a fruit"
38
- description="Please pick one"
39
- placeholder="Select fruit"
40
- searchState={select(
41
- 'Seach state',
42
- ['unset', 'error', 'loading'],
43
- 'unset',
44
- )}
45
- state={isError ? 'error' : 'none'}
46
- disabled={boolean('Disabled', false)}
47
- errorMessage="No Pomegranates!"
48
- onChange={handleChange}
49
- >
50
- <ComboboxOption value="apple" displayName="Apple" />
51
- <ComboboxOption value="banana" displayName="Banana" />
52
- <ComboboxOption value="carrot" displayName="Carrot" />
53
- <ComboboxOption
54
- value="paragraph"
55
- displayName="Nullam quis risus eget urna mollis ornare vel eu leo. Vestibulum id ligula porta felis euismod semper."
56
- />
57
- <ComboboxOption
58
- value="hash"
59
- displayName="5f4dcc3b5aa765d61d8327deb882cf99"
60
- />
61
- <ComboboxOption value="dragonfruit" displayName="Dragonfruit" />
62
- <ComboboxOption value="eggplant" displayName="Eggplant" />
63
- <ComboboxOption value="fig" displayName="Fig" />
64
- <ComboboxOption value="grape" displayName="Grape" />
65
- <ComboboxOption value="honeydew" displayName="Honeydew" />
66
- <ComboboxOption
67
- value="iceberg-lettuce"
68
- displayName="Iceberg lettuce"
69
- />
70
- <ComboboxOption
71
- value="pomegranate"
72
- displayName="Pomegranate"
73
- glyph={<Icon glyph="Warning" />}
74
- />
75
- <ComboboxGroup label="Peppers">
76
- <ComboboxOption value="cayenne" displayName="Cayenne" />
77
- <ComboboxOption value="ghost-pepper" displayName="Ghost pepper" />
78
- <ComboboxOption value="habanero" displayName="Habanero" />
79
- <ComboboxOption value="jalapeno" displayName="Jalapeño" />
80
- <ComboboxOption value="red-pepper" displayName="Red pepper" />
81
- <ComboboxOption value="scotch-bonnet" displayName="Scotch bonnet" />
82
- </ComboboxGroup>
83
- </Combobox>
84
- </Wrapper>
85
- );
86
- })
87
- .add('Multi Select', () => {
88
- const [isError, setIsError] = useState(false);
89
-
90
- const handleChange = (value: Array<string>) => {
91
- if (value.includes('pomegranate')) {
92
- setIsError(true);
93
- } else {
94
- setIsError(false);
95
- }
96
- };
97
-
98
- return (
99
- <Wrapper>
100
- <Combobox
101
- label="Choose some fruit"
102
- description="Pick as many as you want!"
103
- placeholder="Select fruit"
104
- initialValue={['apple', 'carrot', 'fig']}
105
- multiselect={true}
106
- overflow={select(
107
- 'Overflow',
108
- ['expand-y', 'expand-x', 'scroll-x'],
109
- 'expand-y',
110
- )}
111
- state={isError ? 'error' : 'none'}
112
- errorMessage="No Pomegranates!"
113
- onChange={handleChange}
114
- chipTruncationLocation={select(
115
- 'Chip Truncation Location',
116
- ['start', 'middle', 'end', 'none'],
117
- 'middle',
118
- )}
119
- >
120
- <ComboboxOption value="apple" displayName="Apple" />
121
- <ComboboxOption value="banana" displayName="Banana" />
122
- <ComboboxOption value="carrot" displayName="Carrot" />
123
- <ComboboxOption value="dragonfruit" displayName="Dragonfruit" />
124
- <ComboboxOption value="eggplant" displayName="Eggplant" />
125
- <ComboboxOption value="fig" displayName="Fig" />
126
- <ComboboxOption value="grape" displayName="Grape" />
127
- <ComboboxOption value="honeydew" displayName="Honeydew" />
128
- <ComboboxOption
129
- value="iceberg-lettuce"
130
- displayName="Iceberg lettuce"
131
- />
132
- <ComboboxOption
133
- value="pomegranate"
134
- displayName="Pomegranate"
135
- glyph={<Icon glyph="Warning" />}
136
- />
137
- <ComboboxGroup label="Peppers">
138
- <ComboboxOption value="cayenne" displayName="Cayenne" />
139
- <ComboboxOption value="ghost-pepper" displayName="Ghost pepper" />
140
- <ComboboxOption value="habanero" displayName="Habanero" />
141
- <ComboboxOption value="jalapeno" displayName="Jalapeño" />
142
- <ComboboxOption value="red-pepper" displayName="Red pepper" />
143
- <ComboboxOption value="scotch-bonnet" displayName="Scotch bonnet" />
144
- </ComboboxGroup>
145
- </Combobox>
146
- </Wrapper>
147
- );
148
- })
149
- .add('External filter', () => {
150
- const allOptions = [
151
- 'apple',
152
- 'banana',
153
- 'carrot',
154
- 'dragonfruit',
155
- 'eggplant',
156
- 'fig',
157
- 'grape',
158
- 'honeydew',
159
- 'iceberg-lettuce',
160
- 'jalapeño',
161
- ];
162
-
163
- const [filteredOptions, setOptions] = useState(['carrot', 'grape']);
164
-
165
- const handleFilter = (input: string) => {
166
- setOptions(allOptions.filter(option => option.includes(input)));
167
- };
168
-
169
- return (
170
- <Wrapper>
171
- <Combobox
172
- label="Choose some fruit"
173
- placeholder="Select fruit"
174
- initialValue={['apple', 'fig', 'raspberry']}
175
- multiselect={true}
176
- overflow={'expand-y'}
177
- onFilter={handleFilter}
178
- filteredOptions={filteredOptions}
179
- >
180
- {allOptions.map(option => (
181
- <ComboboxOption key={option} value={option} />
182
- ))}
183
- </Combobox>
184
- </Wrapper>
185
- );
186
- })
187
- .add('Empty', () => {
188
- return (
189
- <Wrapper>
190
- <Combobox
191
- multiselect={false}
192
- label="Choose a fruit"
193
- description="Please pick one"
194
- placeholder="Select fruit"
195
- ></Combobox>
196
- </Wrapper>
197
- );
198
- })
199
- .add('Controlled single select', () => {
200
- const [selection, setSelection] = useState<string | null>(null);
201
-
202
- const handleChange = (value: string | null) => {
203
- setSelection(value);
204
- };
205
-
206
- return (
207
- <Wrapper>
208
- <Combobox
209
- multiselect={false}
210
- label="Choose a fruit"
211
- description="Please pick one"
212
- placeholder="Select fruit"
213
- onChange={handleChange}
214
- value={selection}
215
- >
216
- <ComboboxOption value="apple" />
217
- <ComboboxOption value="banana" />
218
- <ComboboxOption value="carrot" />
219
- </Combobox>
220
- <Button onClick={() => setSelection('carrot')}>Select Carrot</Button>
221
- </Wrapper>
222
- );
223
- })
224
- .add('Controlled multiselect', () => {
225
- const [selection, setSelection] = useState([] as Array<string>);
226
-
227
- const handleChange = (value: Array<string>) => {
228
- setSelection(value);
229
- };
230
-
231
- return (
232
- <Wrapper>
233
- <Combobox
234
- multiselect={true}
235
- label="Choose a fruit"
236
- description="Please pick one"
237
- placeholder="Select fruit"
238
- onChange={handleChange}
239
- value={selection}
240
- >
241
- <ComboboxOption value="apple" />
242
- <ComboboxOption value="banana" />
243
- <ComboboxOption value="carrot" />
244
- </Combobox>
245
- <Button
246
- onClick={() =>
247
- setSelection(['apple', 'banana', 'carrot', 'raspberry'])
248
- }
249
- >
250
- Select all
251
- </Button>
252
- </Wrapper>
253
- );
254
- });
177
+ export const Basic = Template.bind({});
178
+ Basic.args = {
179
+ label: 'Choose a fruit',
180
+ description: 'Please pick one',
181
+ placeholder: 'Select fruit',
182
+ children: ComboboxOptions,
183
+ };
184
+
185
+ export const Empty = Template.bind({});
186
+ Empty.args = {
187
+ label: 'Choose a fruit',
188
+ description: 'Please pick one',
189
+ placeholder: 'Select fruit',
190
+ };
191
+
192
+ export const SingleSelect = Template.bind({});
193
+ SingleSelect.args = {
194
+ label: 'Choose a fruit',
195
+ description: 'Please pick one',
196
+ placeholder: 'Select fruit',
197
+ children: ComboboxOptions,
198
+ };
199
+
200
+ export const WithError = Template.bind({});
201
+ WithError.args = {
202
+ label: 'Choose a fruit',
203
+ description: 'Please pick one',
204
+ placeholder: 'Select fruit',
205
+ value: 'pomegranates',
206
+ errorMessage: 'No Pomegranates!',
207
+ state: 'error',
208
+ };
209
+
210
+ export const Multiselect = Template.bind({});
211
+ Multiselect.args = {
212
+ label: 'Choose a fruit',
213
+ description: 'Please pick one',
214
+ placeholder: 'Select fruit',
215
+ multiselect: true,
216
+ children: ComboboxOptions,
217
+ };
218
+
219
+ export const ControlledSingleSelect = () => {
220
+ const [selection, setSelection] = useState<string | null>(null);
221
+
222
+ const handleChange = (value: string | null) => {
223
+ setSelection(value);
224
+ };
225
+
226
+ return (
227
+ <>
228
+ <Combobox
229
+ multiselect={false}
230
+ label="Choose a fruit"
231
+ description="Please pick one"
232
+ placeholder="Select fruit"
233
+ onChange={handleChange}
234
+ value={selection}
235
+ >
236
+ <ComboboxOption value="apple" />
237
+ <ComboboxOption value="banana" />
238
+ <ComboboxOption value="carrot" />
239
+ </Combobox>
240
+ <Button onClick={() => setSelection('carrot')}>Select Carrot</Button>
241
+ </>
242
+ );
243
+ };
244
+
245
+ export const ExternalFilter = () => {
246
+ const allOptions = [
247
+ 'apple',
248
+ 'banana',
249
+ 'carrot',
250
+ 'dragonfruit',
251
+ 'eggplant',
252
+ 'fig',
253
+ 'grape',
254
+ 'honeydew',
255
+ 'iceberg-lettuce',
256
+ 'jalapeño',
257
+ ];
258
+
259
+ const [filteredOptions, setOptions] = useState(['carrot', 'grape']);
260
+
261
+ const handleFilter = (input: string) => {
262
+ setOptions(allOptions.filter(option => option.includes(input)));
263
+ };
264
+
265
+ return (
266
+ <Combobox
267
+ label="Choose some fruit"
268
+ placeholder="Select fruit"
269
+ initialValue={['apple', 'fig', 'raspberry']}
270
+ multiselect={true}
271
+ overflow={'expand-y'}
272
+ onFilter={handleFilter}
273
+ filteredOptions={filteredOptions}
274
+ >
275
+ {allOptions.map(option => (
276
+ <ComboboxOption key={option} value={option} />
277
+ ))}
278
+ </Combobox>
279
+ );
280
+ };