@leafygreen-ui/combobox 1.2.2 → 2.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.
- package/CHANGELOG.md +18 -0
- package/dist/Chip.d.ts.map +1 -1
- package/dist/Combobox.d.ts.map +1 -1
- package/dist/Combobox.styles.d.ts +39 -39
- package/dist/Combobox.styles.d.ts.map +1 -1
- package/dist/Combobox.types.d.ts +5 -0
- package/dist/Combobox.types.d.ts.map +1 -1
- package/dist/ComboboxContext.d.ts +5 -1
- package/dist/ComboboxContext.d.ts.map +1 -1
- package/dist/ComboboxGroup.d.ts.map +1 -1
- package/dist/ComboboxMenu/ComboboxMenu.d.ts +10 -0
- package/dist/ComboboxMenu/ComboboxMenu.d.ts.map +1 -0
- package/dist/ComboboxMenu/Menu.styles.d.ts +25 -0
- package/dist/ComboboxMenu/Menu.styles.d.ts.map +1 -0
- package/dist/ComboboxOption.d.ts.map +1 -1
- package/dist/Menu.styles.d.ts +21 -0
- package/dist/Menu.styles.d.ts.map +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +8 -9
- package/src/Chip.tsx +125 -91
- package/src/Combobox.story.tsx +2 -0
- package/src/Combobox.styles.ts +250 -299
- package/src/Combobox.tsx +156 -189
- package/src/Combobox.types.ts +7 -0
- package/src/ComboboxContext.tsx +16 -1
- package/src/ComboboxGroup.tsx +27 -17
- package/src/ComboboxMenu/ComboboxMenu.tsx +161 -0
- package/src/ComboboxMenu/Menu.styles.ts +131 -0
- package/src/ComboboxOption.tsx +120 -32
- package/src/Menu.styles.ts +110 -0
- package/tsconfig.json +0 -3
- package/tsconfig.tsbuildinfo +1 -1
package/src/Combobox.tsx
CHANGED
|
@@ -7,9 +7,7 @@ import React, {
|
|
|
7
7
|
} from 'react';
|
|
8
8
|
import { clone, isArray, isEqual, isNull, isString, isUndefined } from 'lodash';
|
|
9
9
|
import { Description, Label } from '@leafygreen-ui/typography';
|
|
10
|
-
import Popover from '@leafygreen-ui/popover';
|
|
11
10
|
import {
|
|
12
|
-
useAvailableSpace,
|
|
13
11
|
useDynamicRefs,
|
|
14
12
|
useEventListener,
|
|
15
13
|
useIdAllocator,
|
|
@@ -17,8 +15,8 @@ import {
|
|
|
17
15
|
} from '@leafygreen-ui/hooks';
|
|
18
16
|
import Icon from '@leafygreen-ui/icon';
|
|
19
17
|
import IconButton from '@leafygreen-ui/icon-button';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
18
|
+
import { cx } from '@leafygreen-ui/emotion';
|
|
19
|
+
import { palette } from '@leafygreen-ui/palette';
|
|
22
20
|
import { consoleOnce, isComponentType, keyMap } from '@leafygreen-ui/lib';
|
|
23
21
|
import {
|
|
24
22
|
ComboboxProps,
|
|
@@ -28,28 +26,8 @@ import {
|
|
|
28
26
|
OptionObject,
|
|
29
27
|
ComboboxElement,
|
|
30
28
|
ComboboxSize,
|
|
29
|
+
State,
|
|
31
30
|
} from './Combobox.types';
|
|
32
|
-
import { ComboboxContext } from './ComboboxContext';
|
|
33
|
-
import { InternalComboboxOption } from './ComboboxOption';
|
|
34
|
-
import { Chip } from './Chip';
|
|
35
|
-
import {
|
|
36
|
-
clearButtonStyle,
|
|
37
|
-
clearButtonFocusOverrideStyles,
|
|
38
|
-
comboboxFocusStyle,
|
|
39
|
-
comboboxParentStyle,
|
|
40
|
-
comboboxStyle,
|
|
41
|
-
endIcon,
|
|
42
|
-
errorMessageStyle,
|
|
43
|
-
inputElementStyle,
|
|
44
|
-
inputWrapperStyle,
|
|
45
|
-
loadingIconStyle,
|
|
46
|
-
menuList,
|
|
47
|
-
menuMessage,
|
|
48
|
-
menuStyle,
|
|
49
|
-
menuWrapperStyle,
|
|
50
|
-
_tempLabelDescriptionOverrideStyle,
|
|
51
|
-
} from './Combobox.styles';
|
|
52
|
-
import { InternalComboboxGroup } from './ComboboxGroup';
|
|
53
31
|
import {
|
|
54
32
|
flattenChildren,
|
|
55
33
|
getOptionObjectFromValue,
|
|
@@ -57,6 +35,32 @@ import {
|
|
|
57
35
|
getValueForDisplayName,
|
|
58
36
|
getNameAndValue,
|
|
59
37
|
} from './utils';
|
|
38
|
+
import { ComboboxContext, useDarkMode } from './ComboboxContext';
|
|
39
|
+
import { InternalComboboxGroup } from './ComboboxGroup';
|
|
40
|
+
import { InternalComboboxOption } from './ComboboxOption';
|
|
41
|
+
import { Chip } from './Chip';
|
|
42
|
+
import {
|
|
43
|
+
comboboxFocusStyle,
|
|
44
|
+
inputWrapperStyle,
|
|
45
|
+
baseComboboxStyles,
|
|
46
|
+
comboboxThemeStyles,
|
|
47
|
+
comboboxSizeStyles,
|
|
48
|
+
comboboxDisabledStyles,
|
|
49
|
+
comboboxErrorStyles,
|
|
50
|
+
comboboxParentStyle,
|
|
51
|
+
baseInputElementStyle,
|
|
52
|
+
inputElementSizeStyle,
|
|
53
|
+
inputElementTransitionStyles,
|
|
54
|
+
multiselectInputElementStyle,
|
|
55
|
+
clearButtonStyle,
|
|
56
|
+
endIconStyle,
|
|
57
|
+
errorMessageThemeStyle,
|
|
58
|
+
errorMessageSizeStyle,
|
|
59
|
+
multiselectInputElementPadding,
|
|
60
|
+
labelDescriptionContainerStyle,
|
|
61
|
+
inputElementThemeStyle,
|
|
62
|
+
} from './Combobox.styles';
|
|
63
|
+
import { ComboboxMenu } from './ComboboxMenu/ComboboxMenu';
|
|
60
64
|
|
|
61
65
|
/**
|
|
62
66
|
* Combobox is a combination of a Select and TextInput,
|
|
@@ -97,6 +101,7 @@ export default function Combobox<M extends boolean>({
|
|
|
97
101
|
popoverZIndex,
|
|
98
102
|
...rest
|
|
99
103
|
}: ComboboxProps<M>) {
|
|
104
|
+
const theme = useDarkMode(darkMode);
|
|
100
105
|
const getOptionRef = useDynamicRefs<HTMLLIElement>({ prefix: 'option' });
|
|
101
106
|
const getChipRef = useDynamicRefs<HTMLSpanElement>({ prefix: 'chip' });
|
|
102
107
|
|
|
@@ -357,6 +362,8 @@ export default function Combobox<M extends boolean>({
|
|
|
357
362
|
const [focusedElementName, trackFocusedElement] = useState<
|
|
358
363
|
ComboboxElement | undefined
|
|
359
364
|
>();
|
|
365
|
+
const isElementFocused = (elementName: ComboboxElement) =>
|
|
366
|
+
elementName === focusedElementName;
|
|
360
367
|
|
|
361
368
|
type Direction = 'next' | 'prev' | 'first' | 'last';
|
|
362
369
|
|
|
@@ -595,74 +602,75 @@ export default function Combobox<M extends boolean>({
|
|
|
595
602
|
*/
|
|
596
603
|
|
|
597
604
|
/**
|
|
598
|
-
* Callback to render
|
|
605
|
+
* Callback to render a child as an <InternalComboboxOption> element
|
|
599
606
|
*/
|
|
600
|
-
const
|
|
601
|
-
(
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
} else if (isComponentType(child, 'ComboboxGroup')) {
|
|
643
|
-
const nestedChildren = renderInternalOptions(child.props.children);
|
|
644
|
-
|
|
645
|
-
if (nestedChildren && nestedChildren?.length > 0) {
|
|
646
|
-
return (
|
|
647
|
-
<InternalComboboxGroup
|
|
648
|
-
label={child.props.label}
|
|
649
|
-
className={child.props.className}
|
|
650
|
-
>
|
|
651
|
-
{renderInternalOptions(nestedChildren)}
|
|
652
|
-
</InternalComboboxGroup>
|
|
653
|
-
);
|
|
654
|
-
}
|
|
607
|
+
const renderOption = useCallback(
|
|
608
|
+
(child: React.ReactNode) => {
|
|
609
|
+
if (isComponentType(child, 'ComboboxOption')) {
|
|
610
|
+
const { value, displayName } = getNameAndValue(child.props);
|
|
611
|
+
|
|
612
|
+
if (shouldOptionBeVisible(value)) {
|
|
613
|
+
const { className, glyph, disabled } = child.props;
|
|
614
|
+
const index = allOptions.findIndex(opt => opt.value === value);
|
|
615
|
+
|
|
616
|
+
const isFocused = highlightedOption === value;
|
|
617
|
+
const isSelected = isMultiselect(selection)
|
|
618
|
+
? selection.includes(value)
|
|
619
|
+
: selection === value;
|
|
620
|
+
|
|
621
|
+
const setSelected = () => {
|
|
622
|
+
sethighlightedOption(value);
|
|
623
|
+
updateSelection(value);
|
|
624
|
+
setInputFocus();
|
|
625
|
+
|
|
626
|
+
if (value === selection) {
|
|
627
|
+
closeMenu();
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
const optionRef = getOptionRef(value);
|
|
632
|
+
|
|
633
|
+
return (
|
|
634
|
+
<InternalComboboxOption
|
|
635
|
+
value={value}
|
|
636
|
+
displayName={displayName}
|
|
637
|
+
isFocused={isFocused}
|
|
638
|
+
isSelected={isSelected}
|
|
639
|
+
disabled={disabled}
|
|
640
|
+
setSelected={setSelected}
|
|
641
|
+
glyph={glyph}
|
|
642
|
+
className={className}
|
|
643
|
+
index={index}
|
|
644
|
+
ref={optionRef}
|
|
645
|
+
/>
|
|
646
|
+
);
|
|
655
647
|
}
|
|
656
|
-
})
|
|
648
|
+
} else if (isComponentType(child, 'ComboboxGroup')) {
|
|
649
|
+
const nestedChildren = React.Children.map(
|
|
650
|
+
child.props.children,
|
|
651
|
+
renderOption,
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
if (nestedChildren && nestedChildren?.length > 0) {
|
|
655
|
+
return (
|
|
656
|
+
<InternalComboboxGroup
|
|
657
|
+
label={child.props.label}
|
|
658
|
+
className={child.props.className}
|
|
659
|
+
>
|
|
660
|
+
{React.Children.map(nestedChildren, renderOption)}
|
|
661
|
+
</InternalComboboxGroup>
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
657
665
|
},
|
|
658
666
|
[
|
|
659
667
|
allOptions,
|
|
660
|
-
highlightedOption,
|
|
661
668
|
getOptionRef,
|
|
669
|
+
highlightedOption,
|
|
662
670
|
isMultiselect,
|
|
663
|
-
shouldOptionBeVisible,
|
|
664
671
|
selection,
|
|
665
672
|
setInputFocus,
|
|
673
|
+
shouldOptionBeVisible,
|
|
666
674
|
updateSelection,
|
|
667
675
|
],
|
|
668
676
|
);
|
|
@@ -671,8 +679,8 @@ export default function Combobox<M extends boolean>({
|
|
|
671
679
|
* The rendered JSX elements for the options
|
|
672
680
|
*/
|
|
673
681
|
const renderedOptionsJSX = useMemo(
|
|
674
|
-
() =>
|
|
675
|
-
[children,
|
|
682
|
+
() => React.Children.map(children, renderOption),
|
|
683
|
+
[children, renderOption],
|
|
676
684
|
);
|
|
677
685
|
|
|
678
686
|
/**
|
|
@@ -752,15 +760,20 @@ export default function Combobox<M extends boolean>({
|
|
|
752
760
|
ref={clearButtonRef}
|
|
753
761
|
onClick={handleClearButtonClick}
|
|
754
762
|
onFocus={handleClearButtonFocus}
|
|
755
|
-
className={cx(clearButtonStyle
|
|
763
|
+
className={cx(clearButtonStyle)}
|
|
764
|
+
darkMode={darkMode}
|
|
756
765
|
>
|
|
757
766
|
<Icon glyph="XWithCircle" />
|
|
758
767
|
</IconButton>
|
|
759
768
|
)}
|
|
760
769
|
{state === 'error' ? (
|
|
761
|
-
<Icon
|
|
770
|
+
<Icon
|
|
771
|
+
glyph="Warning"
|
|
772
|
+
color={darkMode ? palette.red.light1 : palette.red.base}
|
|
773
|
+
className={endIconStyle(size)}
|
|
774
|
+
/>
|
|
762
775
|
) : (
|
|
763
|
-
<Icon glyph="CaretDown" className={
|
|
776
|
+
<Icon glyph="CaretDown" className={endIconStyle(size)} />
|
|
764
777
|
)}
|
|
765
778
|
</>
|
|
766
779
|
);
|
|
@@ -769,6 +782,8 @@ export default function Combobox<M extends boolean>({
|
|
|
769
782
|
doesSelectionExist,
|
|
770
783
|
disabled,
|
|
771
784
|
state,
|
|
785
|
+
darkMode,
|
|
786
|
+
size,
|
|
772
787
|
updateSelection,
|
|
773
788
|
onClear,
|
|
774
789
|
onFilter,
|
|
@@ -906,62 +921,11 @@ export default function Combobox<M extends boolean>({
|
|
|
906
921
|
setMenuWidth(comboboxRef.current?.clientWidth ?? 0);
|
|
907
922
|
}, [comboboxRef, isOpen, highlightedOption, selection]);
|
|
908
923
|
|
|
909
|
-
// Handler fired when the
|
|
924
|
+
// Handler fired when the menu has finished transitioning in/out
|
|
910
925
|
const handleTransitionEnd = () => {
|
|
911
926
|
setMenuWidth(comboboxRef.current?.clientWidth ?? 0);
|
|
912
927
|
};
|
|
913
928
|
|
|
914
|
-
/**
|
|
915
|
-
* The rendered menu JSX contents
|
|
916
|
-
* Includes error, empty, search and default states
|
|
917
|
-
*/
|
|
918
|
-
const renderedMenuContents = useMemo((): JSX.Element => {
|
|
919
|
-
switch (searchState) {
|
|
920
|
-
case 'loading': {
|
|
921
|
-
return (
|
|
922
|
-
<span className={menuMessage}>
|
|
923
|
-
<Icon
|
|
924
|
-
glyph="Refresh"
|
|
925
|
-
color={uiColors.blue.base}
|
|
926
|
-
className={loadingIconStyle}
|
|
927
|
-
/>
|
|
928
|
-
{searchLoadingMessage}
|
|
929
|
-
</span>
|
|
930
|
-
);
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
case 'error': {
|
|
934
|
-
return (
|
|
935
|
-
<span className={menuMessage}>
|
|
936
|
-
<Icon glyph="Warning" color={uiColors.red.base} />
|
|
937
|
-
{searchErrorMessage}
|
|
938
|
-
</span>
|
|
939
|
-
);
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
case 'unset':
|
|
943
|
-
default: {
|
|
944
|
-
if (renderedOptionsJSX && renderedOptionsJSX.length > 0) {
|
|
945
|
-
return <ul className={menuList}>{renderedOptionsJSX}</ul>;
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
return <span className={menuMessage}>{searchEmptyMessage}</span>;
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
}, [
|
|
952
|
-
renderedOptionsJSX,
|
|
953
|
-
searchEmptyMessage,
|
|
954
|
-
searchErrorMessage,
|
|
955
|
-
searchLoadingMessage,
|
|
956
|
-
searchState,
|
|
957
|
-
]);
|
|
958
|
-
|
|
959
|
-
/** The max height of the menu element */
|
|
960
|
-
const availableSpace = useAvailableSpace(comboboxRef);
|
|
961
|
-
const maxHeightValue = !isUndefined(availableSpace)
|
|
962
|
-
? `${Math.min(availableSpace, 256)}px`
|
|
963
|
-
: 'unset';
|
|
964
|
-
|
|
965
929
|
/**
|
|
966
930
|
*
|
|
967
931
|
* Event Handlers
|
|
@@ -1214,32 +1178,26 @@ export default function Combobox<M extends boolean>({
|
|
|
1214
1178
|
size,
|
|
1215
1179
|
withIcons,
|
|
1216
1180
|
disabled,
|
|
1181
|
+
isOpen,
|
|
1182
|
+
state,
|
|
1183
|
+
searchState,
|
|
1217
1184
|
chipTruncationLocation,
|
|
1218
1185
|
chipCharacterLimit,
|
|
1219
1186
|
inputValue,
|
|
1220
1187
|
}}
|
|
1221
1188
|
>
|
|
1222
1189
|
<div
|
|
1223
|
-
className={cx(
|
|
1224
|
-
comboboxParentStyle({ darkMode, size, overflow }),
|
|
1225
|
-
className,
|
|
1226
|
-
)}
|
|
1190
|
+
className={cx(comboboxParentStyle(size, overflow), className)}
|
|
1227
1191
|
{...rest}
|
|
1228
1192
|
>
|
|
1229
|
-
<div>
|
|
1193
|
+
<div className={labelDescriptionContainerStyle}>
|
|
1230
1194
|
{label && (
|
|
1231
|
-
<Label
|
|
1232
|
-
id={labelId}
|
|
1233
|
-
htmlFor={inputId}
|
|
1234
|
-
className={_tempLabelDescriptionOverrideStyle}
|
|
1235
|
-
>
|
|
1195
|
+
<Label id={labelId} htmlFor={inputId} darkMode={darkMode}>
|
|
1236
1196
|
{label}
|
|
1237
1197
|
</Label>
|
|
1238
1198
|
)}
|
|
1239
1199
|
{description && (
|
|
1240
|
-
<Description
|
|
1241
|
-
{description}
|
|
1242
|
-
</Description>
|
|
1200
|
+
<Description darkMode={darkMode}>{description}</Description>
|
|
1243
1201
|
)}
|
|
1244
1202
|
</div>
|
|
1245
1203
|
|
|
@@ -1252,25 +1210,29 @@ export default function Combobox<M extends boolean>({
|
|
|
1252
1210
|
aria-controls={menuId}
|
|
1253
1211
|
aria-owns={menuId}
|
|
1254
1212
|
tabIndex={-1}
|
|
1255
|
-
className={cx(comboboxStyle, {
|
|
1256
|
-
[comboboxFocusStyle]: focusedElementName === ComboboxElement.Input,
|
|
1257
|
-
})}
|
|
1258
1213
|
onMouseDown={handleInputWrapperMousedown}
|
|
1259
1214
|
onClick={handleComboboxClick}
|
|
1260
1215
|
onFocus={handleComboboxFocus}
|
|
1261
1216
|
onKeyDown={handleKeyDown}
|
|
1262
1217
|
onTransitionEnd={handleTransitionEnd}
|
|
1263
|
-
|
|
1264
|
-
|
|
1218
|
+
className={cx(
|
|
1219
|
+
baseComboboxStyles,
|
|
1220
|
+
comboboxThemeStyles[theme],
|
|
1221
|
+
comboboxSizeStyles(size),
|
|
1222
|
+
{
|
|
1223
|
+
[comboboxDisabledStyles[theme]]: disabled,
|
|
1224
|
+
[comboboxErrorStyles[theme]]: state === State.error,
|
|
1225
|
+
[comboboxFocusStyle[theme]]: isElementFocused(
|
|
1226
|
+
ComboboxElement.Input,
|
|
1227
|
+
),
|
|
1228
|
+
},
|
|
1229
|
+
)}
|
|
1265
1230
|
>
|
|
1266
1231
|
<div
|
|
1267
1232
|
ref={inputWrapperRef}
|
|
1268
1233
|
className={inputWrapperStyle({
|
|
1269
|
-
overflow,
|
|
1270
|
-
isOpen,
|
|
1271
|
-
selection,
|
|
1272
1234
|
size,
|
|
1273
|
-
|
|
1235
|
+
overflow,
|
|
1274
1236
|
})}
|
|
1275
1237
|
>
|
|
1276
1238
|
{renderedChips}
|
|
@@ -1281,7 +1243,18 @@ export default function Combobox<M extends boolean>({
|
|
|
1281
1243
|
aria-labelledby={labelId}
|
|
1282
1244
|
ref={inputRef}
|
|
1283
1245
|
id={inputId}
|
|
1284
|
-
className={
|
|
1246
|
+
className={cx(
|
|
1247
|
+
baseInputElementStyle,
|
|
1248
|
+
inputElementSizeStyle[size],
|
|
1249
|
+
inputElementThemeStyle[theme],
|
|
1250
|
+
inputElementTransitionStyles(isOpen, overflow),
|
|
1251
|
+
{
|
|
1252
|
+
[multiselectInputElementStyle(size, inputValue)]:
|
|
1253
|
+
isMultiselect(selection),
|
|
1254
|
+
[multiselectInputElementPadding(selection)]:
|
|
1255
|
+
isMultiselect(selection),
|
|
1256
|
+
},
|
|
1257
|
+
)}
|
|
1285
1258
|
placeholder={placeholderValue}
|
|
1286
1259
|
disabled={disabled ?? undefined}
|
|
1287
1260
|
onChange={handleInputChange}
|
|
@@ -1293,39 +1266,33 @@ export default function Combobox<M extends boolean>({
|
|
|
1293
1266
|
</div>
|
|
1294
1267
|
|
|
1295
1268
|
{state === 'error' && errorMessage && (
|
|
1296
|
-
<div
|
|
1269
|
+
<div
|
|
1270
|
+
className={cx(
|
|
1271
|
+
errorMessageThemeStyle[theme],
|
|
1272
|
+
errorMessageSizeStyle[size],
|
|
1273
|
+
)}
|
|
1274
|
+
>
|
|
1275
|
+
{errorMessage}
|
|
1276
|
+
</div>
|
|
1297
1277
|
)}
|
|
1298
1278
|
|
|
1299
1279
|
{/******* /
|
|
1300
1280
|
* Menu *
|
|
1301
1281
|
/ *******/}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
justify="middle"
|
|
1282
|
+
|
|
1283
|
+
<ComboboxMenu
|
|
1284
|
+
id={menuId}
|
|
1285
|
+
labelId={labelId}
|
|
1307
1286
|
refEl={comboboxRef}
|
|
1308
|
-
|
|
1309
|
-
|
|
1287
|
+
ref={menuRef}
|
|
1288
|
+
menuWidth={menuWidth}
|
|
1289
|
+
searchLoadingMessage={searchLoadingMessage}
|
|
1290
|
+
searchErrorMessage={searchErrorMessage}
|
|
1291
|
+
searchEmptyMessage={searchEmptyMessage}
|
|
1310
1292
|
{...popoverProps}
|
|
1311
1293
|
>
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
role="listbox"
|
|
1315
|
-
aria-labelledby={labelId}
|
|
1316
|
-
aria-expanded={isOpen}
|
|
1317
|
-
ref={menuRef}
|
|
1318
|
-
className={cx(
|
|
1319
|
-
menuStyle,
|
|
1320
|
-
css`
|
|
1321
|
-
max-height: ${maxHeightValue};
|
|
1322
|
-
`,
|
|
1323
|
-
)}
|
|
1324
|
-
onMouseDownCapture={e => e.preventDefault()}
|
|
1325
|
-
>
|
|
1326
|
-
{renderedMenuContents}
|
|
1327
|
-
</div>
|
|
1328
|
-
</Popover>
|
|
1294
|
+
{renderedOptionsJSX}
|
|
1295
|
+
</ComboboxMenu>
|
|
1329
1296
|
</div>
|
|
1330
1297
|
</ComboboxContext.Provider>
|
|
1331
1298
|
);
|
package/src/Combobox.types.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { ReactElement, ReactNode } from 'react';
|
|
2
2
|
import { Either } from '@leafygreen-ui/lib';
|
|
3
3
|
|
|
4
|
+
export const Theme = {
|
|
5
|
+
Dark: 'dark',
|
|
6
|
+
Light: 'light',
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export type Theme = typeof Theme[keyof typeof Theme];
|
|
10
|
+
|
|
4
11
|
/**
|
|
5
12
|
* Prop Enums & Types
|
|
6
13
|
*/
|
package/src/ComboboxContext.tsx
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { createContext } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ComboboxSize,
|
|
4
|
+
SearchState,
|
|
5
|
+
State,
|
|
6
|
+
Theme,
|
|
7
|
+
TrunctationLocation,
|
|
8
|
+
} from './Combobox.types';
|
|
3
9
|
|
|
4
10
|
interface ComboboxData {
|
|
5
11
|
multiselect: boolean;
|
|
@@ -7,6 +13,9 @@ interface ComboboxData {
|
|
|
7
13
|
size: ComboboxSize;
|
|
8
14
|
withIcons: boolean;
|
|
9
15
|
disabled: boolean;
|
|
16
|
+
isOpen: boolean;
|
|
17
|
+
state: State;
|
|
18
|
+
searchState: SearchState;
|
|
10
19
|
chipTruncationLocation?: TrunctationLocation;
|
|
11
20
|
chipCharacterLimit?: number;
|
|
12
21
|
inputValue?: string;
|
|
@@ -18,4 +27,10 @@ export const ComboboxContext = createContext<ComboboxData>({
|
|
|
18
27
|
size: ComboboxSize.Default,
|
|
19
28
|
withIcons: false,
|
|
20
29
|
disabled: false,
|
|
30
|
+
isOpen: false,
|
|
31
|
+
state: State.none,
|
|
32
|
+
searchState: SearchState.unset,
|
|
21
33
|
});
|
|
34
|
+
|
|
35
|
+
export const useDarkMode = (darkMode: boolean) =>
|
|
36
|
+
darkMode ? Theme.Dark : Theme.Light;
|
package/src/ComboboxGroup.tsx
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import { css, cx } from '@leafygreen-ui/emotion';
|
|
2
2
|
import { useIdAllocator } from '@leafygreen-ui/hooks';
|
|
3
|
-
import {
|
|
3
|
+
import { palette } from '@leafygreen-ui/palette';
|
|
4
4
|
import React, { useContext } from 'react';
|
|
5
|
-
import { ComboboxGroupProps } from './Combobox.types';
|
|
6
|
-
import { ComboboxContext } from './ComboboxContext';
|
|
7
|
-
|
|
8
|
-
const comboboxGroupStyle
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
border-bottom: 1px solid var(--lg-combobox-group-border-color);
|
|
17
|
-
`;
|
|
5
|
+
import { ComboboxGroupProps, Theme } from './Combobox.types';
|
|
6
|
+
import { ComboboxContext, useDarkMode } from './ComboboxContext';
|
|
7
|
+
|
|
8
|
+
const comboboxGroupStyle: Record<Theme, string> = {
|
|
9
|
+
[Theme.Light]: css`
|
|
10
|
+
padding-top: 8px;
|
|
11
|
+
`,
|
|
12
|
+
[Theme.Dark]: css`
|
|
13
|
+
padding-top: 8px;
|
|
14
|
+
`,
|
|
15
|
+
};
|
|
18
16
|
|
|
19
17
|
const comboboxGroupLabel = css`
|
|
20
18
|
cursor: default;
|
|
@@ -27,22 +25,34 @@ const comboboxGroupLabel = css`
|
|
|
27
25
|
font-weight: bold;
|
|
28
26
|
text-transform: uppercase;
|
|
29
27
|
letter-spacing: 0.4px;
|
|
30
|
-
color: var(--lg-combobox-group-label-color);
|
|
31
28
|
`;
|
|
32
29
|
|
|
30
|
+
const comboboxGroupLabelThemeStyle: Record<Theme, string> = {
|
|
31
|
+
[Theme.Light]: css`
|
|
32
|
+
color: ${palette.gray.dark1};
|
|
33
|
+
`,
|
|
34
|
+
[Theme.Dark]: css`
|
|
35
|
+
color: ${palette.gray.light1};
|
|
36
|
+
`,
|
|
37
|
+
};
|
|
38
|
+
|
|
33
39
|
export function InternalComboboxGroup({
|
|
34
40
|
label,
|
|
35
41
|
className,
|
|
36
42
|
children,
|
|
37
43
|
}: ComboboxGroupProps): JSX.Element {
|
|
38
44
|
const { darkMode } = useContext(ComboboxContext);
|
|
45
|
+
const theme = useDarkMode(darkMode);
|
|
39
46
|
|
|
40
47
|
const groupId = useIdAllocator({ prefix: 'combobox-group' });
|
|
41
48
|
const childCount = React.Children.count(children);
|
|
42
49
|
|
|
43
50
|
return childCount > 0 ? (
|
|
44
|
-
<div className={cx(comboboxGroupStyle
|
|
45
|
-
<div
|
|
51
|
+
<div className={cx(comboboxGroupStyle[theme], className)}>
|
|
52
|
+
<div
|
|
53
|
+
className={cx(comboboxGroupLabel, comboboxGroupLabelThemeStyle[theme])}
|
|
54
|
+
id={groupId}
|
|
55
|
+
>
|
|
46
56
|
{label}
|
|
47
57
|
</div>
|
|
48
58
|
<div role="group" aria-labelledby={groupId}>
|