@leafygreen-ui/combobox 5.0.8 → 5.0.10

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 (79) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/{Chip.d.ts → Chip/Chip.d.ts} +1 -1
  3. package/dist/Chip/Chip.d.ts.map +1 -0
  4. package/dist/Chip/Chip.styles.d.ts +23 -0
  5. package/dist/Chip/Chip.styles.d.ts.map +1 -0
  6. package/dist/Chip/index.d.ts +2 -0
  7. package/dist/Chip/index.d.ts.map +1 -0
  8. package/dist/{Combobox.d.ts → Combobox/Combobox.d.ts} +1 -1
  9. package/dist/Combobox/Combobox.d.ts.map +1 -0
  10. package/dist/{Combobox.styles.d.ts → Combobox/Combobox.styles.d.ts} +1 -12
  11. package/dist/Combobox/Combobox.styles.d.ts.map +1 -0
  12. package/dist/Combobox/index.d.ts +2 -0
  13. package/dist/Combobox/index.d.ts.map +1 -0
  14. package/dist/{ComboboxContext.d.ts → ComboboxContext/ComboboxContext.d.ts} +1 -4
  15. package/dist/ComboboxContext/ComboboxContext.d.ts.map +1 -0
  16. package/dist/ComboboxContext/index.d.ts +2 -0
  17. package/dist/ComboboxContext/index.d.ts.map +1 -0
  18. package/dist/{ComboboxGroup.d.ts → ComboboxGroup/ComboboxGroup.d.ts} +3 -4
  19. package/dist/ComboboxGroup/ComboboxGroup.d.ts.map +1 -0
  20. package/dist/ComboboxGroup/ComboboxGroup.styles.d.ts +5 -0
  21. package/dist/ComboboxGroup/ComboboxGroup.styles.d.ts.map +1 -0
  22. package/dist/ComboboxGroup/index.d.ts +2 -0
  23. package/dist/ComboboxGroup/index.d.ts.map +1 -0
  24. package/dist/ComboboxMenu/ComboboxMenu.d.ts.map +1 -1
  25. package/dist/ComboboxMenu/index.d.ts +2 -0
  26. package/dist/ComboboxMenu/index.d.ts.map +1 -0
  27. package/dist/{ComboboxOption.d.ts → ComboboxOption/ComboboxOption.d.ts} +5 -4
  28. package/dist/ComboboxOption/ComboboxOption.d.ts.map +1 -0
  29. package/dist/ComboboxOption/ComboboxOption.styles.d.ts +11 -0
  30. package/dist/ComboboxOption/ComboboxOption.styles.d.ts.map +1 -0
  31. package/dist/ComboboxOption/OptionContent.d.ts +4 -0
  32. package/dist/ComboboxOption/OptionContent.d.ts.map +1 -0
  33. package/dist/ComboboxOption/index.d.ts +2 -0
  34. package/dist/ComboboxOption/index.d.ts.map +1 -0
  35. package/dist/esm/index.js +1 -1
  36. package/dist/esm/index.js.map +1 -1
  37. package/dist/index.d.ts +1 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/{ComboboxTestUtils.d.ts → utils/ComboboxTestUtils.d.ts} +1 -1
  42. package/dist/utils/ComboboxTestUtils.d.ts.map +1 -0
  43. package/package.json +11 -10
  44. package/src/Chip/Chip.styles.ts +150 -0
  45. package/src/{Chip.tsx → Chip/Chip.tsx} +18 -138
  46. package/src/Chip/index.ts +1 -0
  47. package/src/{Combobox.spec.tsx → Combobox/Combobox.spec.tsx} +2 -2
  48. package/src/{Combobox.story.tsx → Combobox/Combobox.story.tsx} +3 -3
  49. package/src/{Combobox.styles.ts → Combobox/Combobox.styles.ts} +5 -19
  50. package/src/{Combobox.tsx → Combobox/Combobox.tsx} +192 -257
  51. package/src/Combobox/index.ts +1 -0
  52. package/src/{ComboboxContext.tsx → ComboboxContext/ComboboxContext.tsx} +1 -7
  53. package/src/ComboboxContext/index.ts +1 -0
  54. package/src/ComboboxGroup/ComboboxGroup.styles.ts +34 -0
  55. package/src/ComboboxGroup/ComboboxGroup.tsx +53 -0
  56. package/src/ComboboxGroup/index.ts +1 -0
  57. package/src/ComboboxMenu/ComboboxMenu.tsx +3 -2
  58. package/src/ComboboxMenu/index.ts +1 -0
  59. package/src/ComboboxOption/ComboboxOption.styles.ts +68 -0
  60. package/src/ComboboxOption/ComboboxOption.tsx +107 -0
  61. package/src/ComboboxOption/OptionContent.tsx +87 -0
  62. package/src/ComboboxOption/index.ts +1 -0
  63. package/src/index.ts +1 -1
  64. package/src/{ComboboxTestUtils.tsx → utils/ComboboxTestUtils.tsx} +2 -2
  65. package/tsconfig.json +3 -0
  66. package/tsconfig.tsbuildinfo +1 -1
  67. package/tsdoc.json +540 -3
  68. package/dist/Chip.d.ts.map +0 -1
  69. package/dist/Combobox.d.ts.map +0 -1
  70. package/dist/Combobox.styles.d.ts.map +0 -1
  71. package/dist/ComboboxContext.d.ts.map +0 -1
  72. package/dist/ComboboxGroup.d.ts.map +0 -1
  73. package/dist/ComboboxOption.d.ts.map +0 -1
  74. package/dist/ComboboxTestUtils.d.ts.map +0 -1
  75. package/dist/Menu.styles.d.ts +0 -22
  76. package/dist/Menu.styles.d.ts.map +0 -1
  77. package/src/ComboboxGroup.tsx +0 -80
  78. package/src/ComboboxOption.tsx +0 -329
  79. package/src/Menu.styles.ts +0 -112
