@leafygreen-ui/combobox 8.1.3 → 9.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.
@@ -39,17 +39,18 @@ export declare const Overflow: {
39
39
  readonly expandX: "expand-x";
40
40
  };
41
41
  export type Overflow = (typeof Overflow)[keyof typeof Overflow];
42
- /** The error state of the Combobox */
42
+ /** The error/valid state of the Combobox */
43
43
  export declare const State: {
44
- readonly none: "none";
45
- readonly error: "error";
44
+ readonly None: "none";
45
+ readonly Error: "error";
46
+ readonly Valid: "valid";
46
47
  };
47
48
  export type State = (typeof State)[keyof typeof State];
48
49
  /** The search state of the Combobox */
49
50
  export declare const SearchState: {
50
- readonly unset: "unset";
51
- readonly error: "error";
52
- readonly loading: "loading";
51
+ readonly Unset: "unset";
52
+ readonly Error: "error";
53
+ readonly Loading: "loading";
53
54
  };
54
55
  export type SearchState = (typeof SearchState)[keyof typeof SearchState];
55
56
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.types.d.ts","sourceRoot":"","sources":["../../src/types/Combobox.types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,eAAe;;;;;;;;CAQlB,CAAC;AACX,MAAM,MAAM,eAAe,GACzB,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAEzD;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;CAKf,CAAC;AACX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE5E;;GAEG;AACH,eAAO,MAAM,QAAQ;IACnB;;OAEG;;IAEH;;OAEG;;IAEH;;OAEG;;CAEK,CAAC;AACX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAC;AAEhE,sCAAsC;AACtC,eAAO,MAAM,KAAK;;;CAGR,CAAC;AACX,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,OAAO,KAAK,CAAC,CAAC;AAEvD,uCAAuC;AACvC,eAAO,MAAM,WAAW;;;;CAId,CAAC;AACX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,OAAO,IAAI,CAAC,SAAS,IAAI,GAC3D,KAAK,CAAC,MAAM,CAAC,GACb,MAAM,GAAG,IAAI,CAAC;AAElB,uFAAuF;AACvF,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC9B,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;CAC/B;AAED;;;;GAIG;AAEH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,OAAO,IAAI,CAAC,SAAS,IAAI,GACxD,CAAC,KAAK,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,UAAU,KAAK,IAAI,GACzD,CAAC,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;AAE5C;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,OAAO,EAChD,WAAW,EAAE,CAAC,GACb,eAAe,CAAC,CAAC,CAAC,CAMpB"}
1
+ {"version":3,"file":"Combobox.types.d.ts","sourceRoot":"","sources":["../../src/types/Combobox.types.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,eAAO,MAAM,eAAe;;;;;;;;CAQlB,CAAC;AACX,MAAM,MAAM,eAAe,GACzB,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAEzD;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;CAKf,CAAC;AACX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE5E;;GAEG;AACH,eAAO,MAAM,QAAQ;IACnB;;OAEG;;IAEH;;OAEG;;IAEH;;OAEG;;CAEK,CAAC;AACX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAC;AAEhE,4CAA4C;AAC5C,eAAO,MAAM,KAAK;;;;CAAiB,CAAC;AACpC,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,OAAO,KAAK,CAAC,CAAC;AAEvD,uCAAuC;AACvC,eAAO,MAAM,WAAW;;;;CAId,CAAC;AACX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,OAAO,IAAI,CAAC,SAAS,IAAI,GAC3D,KAAK,CAAC,MAAM,CAAC,GACb,MAAM,GAAG,IAAI,CAAC;AAElB,uFAAuF;AACvF,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC9B,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;CAC/B;AAED;;;;GAIG;AAEH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,OAAO,IAAI,CAAC,SAAS,IAAI,GACxD,CAAC,KAAK,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,UAAU,KAAK,IAAI,GACzD,CAAC,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;AAE5C;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,OAAO,EAChD,WAAW,EAAE,CAAC,GACb,eAAe,CAAC,CAAC,CAAC,CAMpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leafygreen-ui/combobox",
3
- "version": "8.1.3",
3
+ "version": "9.0.0",
4
4
  "description": "leafyGreen UI Kit Combobox",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -22,18 +22,19 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@leafygreen-ui/checkbox": "^13.0.0",
25
+ "@leafygreen-ui/checkbox": "^13.1.0",
26
26
  "@leafygreen-ui/chip": "^1.0.2",
27
27
  "@leafygreen-ui/emotion": "^4.0.8",
28
+ "@leafygreen-ui/form-field": "^1.2.0",
28
29
  "@leafygreen-ui/hooks": "^8.1.3",
29
30
  "@leafygreen-ui/icon": "^12.0.1",
30
31
  "@leafygreen-ui/icon-button": "^15.0.21",
31
- "@leafygreen-ui/input-option": "^1.1.2",
32
+ "@leafygreen-ui/input-option": "^1.1.3",
32
33
  "@leafygreen-ui/lib": "^13.3.0",
33
34
  "@leafygreen-ui/palette": "^4.0.9",
34
35
  "@leafygreen-ui/popover": "^11.3.1",
35
- "@leafygreen-ui/tokens": "^2.5.2",
36
- "@leafygreen-ui/typography": "^18.3.0",
36
+ "@leafygreen-ui/tokens": "^2.6.0",
37
+ "@leafygreen-ui/typography": "^19.1.0",
37
38
  "chalk": "^4.1.2",
38
39
  "lodash": "^4.17.21",
39
40
  "polished": "^4.2.2"
@@ -42,9 +43,9 @@
42
43
  "@leafygreen-ui/leafygreen-provider": "^3.1.12"
43
44
  },
44
45
  "devDependencies": {
45
- "@leafygreen-ui/button": "^21.1.0",
46
+ "@leafygreen-ui/button": "^21.2.0",
46
47
  "@leafygreen-ui/testing-lib": "^0.4.2",
47
- "@lg-tools/storybook-utils": "^0.1.0"
48
+ "@lg-tools/storybook-utils": "^0.1.1"
48
49
  },
49
50
  "homepage": "https://github.com/mongodb/leafygreen-ui/tree/main/packages/combobox",
50
51
  "repository": {
@@ -344,6 +344,15 @@ describe('packages/combobox', () => {
344
344
  });
345
345
 
346
346
  describe('When disabled', () => {
347
+ test(`Input element renders with aria-disabled and readonly attributes but not disabled attribute when disabled prop is set`, () => {
348
+ const { inputEl } = renderCombobox(select, {
349
+ disabled: true,
350
+ });
351
+ expect(inputEl.getAttribute('aria-disabled')).toBeTruthy();
352
+ expect(inputEl.hasAttribute('readonly')).toBeTruthy();
353
+ expect(inputEl.getAttribute('disabled')).toBeFalsy();
354
+ });
355
+
347
356
  // disabled prop
348
357
  test('Combobox is not clickable when `disabled`', () => {
349
358
  const { comboboxEl } = renderCombobox(select, { disabled: true });
@@ -4,6 +4,7 @@ import { css } from '@leafygreen-ui/emotion';
4
4
  import { Theme } from '@leafygreen-ui/lib';
5
5
  import { palette } from '@leafygreen-ui/palette';
6
6
  import {
7
+ color,
7
8
  focusRing,
8
9
  fontFamilies,
9
10
  spacing,
@@ -17,7 +18,7 @@ import {
17
18
  fontSize,
18
19
  lineHeight,
19
20
  } from '../ComboboxChip/ComboboxChip.styles';
20
- import { ComboboxSize as Size, Overflow } from '../types';
21
+ import { ComboboxSize as Size, Overflow, State } from '../types';
21
22
 
22
23
  /**
23
24
  * Util to get the chip height
@@ -29,7 +30,7 @@ const inputHeight = (size: Size) => {
29
30
  };
30
31
 
31
32
  // Gap between each chip
32
- const flexGap = 4;
33
+ const flexGap = spacing[100];
33
34
 
34
35
  /**
35
36
  * The min-height of the combobox.
@@ -64,27 +65,27 @@ export const comboboxPadding: Record<
64
65
  > = {
65
66
  [Size.XSmall]: {
66
67
  y: getYPadding(Size.XSmall),
67
- xLeftWithChip: 1,
68
- xLeftWithoutChip: 10,
69
- xRight: 4,
68
+ xLeftWithChip: spacing[25],
69
+ xLeftWithoutChip: spacing[200],
70
+ xRight: spacing[100],
70
71
  },
71
72
  [Size.Small]: {
72
73
  y: getYPadding(Size.Small),
73
- xLeftWithChip: 4,
74
- xLeftWithoutChip: 10,
75
- xRight: 8,
74
+ xLeftWithChip: spacing[100],
75
+ xLeftWithoutChip: spacing[200],
76
+ xRight: spacing[100],
76
77
  },
77
78
  [Size.Default]: {
78
79
  y: getYPadding(Size.Default),
79
- xLeftWithChip: 6,
80
- xLeftWithoutChip: 12,
81
- xRight: 12,
80
+ xLeftWithChip: spacing[150],
81
+ xLeftWithoutChip: spacing[300],
82
+ xRight: spacing[200],
82
83
  },
83
84
  [Size.Large]: {
84
85
  y: getYPadding(Size.Large),
85
- xLeftWithChip: spacing[2] - 1,
86
- xLeftWithoutChip: spacing[2] - 1,
87
- xRight: spacing[2] - 1,
86
+ xLeftWithChip: spacing[300],
87
+ xLeftWithoutChip: spacing[300],
88
+ xRight: spacing[200],
88
89
  },
89
90
  };
90
91
 
@@ -92,7 +93,7 @@ export const comboboxPadding: Record<
92
93
  export const clearButtonIconSize = 28;
93
94
 
94
95
  /** Width of the dropdown caret icon (in px) */
95
- export const caretIconSize = spacing[3];
96
+ export const caretIconSize = spacing[400];
96
97
 
97
98
  export const comboboxParentStyle = (size: Size): string => {
98
99
  return css`
@@ -108,7 +109,7 @@ export const comboboxParentStyle = (size: Size): string => {
108
109
  export const baseComboboxStyles = css`
109
110
  display: flex;
110
111
  align-items: center;
111
- gap: ${spacing[2]}px;
112
+ gap: ${spacing[200]}px;
112
113
  cursor: text;
113
114
  transition: ${transitionDuration.default}ms ease-in-out;
114
115
  transition-property: background-color, box-shadow, border-color;
@@ -137,14 +138,12 @@ export const baseComboboxStyles = css`
137
138
 
138
139
  export const comboboxThemeStyles: Record<Theme, string> = {
139
140
  [Theme.Light]: css`
140
- color: ${palette.gray.dark3};
141
- background-color: ${palette.white};
142
- border-color: ${palette.gray.base};
141
+ color: ${color.light.text.primary.default};
142
+ background-color: ${color.light.background.primary.default};
143
143
  `,
144
144
  [Theme.Dark]: css`
145
- color: ${palette.gray.light2};
145
+ color: ${color.dark.text.primary.default};
146
146
  background-color: ${palette.gray.dark4};
147
- border-color: ${palette.gray.base};
148
147
  `,
149
148
  };
150
149
 
@@ -160,29 +159,24 @@ export const comboboxSizeStyles = (
160
159
  padding-right: ${comboboxPadding[size].xRight}px;
161
160
  `;
162
161
 
163
- export const comboboxDisabledStyles: Record<Theme, string> = {
164
- [Theme.Light]: css`
165
- cursor: not-allowed;
166
- color: ${palette.gray.dark1};
167
- background-color: ${palette.gray.light2};
168
- border-color: ${palette.gray.light1};
169
- `,
170
- [Theme.Dark]: css`
171
- cursor: not-allowed;
172
- color: ${palette.gray.dark1};
173
- background-color: ${palette.gray.dark3};
174
- border-color: ${palette.gray.dark2};
175
- `,
176
- };
162
+ export const getComboboxDisabledStyles = (theme: Theme) => css`
163
+ cursor: not-allowed;
164
+ color: ${color[theme].text.disabled.default};
165
+ background-color: ${color[theme].background.disabled.default};
166
+ border-color: ${color[theme].border.disabled.default};
167
+ `;
177
168
 
178
- export const comboboxErrorStyles: Record<Theme, string> = {
179
- [Theme.Light]: css`
180
- border-color: ${palette.red.base};
169
+ export const getComboboxStateStyles = (theme: Theme) => ({
170
+ [State.Error]: css`
171
+ border-color: ${color[theme].border.error.default};
181
172
  `,
182
- [Theme.Dark]: css`
183
- border-color: ${palette.red.light1};
173
+ [State.None]: css`
174
+ border-color: ${color[theme].border.primary.default};
184
175
  `,
185
- };
176
+ [State.Valid]: css`
177
+ border-color: ${color[theme].border.success.default};
178
+ `,
179
+ });
186
180
 
187
181
  export const comboboxFocusStyle: Record<Theme, string> = {
188
182
  [Theme.Light]: css`
@@ -206,16 +200,16 @@ export const iconsWrapperBaseStyles = css`
206
200
 
207
201
  export const iconsWrapperSizeStyles: Record<Size, string> = {
208
202
  [Size.XSmall]: css`
209
- gap: ${spacing[1]}px;
203
+ gap: ${spacing[100]}px;
210
204
  `,
211
205
  [Size.Small]: css`
212
- gap: ${spacing[2]}px;
206
+ gap: ${spacing[200]}px;
213
207
  `,
214
208
  [Size.Default]: css`
215
- gap: ${spacing[2]}px;
209
+ gap: ${spacing[200]}px;
216
210
  `,
217
211
  [Size.Large]: css`
218
- gap: ${spacing[2]}px;
212
+ gap: ${spacing[200]}px;
219
213
  `,
220
214
  };
221
215
 
@@ -291,7 +285,7 @@ export const baseInputElementStyle = css`
291
285
 
292
286
  // Only add padding if there are chips
293
287
  &:not(:first-child) {
294
- padding-left: ${spacing[1]}px;
288
+ padding-left: ${spacing[100]}px;
295
289
  }
296
290
 
297
291
  &:placeholder-shown {
@@ -305,12 +299,12 @@ export const baseInputElementStyle = css`
305
299
  export const inputElementThemeStyle: Record<Theme, string> = {
306
300
  [Theme.Light]: css`
307
301
  &::placeholder {
308
- color: ${palette.gray.dark1};
302
+ color: ${palette.gray.base};
309
303
  }
310
304
  `,
311
305
  [Theme.Dark]: css`
312
306
  &::placeholder {
313
- color: ${palette.gray.light1};
307
+ color: ${palette.gray.dark1};
314
308
  }
315
309
  `,
316
310
  };
@@ -318,7 +312,7 @@ export const inputElementThemeStyle: Record<Theme, string> = {
318
312
  export const inputElementDisabledThemeStyle: Record<Theme, string> = {
319
313
  [Theme.Light]: css`
320
314
  &::placeholder {
321
- color: ${palette.gray.dark1};
315
+ color: ${palette.gray.base};
322
316
  }
323
317
  `,
324
318
  [Theme.Dark]: css`
@@ -362,28 +356,13 @@ export const clearButtonStyle = css`
362
356
  margin-inline: -6px;
363
357
  `;
364
358
 
365
- export const endIconStyle = css`
359
+ export const iconStyle = css`
366
360
  height: ${caretIconSize}px;
367
361
  width: ${caretIconSize}px;
368
362
  `;
369
363
 
370
- export const errorMessageThemeStyle: Record<Theme, string> = {
371
- [Theme.Light]: css`
372
- color: ${palette.red.base};
373
- `,
374
- [Theme.Dark]: css`
375
- color: ${palette.red.light1};
376
- `,
377
- };
378
-
379
- export const errorMessageSizeStyle = (size: Size) => css`
380
- font-size: ${fontSize[size]}px;
381
- line-height: ${lineHeight[size]}px;
382
- padding-top: ${comboboxPadding[size].y}px;
383
- `;
384
-
385
364
  export const labelDescriptionContainerStyle = css`
386
- margin-bottom: ${spacing[1]}px;
365
+ margin-bottom: ${spacing[100]}px;
387
366
  display: flex;
388
367
  flex-direction: column;
389
368
  `;
@@ -407,17 +386,8 @@ export const comboboxOverflowShadowStyles: Record<Theme, string> = {
407
386
  `,
408
387
  };
409
388
 
410
- export const errorIconThemeStyles: Record<Theme, string> = {
411
- [Theme.Light]: palette.red.base,
412
- [Theme.Dark]: palette.red.light1,
413
- };
389
+ export const getCaretIconFill = (theme: Theme) =>
390
+ color[theme].icon.primary.default;
414
391
 
415
- export const caretIconThemeStyles: Record<Theme, string> = {
416
- [Theme.Light]: palette.gray.dark2,
417
- [Theme.Dark]: palette.gray.light1,
418
- };
419
-
420
- export const caretIconDisabledStyles: Record<Theme, string> = {
421
- [Theme.Light]: palette.gray.base,
422
- [Theme.Dark]: palette.gray.dark1,
423
- };
392
+ export const getCaretIconDisabledFill = (theme: Theme) =>
393
+ color[theme].icon.disabled.default;
@@ -21,6 +21,7 @@ import isUndefined from 'lodash/isUndefined';
21
21
  import PropTypes from 'prop-types';
22
22
 
23
23
  import { cx } from '@leafygreen-ui/emotion';
24
+ import { DEFAULT_MESSAGES, FormFieldFeedback } from '@leafygreen-ui/form-field';
24
25
  import {
25
26
  useAutoScroll,
26
27
  useBackdropClick,
@@ -68,20 +69,17 @@ import { isValueCurrentSelection } from './utils/isValueCurrentSelection';
68
69
  import {
69
70
  baseComboboxStyles,
70
71
  baseInputElementStyle,
71
- caretIconDisabledStyles,
72
- caretIconThemeStyles,
73
72
  clearButtonStyle,
74
- comboboxDisabledStyles,
75
- comboboxErrorStyles,
76
73
  comboboxFocusStyle,
77
74
  comboboxOverflowShadowStyles,
78
75
  comboboxParentStyle,
79
76
  comboboxSizeStyles,
80
77
  comboboxThemeStyles,
81
- endIconStyle,
82
- errorIconThemeStyles,
83
- errorMessageSizeStyle,
84
- errorMessageThemeStyle,
78
+ getCaretIconDisabledFill,
79
+ getCaretIconFill,
80
+ getComboboxDisabledStyles,
81
+ getComboboxStateStyles,
82
+ iconStyle,
85
83
  iconsWrapperBaseStyles,
86
84
  iconsWrapperSizeStyles,
87
85
  inputElementDisabledThemeStyle,
@@ -110,7 +108,8 @@ export function Combobox<M extends boolean>({
110
108
  size = ComboboxSize.Default,
111
109
  darkMode: darkModeProp,
112
110
  state = 'none',
113
- errorMessage,
111
+ errorMessage = DEFAULT_MESSAGES.error,
112
+ successMessage = DEFAULT_MESSAGES.success,
114
113
  searchState = 'unset',
115
114
  searchEmptyMessage = 'No results found',
116
115
  searchErrorMessage = 'Could not get results!',
@@ -1166,6 +1165,14 @@ export function Combobox<M extends boolean>({
1166
1165
  : { usePortal }),
1167
1166
  } as const;
1168
1167
 
1168
+ const formFieldFeedbackProps = {
1169
+ disabled,
1170
+ errorMessage,
1171
+ size,
1172
+ state,
1173
+ successMessage,
1174
+ } as const;
1175
+
1169
1176
  return (
1170
1177
  <LeafyGreenProvider darkMode={darkMode}>
1171
1178
  <ComboboxContext.Provider
@@ -1232,12 +1239,12 @@ export function Combobox<M extends boolean>({
1232
1239
  baseComboboxStyles,
1233
1240
  comboboxThemeStyles[theme],
1234
1241
  comboboxSizeStyles(size, isMultiselectWithSelections),
1242
+ getComboboxStateStyles(theme)[state],
1235
1243
  {
1236
- [comboboxDisabledStyles[theme]]: disabled,
1237
- [comboboxErrorStyles[theme]]: state === State.error,
1238
1244
  [comboboxFocusStyle[theme]]: isElementFocused(
1239
1245
  ComboboxElement.Input,
1240
1246
  ),
1247
+ [getComboboxDisabledStyles(theme)]: disabled,
1241
1248
  [comboboxOverflowShadowStyles[theme]]: shouldShowOverflowShadow,
1242
1249
  },
1243
1250
  )}
@@ -1270,7 +1277,8 @@ export function Combobox<M extends boolean>({
1270
1277
  },
1271
1278
  )}
1272
1279
  placeholder={placeholderValue}
1273
- disabled={disabled ?? undefined}
1280
+ aria-disabled={disabled}
1281
+ readOnly={disabled}
1274
1282
  onChange={handleInputChange}
1275
1283
  value={inputValue}
1276
1284
  autoComplete="off"
@@ -1282,17 +1290,9 @@ export function Combobox<M extends boolean>({
1282
1290
  iconsWrapperSizeStyles[size],
1283
1291
  )}
1284
1292
  >
1285
- {state === 'error' && (
1286
- <Icon
1287
- glyph="Warning"
1288
- fill={errorIconThemeStyles[theme]}
1289
- className={endIconStyle}
1290
- />
1291
- )}
1292
1293
  {clearable && doesSelectionExist(selection) && !disabled && (
1293
1294
  <IconButton
1294
1295
  aria-label="Clear selection"
1295
- aria-disabled={disabled}
1296
1296
  disabled={disabled}
1297
1297
  ref={clearButtonRef}
1298
1298
  onClick={handleClearButtonClick}
@@ -1305,25 +1305,15 @@ export function Combobox<M extends boolean>({
1305
1305
  )}
1306
1306
  <Icon
1307
1307
  glyph="CaretDown"
1308
- className={endIconStyle}
1308
+ className={iconStyle}
1309
1309
  fill={cx({
1310
- [caretIconThemeStyles[theme]]: !disabled,
1311
- [caretIconDisabledStyles[theme]]: disabled,
1310
+ [getCaretIconFill(theme)]: !disabled,
1311
+ [getCaretIconDisabledFill(theme)]: disabled,
1312
1312
  })}
1313
1313
  />
1314
1314
  </div>
1315
1315
  </div>
1316
-
1317
- {state === 'error' && errorMessage && (
1318
- <div
1319
- className={cx(
1320
- errorMessageThemeStyle[theme],
1321
- errorMessageSizeStyle(size),
1322
- )}
1323
- >
1324
- {errorMessage}
1325
- </div>
1326
- )}
1316
+ <FormFieldFeedback {...formFieldFeedbackProps} />
1327
1317
 
1328
1318
  {/******* /
1329
1319
  * Menu *
@@ -110,7 +110,12 @@ export type BaseComboboxProps = Omit<HTMLElementProps<'div'>, 'onChange'> &
110
110
  /**
111
111
  * The message shown below the input when state is `error`
112
112
  */
113
- errorMessage?: string;
113
+ errorMessage?: ReactNode;
114
+
115
+ /**
116
+ * The message shown below the input when state is `valid`
117
+ */
118
+ successMessage?: ReactNode;
114
119
 
115
120
  /**
116
121
  * The state of search results. Toggles search messages within the menu.
@@ -118,11 +118,11 @@ const meta: StoryMetaType<typeof Combobox> = {
118
118
  },
119
119
  searchErrorMessage: {
120
120
  control: 'text',
121
- if: { arg: 'searchState', eq: SearchState.error },
121
+ if: { arg: 'searchState', eq: SearchState.Error },
122
122
  },
123
123
  searchLoadingMessage: {
124
124
  control: 'text',
125
- if: { arg: 'searchState', eq: SearchState.loading },
125
+ if: { arg: 'searchState', eq: SearchState.Loading },
126
126
  },
127
127
  chipTruncationLocation: {
128
128
  options: Object.values(TruncationLocation),
@@ -29,8 +29,8 @@ export const defaultContext = {
29
29
  withIcons: false,
30
30
  disabled: false,
31
31
  isOpen: false,
32
- state: State.none,
33
- searchState: SearchState.unset,
32
+ state: State.None,
33
+ searchState: SearchState.Unset,
34
34
  overflow: Overflow.expandY,
35
35
  };
36
36
 
@@ -1,3 +1,5 @@
1
+ import { FormFieldState } from '@leafygreen-ui/form-field';
2
+
1
3
  /**
2
4
  * Identifier for individual component elements within the Combobox
3
5
  */
@@ -43,18 +45,15 @@ export const Overflow = {
43
45
  } as const;
44
46
  export type Overflow = (typeof Overflow)[keyof typeof Overflow];
45
47
 
46
- /** The error state of the Combobox */
47
- export const State = {
48
- none: 'none',
49
- error: 'error',
50
- } as const;
48
+ /** The error/valid state of the Combobox */
49
+ export const State = FormFieldState;
51
50
  export type State = (typeof State)[keyof typeof State];
52
51
 
53
52
  /** The search state of the Combobox */
54
53
  export const SearchState = {
55
- unset: 'unset',
56
- error: 'error',
57
- loading: 'loading',
54
+ Unset: 'unset',
55
+ Error: 'error',
56
+ Loading: 'loading',
58
57
  } as const;
59
58
  export type SearchState = (typeof SearchState)[keyof typeof SearchState];
60
59