@@ -1,4 +1,9 @@
1
1
  import React, {
2
+ ChangeEventHandler,
3
+ FocusEventHandler,
4
+ KeyboardEventHandler,
5
+ MouseEventHandler,
6
+ TransitionEventHandler,
2
7
  useCallback,
3
8
  useEffect,
4
9
  useMemo,
@@ -15,20 +20,46 @@ import PropTypes from 'prop-types';
15
20
 
16
21
  import { cx } from '@leafygreen-ui/emotion';
17
22
  import {
23
+ useBackdropClick,
18
24
  useDynamicRefs,
19
- useEventListener,
20
25
  useIdAllocator,
21
26
  usePrevious,
22
27
  } from '@leafygreen-ui/hooks';
23
28
  import Icon from '@leafygreen-ui/icon';
24
29
  import IconButton from '@leafygreen-ui/icon-button';
25
- import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
30
+ import LeafyGreenProvider, {
31
+ useDarkMode,
32
+ } from '@leafygreen-ui/leafygreen-provider';
26
33
  import { consoleOnce, isComponentType, keyMap } from '@leafygreen-ui/lib';
27
34
  import { palette } from '@leafygreen-ui/palette';
28
35
  import { Description, Label } from '@leafygreen-ui/typography';
29
36
 
30
- import { ComboboxMenu } from './ComboboxMenu/ComboboxMenu';
31
- import { Chip } from './Chip';
37
+ import { Chip } from '../Chip';
38
+ import {
39
+ ComboboxElement,
40
+ ComboboxProps,
41
+ ComboboxSize,
42
+ getNullSelection,
43
+ onChangeType,
44
+ OptionObject,
45
+ Overflow,
46
+ SearchState,
47
+ SelectValueType,
48
+ State,
49
+ TruncationLocation,
50
+ } from '../Combobox.types';
51
+ import { ComboboxContext } from '../ComboboxContext';
52
+ import { InternalComboboxGroup } from '../ComboboxGroup';
53
+ import { ComboboxMenu } from '../ComboboxMenu';
54
+ import { InternalComboboxOption } from '../ComboboxOption';
55
+ import {
56
+ flattenChildren,
57
+ getDisplayNameForValue,
58
+ getNameAndValue,
59
+ getOptionObjectFromValue,
60
+ getValueForDisplayName,
61
+ } from '../utils';
62
+
32
63
  import {
33
64
  baseComboboxStyles,
34
65
  baseInputElementStyle,
@@ -50,29 +81,6 @@ import {
50
81
  labelDescriptionContainerStyle,
51
82
  multiselectInputElementStyle,
52
83
  } from './Combobox.styles';
53
- import {
54
- ComboboxElement,
55
- ComboboxProps,
56
- ComboboxSize,
57
- getNullSelection,
58
- onChangeType,
59
- OptionObject,
60
- Overflow,
61
- SearchState,
62
- SelectValueType,
63
- State,
64
- TruncationLocation,
65
- } from './Combobox.types';
66
- import { ComboboxContext } from './ComboboxContext';
67
- import { InternalComboboxGroup } from './ComboboxGroup';
68
- import { InternalComboboxOption } from './ComboboxOption';
69
- import {
70
- flattenChildren,
71
- getDisplayNameForValue,
72
- getNameAndValue,
73
- getOptionObjectFromValue,
74
- getValueForDisplayName,
75
- } from './utils';
76
84
 
77
85
  /**
78
86
  * Combobox is a combination of a Select and TextInput,
@@ -748,64 +756,6 @@ export function Combobox<M extends boolean>({
748
756
  updateFocusedChip,
749
757
  ]);
750
758
 
751
- /**
752
- * The rendered JSX for the input icons (clear, warn & caret)
753
- */
754
- const renderedInputIcons = useMemo(() => {
755
- const handleClearButtonClick = (
756
- e: React.MouseEvent<HTMLButtonElement, MouseEvent>,
757
- ) => {
758
- if (!disabled) {
759
- // Prevents triggering the setOpen function called by clicking anywhere within the input wrapper.
760
- e.stopPropagation();
761
- updateSelection(null);
762
- onClear?.(e);
763
- onFilter?.('');
764
- setInputFocus();
765
- }
766
- };
767
-
768
- return (
769
- <>
770
- {clearable && doesSelectionExist && (
771
- <IconButton
772
- aria-label="Clear selection"
773
- aria-disabled={disabled}
774
- disabled={disabled}
775
- ref={clearButtonRef}
776
- onClick={handleClearButtonClick}
777
- onFocus={handleClearButtonFocus}
778
- className={cx(clearButtonStyle)}
779
- darkMode={darkMode}
780
- >
781
- <Icon glyph="XWithCircle" />
782
- </IconButton>
783
- )}
784
- {state === 'error' ? (
785
- <Icon
786
- glyph="Warning"
787
- color={darkMode ? palette.red.light1 : palette.red.base}
788
- className={endIconStyle(size)}
789
- />
790
- ) : (
791
- <Icon glyph="CaretDown" className={endIconStyle(size)} />
792
- )}
793
- </>
794
- );
795
- }, [
796
- clearable,
797
- doesSelectionExist,
798
- disabled,
799
- state,
800
- darkMode,
801
- size,
802
- updateSelection,
803
- onClear,
804
- onFilter,
805
- isOpen,
806
- setInputFocus,
807
- ]);
808
-
809
759
  /**
810
760
  * Flag to determine whether the rendered options have icons
811
761
  */
@@ -937,26 +887,26 @@ export function Combobox<M extends boolean>({
937
887
  setMenuWidth(comboboxRef.current?.clientWidth ?? 0);
938
888
  }, [comboboxRef, isOpen, highlightedOption, selection]);
939
889
 
940
- // Handler fired when the menu has finished transitioning in/out
941
- const handleTransitionEnd = () => {
942
- setMenuWidth(comboboxRef.current?.clientWidth ?? 0);
943
- };
944
-
945
890
  /**
946
891
  *
947
892
  * Event Handlers
948
893
  *
949
894
  */
950
895
 
896
+ // Handler fired when the menu has finished transitioning in/out
897
+ const handleTransitionEnd: TransitionEventHandler<HTMLDivElement> = () => {
898
+ setMenuWidth(comboboxRef.current?.clientWidth ?? 0);
899
+ };
900
+
951
901
  // Prevent combobox from gaining focus by default
952
- const handleInputWrapperMousedown = (e: React.MouseEvent) => {
902
+ const handleInputWrapperMousedown: MouseEventHandler<HTMLDivElement> = e => {
953
903
  if (disabled) {
954
904
  e.preventDefault();
955
905
  }
956
906
  };
957
907
 
958
908
  // Set focus to the input element on click
959
- const handleComboboxClick = (e: React.MouseEvent) => {
909
+ const handleComboboxClick: MouseEventHandler<HTMLDivElement> = e => {
960
910
  // If we clicked the wrapper, not the input itself.
961
911
  // (Focus is set automatically if the click is on the input)
962
912
  if (e.target !== inputRef.current) {
@@ -978,13 +928,13 @@ export function Combobox<M extends boolean>({
978
928
 
979
929
  // Fired whenever the wrapper gains focus,
980
930
  // and any time the focus within changes
981
- const handleComboboxFocus = (e: React.FocusEvent) => {
931
+ const handleComboboxFocus: FocusEventHandler<HTMLDivElement> = e => {
982
932
  scrollInputToEnd();
983
933
  trackFocusedElement(getNameFromElement(e.target));
984
934
  };
985
935
 
986
936
  // Fired onChange
987
- const handleInputChange = ({
937
+ const handleInputChange: ChangeEventHandler<HTMLInputElement> = ({
988
938
  target: { value },
989
939
  }: React.ChangeEvent<HTMLInputElement>) => {
990
940
  setInputValue(value);
@@ -992,11 +942,22 @@ export function Combobox<M extends boolean>({
992
942
  onFilter?.(value);
993
943
  };
994
944
 
995
- const handleClearButtonFocus = () => {
945
+ const handleClearButtonFocus: FocusEventHandler<HTMLButtonElement> = () => {
996
946
  setHighlightedOption(null);
997
947
  };
998
948
 
999
- const handleKeyDown = (event: React.KeyboardEvent) => {
949
+ const handleClearButtonClick: MouseEventHandler<HTMLButtonElement> = e => {
950
+ if (!disabled) {
951
+ // Prevents triggering the setOpen function called by clicking anywhere within the input wrapper.
952
+ e.stopPropagation();
953
+ updateSelection(null);
954
+ onClear?.(e);
955
+ onFilter?.('');
956
+ setInputFocus();
957
+ }
958
+ };
959
+
960
+ const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = event => {
1000
961
  const isFocusInMenu = menuRef.current?.contains(document.activeElement);
1001
962
  const isFocusOnCombobox = comboboxRef.current?.contains(
1002
963
  document.activeElement,
@@ -1137,44 +1098,7 @@ export function Combobox<M extends boolean>({
1137
1098
  *
1138
1099
  */
1139
1100
 
1140
- // TODO: Replace this with `useBackdropClick`
1141
- /**
1142
- * We add two event handlers to the document to handle the backdrop click behavior.
1143
- * Intended behavior is to close the menu, and keep focus on the Combobox.
1144
- * No other click event handlers should fire on backdrop click
1145
- *
1146
- * 1. Mousedown event fires
1147
- * 2. We prevent `mousedown`'s default behavior, to prevent focus from being applied to the body (or other target)
1148
- * 3. Click event fires
1149
- * 4. We handle this event on _capture_, and stop propagation before the `click` event propagates all the way to any other element.
1150
- * This ensures that even if we click on a button, that handler is not fired
1151
- * 5. Then we call `closeMenu`, setting `isOpen = false`, and rerender the component
1152
- */
1153
- useEventListener(
1154
- 'mousedown',
1155
- (mousedown: MouseEvent) => {
1156
- if (!doesComponentContainEventTarget(mousedown)) {
1157
- mousedown.preventDefault(); // Prevent focus from being applied to body
1158
- mousedown.stopPropagation(); // Stop any other mousedown events from firing
1159
- }
1160
- },
1161
- {
1162
- enabled: isOpen,
1163
- },
1164
- );
1165
- useEventListener(
1166
- 'click',
1167
- (click: MouseEvent) => {
1168
- if (!doesComponentContainEventTarget(click)) {
1169
- click.stopPropagation(); // Stop any other click events from firing
1170
- closeMenu();
1171
- }
1172
- },
1173
- {
1174
- options: { capture: true },
1175
- enabled: isOpen,
1176
- },
1177
- );
1101
+ useBackdropClick(closeMenu, [menuRef, comboboxRef], isOpen);
1178
1102
 
1179
1103
  const popoverProps = {
1180
1104
  popoverZIndex,
@@ -1189,144 +1113,155 @@ export function Combobox<M extends boolean>({
1189
1113
  } as const;
1190
1114
 
1191
1115
  return (
1192
- <ComboboxContext.Provider
1193
- value={{
1194
- multiselect,
1195
- darkMode,
1196
- theme,
1197
- size,
1198
- withIcons,
1199
- disabled,
1200
- isOpen,
1201
- state,
1202
- searchState,
1203
- chipTruncationLocation,
1204
- chipCharacterLimit,
1205
- inputValue,
1206
- }}
1207
- >
1208
- <div className={cx(comboboxParentStyle(size), className)} {...rest}>
1209
- {(label || description) && (
1210
- <div className={labelDescriptionContainerStyle}>
1211
- {label && (
1212
- <Label id={labelId} htmlFor={inputId} darkMode={darkMode}>
1213
- {label}
1214
- </Label>
1215
- )}
1216
- {description && (
1217
- <Description darkMode={darkMode}>{description}</Description>
1218
- )}
1219
- </div>
1220
- )}
1221
-
1222
- {/* Disable eslint: onClick sets focus. Key events would already have focus */}
1223
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
1224
- <div
1225
- ref={comboboxRef}
1226
- role="combobox"
1227
- aria-expanded={isOpen}
1228
- aria-controls={menuId}
1229
- aria-owns={menuId}
1230
- tabIndex={-1}
1231
- onMouseDown={handleInputWrapperMousedown}
1232
- onClick={handleComboboxClick}
1233
- onFocus={handleComboboxFocus}
1234
- onKeyDown={handleKeyDown}
1235
- onTransitionEnd={handleTransitionEnd}
1236
- className={cx(
1237
- baseComboboxStyles,
1238
- comboboxThemeStyles[theme],
1239
- comboboxSizeStyles(size),
1240
- {
1241
- [comboboxSelectionStyles]: clearable && doesSelectionExist,
1242
- [comboboxDisabledStyles[theme]]: disabled,
1243
- [comboboxErrorStyles[theme]]: state === State.error,
1244
- [comboboxFocusStyle[theme]]: isElementFocused(
1245
- ComboboxElement.Input,
1246
- ),
1247
- },
1248
- )}
1249
- >
1250
- <div
1251
- ref={inputWrapperRef}
1252
- className={inputWrapperStyle({
1253
- size,
1254
- overflow,
1255
- })}
1256
- >
1257
- {renderedChips}
1258
- <input
1259
- aria-label={ariaLabel ?? label}
1260
- aria-autocomplete="list"
1261
- aria-controls={menuId}
1262
- aria-labelledby={labelId}
1263
- ref={inputRef}
1264
- id={inputId}
1265
- className={cx(
1266
- baseInputElementStyle,
1267
- inputElementSizeStyle[size],
1268
- inputElementThemeStyle[theme],
1269
- inputElementTransitionStyles(isOpen),
1270
- {
1271
- [multiselectInputElementStyle(size, inputValue)]:
1272
- isMultiselect(selection),
1273
- },
1116
+ <LeafyGreenProvider darkMode={darkMode}>
1117
+ <ComboboxContext.Provider
1118
+ value={{
1119
+ multiselect,
1120
+ size,
1121
+ withIcons,
1122
+ disabled,
1123
+ isOpen,
1124
+ state,
1125
+ searchState,
1126
+ chipTruncationLocation,
1127
+ chipCharacterLimit,
1128
+ inputValue,
1129
+ }}
1130
+ >
1131
+ <div className={cx(comboboxParentStyle(size), className)} {...rest}>
1132
+ {(label || description) && (
1133
+ <div className={labelDescriptionContainerStyle}>
1134
+ {label && (
1135
+ <Label id={labelId} htmlFor={inputId} darkMode={darkMode}>
1136
+ {label}
1137
+ </Label>
1274
1138
  )}
1275
- placeholder={placeholderValue}
1276
- disabled={disabled ?? undefined}
1277
- onChange={handleInputChange}
1278
- value={inputValue}
1279
- autoComplete="off"
1280
- />
1281
- </div>
1282
- {renderedInputIcons}
1283
- </div>
1139
+ {description && (
1140
+ <Description darkMode={darkMode}>{description}</Description>
1141
+ )}
1142
+ </div>
1143
+ )}
1284
1144
 
1285
- {state === 'error' && errorMessage && (
1145
+ {/* Disable eslint: onClick sets focus. Key events would already have focus */}
1146
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
1286
1147
  <div
1148
+ ref={comboboxRef}
1149
+ role="combobox"
1150
+ aria-expanded={isOpen}
1151
+ aria-controls={menuId}
1152
+ aria-owns={menuId}
1153
+ tabIndex={-1}
1154
+ onMouseDown={handleInputWrapperMousedown}
1155
+ onClick={handleComboboxClick}
1156
+ onFocus={handleComboboxFocus}
1157
+ onKeyDown={handleKeyDown}
1158
+ onTransitionEnd={handleTransitionEnd}
1287
1159
  className={cx(
1288
- errorMessageThemeStyle[theme],
1289
- errorMessageSizeStyle[size],
1160
+ baseComboboxStyles,
1161
+ comboboxThemeStyles[theme],
1162
+ comboboxSizeStyles(size),
1163
+ {
1164
+ [comboboxSelectionStyles]: clearable && doesSelectionExist,
1165
+ [comboboxDisabledStyles[theme]]: disabled,
1166
+ [comboboxErrorStyles[theme]]: state === State.error,
1167
+ [comboboxFocusStyle[theme]]: isElementFocused(
1168
+ ComboboxElement.Input,
1169
+ ),
1170
+ },
1290
1171
  )}
1291
1172
  >
1292
- {errorMessage}
1173
+ <div
1174
+ ref={inputWrapperRef}
1175
+ className={inputWrapperStyle({
1176
+ size,
1177
+ overflow,
1178
+ })}
1179
+ >
1180
+ {renderedChips}
1181
+ <input
1182
+ aria-label={ariaLabel ?? label}
1183
+ aria-autocomplete="list"
1184
+ aria-controls={menuId}
1185
+ aria-labelledby={labelId}
1186
+ ref={inputRef}
1187
+ id={inputId}
1188
+ className={cx(
1189
+ baseInputElementStyle,
1190
+ inputElementSizeStyle[size],
1191
+ inputElementThemeStyle[theme],
1192
+ inputElementTransitionStyles(isOpen),
1193
+ {
1194
+ [multiselectInputElementStyle(size, inputValue)]:
1195
+ isMultiselect(selection),
1196
+ },
1197
+ )}
1198
+ placeholder={placeholderValue}
1199
+ disabled={disabled ?? undefined}
1200
+ onChange={handleInputChange}
1201
+ value={inputValue}
1202
+ autoComplete="off"
1203
+ />
1204
+ </div>
1205
+ {clearable && doesSelectionExist && (
1206
+ <IconButton
1207
+ aria-label="Clear selection"
1208
+ aria-disabled={disabled}
1209
+ disabled={disabled}
1210
+ ref={clearButtonRef}
1211
+ onClick={handleClearButtonClick}
1212
+ onFocus={handleClearButtonFocus}
1213
+ className={cx(clearButtonStyle)}
1214
+ darkMode={darkMode}
1215
+ >
1216
+ <Icon glyph="XWithCircle" />
1217
+ </IconButton>
1218
+ )}
1219
+ {state === 'error' ? (
1220
+ <Icon
1221
+ glyph="Warning"
1222
+ color={darkMode ? palette.red.light1 : palette.red.base}
1223
+ className={endIconStyle(size)}
1224
+ />
1225
+ ) : (
1226
+ <Icon glyph="CaretDown" className={endIconStyle(size)} />
1227
+ )}
1293
1228
  </div>
1294
- )}
1295
1229
 
1296
- {/******* /
1230
+ {state === 'error' && errorMessage && (
1231
+ <div
1232
+ className={cx(
1233
+ errorMessageThemeStyle[theme],
1234
+ errorMessageSizeStyle[size],
1235
+ )}
1236
+ >
1237
+ {errorMessage}
1238
+ </div>
1239
+ )}
1240
+
1241
+ {/******* /
1297
1242
  * Menu *
1298
1243
  / *******/}
1299
1244
 
1300
- <ComboboxMenu
1301
- id={menuId}
1302
- labelId={labelId}
1303
- refEl={comboboxRef}
1304
- ref={menuRef}
1305
- menuWidth={menuWidth}
1306
- searchLoadingMessage={searchLoadingMessage}
1307
- searchErrorMessage={searchErrorMessage}
1308
- searchEmptyMessage={searchEmptyMessage}
1309
- {...popoverProps}
1310
- >
1311
- {renderedOptionsJSX}
1312
- </ComboboxMenu>
1313
- </div>
1314
- </ComboboxContext.Provider>
1245
+ <ComboboxMenu
1246
+ id={menuId}
1247
+ labelId={labelId}
1248
+ refEl={comboboxRef}
1249
+ ref={menuRef}
1250
+ menuWidth={menuWidth}
1251
+ searchLoadingMessage={searchLoadingMessage}
1252
+ searchErrorMessage={searchErrorMessage}
1253
+ searchEmptyMessage={searchEmptyMessage}
1254
+ {...popoverProps}
1255
+ >
1256
+ {renderedOptionsJSX}
1257
+ </ComboboxMenu>
1258
+ </div>
1259
+ </ComboboxContext.Provider>
1260
+ </LeafyGreenProvider>
1315
1261
  );
1316
1262
 
1317
1263
  // Closure-dependant utils
1318
1264
 
1319
- /**
1320
- * Returns whether the event target is a Combobox element
1321
- */
1322
- function doesComponentContainEventTarget({ target }: MouseEvent): boolean {
1323
- return (
1324
- menuRef.current?.contains(target as Node) ||
1325
- comboboxRef.current?.contains(target as Node) ||
1326
- false
1327
- );
1328
- }
1329
-
1330
1265
  /**
1331
1266
  * Scrolls the combobox to the far right.
1332
1267
  * Used when `overflow == 'scroll-x'`.
@@ -0,0 +1 @@
1
+ export { Combobox } from './Combobox';
@@ -1,18 +1,14 @@
1
1
  import { createContext } from 'react';
2
2
 
3
- import { Theme } from '@leafygreen-ui/lib';
4
-
5
3
  import {
6
4
  ComboboxSize,
7
5
  SearchState,
8
6
  State,
9
7
  TruncationLocation,
10
- } from './Combobox.types';
8
+ } from '../Combobox.types';
11
9
 
12
10
  interface ComboboxData {
13
11
  multiselect: boolean;
14
- darkMode: boolean;
15
- theme: Theme;
16
12
  size: ComboboxSize;
17
13
  withIcons: boolean;
18
14
  disabled: boolean;
@@ -26,8 +22,6 @@ interface ComboboxData {
26
22
 
27
23
  export const ComboboxContext = createContext<ComboboxData>({
28
24
  multiselect: false,
29
- darkMode: false,
30
- theme: Theme.Light,
31
25
  size: ComboboxSize.Default,
32
26
  withIcons: false,
33
27
  disabled: false,
@@ -0,0 +1 @@
1
+ export { ComboboxContext } from './ComboboxContext';
@@ -0,0 +1,34 @@
1
+ import { css } from '@leafygreen-ui/emotion';
2
+ import { Theme } from '@leafygreen-ui/lib';
3
+ import { palette } from '@leafygreen-ui/palette';
4
+
5
+ export const comboboxGroupStyle: Record<Theme, string> = {
6
+ [Theme.Light]: css`
7
+ padding-top: 8px;
8
+ `,
9
+ [Theme.Dark]: css`
10
+ padding-top: 8px;
11
+ `,
12
+ };
13
+
14
+ export const comboboxGroupLabel = css`
15
+ cursor: default;
16
+ width: 100%;
17
+ padding: 0 12px 2px;
18
+ outline: none;
19
+ overflow-wrap: anywhere;
20
+ font-size: 12px;
21
+ line-height: 16px;
22
+ font-weight: bold;
23
+ text-transform: uppercase;
24
+ letter-spacing: 0.4px;
25
+ `;
26
+
27
+ export const comboboxGroupLabelThemeStyle: Record<Theme, string> = {
28
+ [Theme.Light]: css`
29
+ color: ${palette.gray.dark1};
30
+ `,
31
+ [Theme.Dark]: css`
32
+ color: ${palette.gray.light1};
33
+ `,
34
+ };
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import { cx } from '@leafygreen-ui/emotion';
5
+ import { useIdAllocator } from '@leafygreen-ui/hooks';
6
+ import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
7
+
8
+ import { ComboboxGroupProps } from '../Combobox.types';
9
+
10
+ import {
11
+ comboboxGroupLabel,
12
+ comboboxGroupLabelThemeStyle,
13
+ comboboxGroupStyle,
14
+ } from './ComboboxGroup.styles';
15
+
16
+ export function InternalComboboxGroup({
17
+ label,
18
+ className,
19
+ children,
20
+ }: ComboboxGroupProps): JSX.Element {
21
+ const { theme } = useDarkMode();
22
+
23
+ const groupId = useIdAllocator({ prefix: 'combobox-group' });
24
+ const childCount = React.Children.count(children);
25
+
26
+ return childCount > 0 ? (
27
+ <div className={cx(comboboxGroupStyle[theme], className)}>
28
+ <div
29
+ className={cx(comboboxGroupLabel, comboboxGroupLabelThemeStyle[theme])}
30
+ id={groupId}
31
+ >
32
+ {label}
33
+ </div>
34
+ <div role="group" aria-labelledby={groupId}>
35
+ {children}
36
+ </div>
37
+ </div>
38
+ ) : (
39
+ <></>
40
+ );
41
+ }
42
+
43
+ ComboboxGroup.displayName = 'ComboboxGroup';
44
+
45
+ ComboboxGroup.propTypes = {
46
+ className: PropTypes.string,
47
+ children: PropTypes.node.isRequired,
48
+ label: PropTypes.string.isRequired,
49
+ };
50
+
51
+ export function ComboboxGroup(_: ComboboxGroupProps): JSX.Element {
52
+ throw Error('`ComboboxGroup` must be a child of a `Combobox` instance');
53
+ }
@@ -0,0 +1 @@
1
+ export { ComboboxGroup, InternalComboboxGroup } from './ComboboxGroup';