@leafygreen-ui/combobox 1.2.2 → 2.0.2
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 +43 -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/ComboboxTestUtils.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/dist/utils/wrapJSX.d.ts.map +1 -1
- package/package.json +12 -9
- package/src/Chip.tsx +125 -91
- package/src/Combobox.spec.tsx +3 -1
- package/src/Combobox.story.tsx +4 -2
- package/src/Combobox.styles.ts +250 -299
- package/src/Combobox.tsx +162 -190
- 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/ComboboxTestUtils.tsx +3 -2
- package/src/Menu.styles.ts +110 -0
- package/src/utils/ComboboxUtils.spec.tsx +23 -0
- package/src/utils/wrapJSX.tsx +3 -1
- package/tsconfig.json +0 -3
- package/tsconfig.tsbuildinfo +1 -1
package/src/Combobox.tsx
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import clone from 'lodash/clone';
|
|
2
|
+
import isArray from 'lodash/isArray';
|
|
3
|
+
import isEqual from 'lodash/isEqual';
|
|
4
|
+
import isNull from 'lodash/isNull';
|
|
5
|
+
import isString from 'lodash/isString';
|
|
6
|
+
import isUndefined from 'lodash/isUndefined';
|
|
1
7
|
import React, {
|
|
2
8
|
useCallback,
|
|
3
9
|
useEffect,
|
|
@@ -5,11 +11,8 @@ import React, {
|
|
|
5
11
|
useRef,
|
|
6
12
|
useState,
|
|
7
13
|
} from 'react';
|
|
8
|
-
import { clone, isArray, isEqual, isNull, isString, isUndefined } from 'lodash';
|
|
9
14
|
import { Description, Label } from '@leafygreen-ui/typography';
|
|
10
|
-
import Popover from '@leafygreen-ui/popover';
|
|
11
15
|
import {
|
|
12
|
-
useAvailableSpace,
|
|
13
16
|
useDynamicRefs,
|
|
14
17
|
useEventListener,
|
|
15
18
|
useIdAllocator,
|
|
@@ -17,8 +20,8 @@ import {
|
|
|
17
20
|
} from '@leafygreen-ui/hooks';
|
|
18
21
|
import Icon from '@leafygreen-ui/icon';
|
|
19
22
|
import IconButton from '@leafygreen-ui/icon-button';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
23
|
+
import { cx } from '@leafygreen-ui/emotion';
|
|
24
|
+
import { palette } from '@leafygreen-ui/palette';
|
|
22
25
|
import { consoleOnce, isComponentType, keyMap } from '@leafygreen-ui/lib';
|
|
23
26
|
import {
|
|
24
27
|
ComboboxProps,
|
|
@@ -28,28 +31,8 @@ import {
|
|
|
28
31
|
OptionObject,
|
|
29
32
|
ComboboxElement,
|
|
30
33
|
ComboboxSize,
|
|
34
|
+
State,
|
|
31
35
|
} 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
36
|
import {
|
|
54
37
|
flattenChildren,
|
|
55
38
|
getOptionObjectFromValue,
|
|
@@ -57,6 +40,32 @@ import {
|
|
|
57
40
|
getValueForDisplayName,
|
|
58
41
|
getNameAndValue,
|
|
59
42
|
} from './utils';
|
|
43
|
+
import { ComboboxContext, useDarkMode } from './ComboboxContext';
|
|
44
|
+
import { InternalComboboxGroup } from './ComboboxGroup';
|
|
45
|
+
import { InternalComboboxOption } from './ComboboxOption';
|
|
46
|
+
import { Chip } from './Chip';
|
|
47
|
+
import {
|
|
48
|
+
comboboxFocusStyle,
|
|
49
|
+
inputWrapperStyle,
|
|
50
|
+
baseComboboxStyles,
|
|
51
|
+
comboboxThemeStyles,
|
|
52
|
+
comboboxSizeStyles,
|
|
53
|
+
comboboxDisabledStyles,
|
|
54
|
+
comboboxErrorStyles,
|
|
55
|
+
comboboxParentStyle,
|
|
56
|
+
baseInputElementStyle,
|
|
57
|
+
inputElementSizeStyle,
|
|
58
|
+
inputElementTransitionStyles,
|
|
59
|
+
multiselectInputElementStyle,
|
|
60
|
+
clearButtonStyle,
|
|
61
|
+
endIconStyle,
|
|
62
|
+
errorMessageThemeStyle,
|
|
63
|
+
errorMessageSizeStyle,
|
|
64
|
+
multiselectInputElementPadding,
|
|
65
|
+
labelDescriptionContainerStyle,
|
|
66
|
+
inputElementThemeStyle,
|
|
67
|
+
} from './Combobox.styles';
|
|
68
|
+
import { ComboboxMenu } from './ComboboxMenu/ComboboxMenu';
|
|
60
69
|
|
|
61
70
|
/**
|
|
62
71
|
* Combobox is a combination of a Select and TextInput,
|
|
@@ -97,6 +106,7 @@ export default function Combobox<M extends boolean>({
|
|
|
97
106
|
popoverZIndex,
|
|
98
107
|
...rest
|
|
99
108
|
}: ComboboxProps<M>) {
|
|
109
|
+
const theme = useDarkMode(darkMode);
|
|
100
110
|
const getOptionRef = useDynamicRefs<HTMLLIElement>({ prefix: 'option' });
|
|
101
111
|
const getChipRef = useDynamicRefs<HTMLSpanElement>({ prefix: 'chip' });
|
|
102
112
|
|
|
@@ -357,6 +367,8 @@ export default function Combobox<M extends boolean>({
|
|
|
357
367
|
const [focusedElementName, trackFocusedElement] = useState<
|
|
358
368
|
ComboboxElement | undefined
|
|
359
369
|
>();
|
|
370
|
+
const isElementFocused = (elementName: ComboboxElement) =>
|
|
371
|
+
elementName === focusedElementName;
|
|
360
372
|
|
|
361
373
|
type Direction = 'next' | 'prev' | 'first' | 'last';
|
|
362
374
|
|
|
@@ -595,74 +607,75 @@ export default function Combobox<M extends boolean>({
|
|
|
595
607
|
*/
|
|
596
608
|
|
|
597
609
|
/**
|
|
598
|
-
* Callback to render
|
|
610
|
+
* Callback to render a child as an <InternalComboboxOption> element
|
|
599
611
|
*/
|
|
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
|
-
}
|
|
612
|
+
const renderOption = useCallback(
|
|
613
|
+
(child: React.ReactNode) => {
|
|
614
|
+
if (isComponentType(child, 'ComboboxOption')) {
|
|
615
|
+
const { value, displayName } = getNameAndValue(child.props);
|
|
616
|
+
|
|
617
|
+
if (shouldOptionBeVisible(value)) {
|
|
618
|
+
const { className, glyph, disabled } = child.props;
|
|
619
|
+
const index = allOptions.findIndex(opt => opt.value === value);
|
|
620
|
+
|
|
621
|
+
const isFocused = highlightedOption === value;
|
|
622
|
+
const isSelected = isMultiselect(selection)
|
|
623
|
+
? selection.includes(value)
|
|
624
|
+
: selection === value;
|
|
625
|
+
|
|
626
|
+
const setSelected = () => {
|
|
627
|
+
sethighlightedOption(value);
|
|
628
|
+
updateSelection(value);
|
|
629
|
+
setInputFocus();
|
|
630
|
+
|
|
631
|
+
if (value === selection) {
|
|
632
|
+
closeMenu();
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
const optionRef = getOptionRef(value);
|
|
637
|
+
|
|
638
|
+
return (
|
|
639
|
+
<InternalComboboxOption
|
|
640
|
+
value={value}
|
|
641
|
+
displayName={displayName}
|
|
642
|
+
isFocused={isFocused}
|
|
643
|
+
isSelected={isSelected}
|
|
644
|
+
disabled={disabled}
|
|
645
|
+
setSelected={setSelected}
|
|
646
|
+
glyph={glyph}
|
|
647
|
+
className={className}
|
|
648
|
+
index={index}
|
|
649
|
+
ref={optionRef}
|
|
650
|
+
/>
|
|
651
|
+
);
|
|
655
652
|
}
|
|
656
|
-
})
|
|
653
|
+
} else if (isComponentType(child, 'ComboboxGroup')) {
|
|
654
|
+
const nestedChildren = React.Children.map(
|
|
655
|
+
child.props.children,
|
|
656
|
+
renderOption,
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
if (nestedChildren && nestedChildren?.length > 0) {
|
|
660
|
+
return (
|
|
661
|
+
<InternalComboboxGroup
|
|
662
|
+
label={child.props.label}
|
|
663
|
+
className={child.props.className}
|
|
664
|
+
>
|
|
665
|
+
{React.Children.map(nestedChildren, renderOption)}
|
|
666
|
+
</InternalComboboxGroup>
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
657
670
|
},
|
|
658
671
|
[
|
|
659
672
|
allOptions,
|
|
660
|
-
highlightedOption,
|
|
661
673
|
getOptionRef,
|
|
674
|
+
highlightedOption,
|
|
662
675
|
isMultiselect,
|
|
663
|
-
shouldOptionBeVisible,
|
|
664
676
|
selection,
|
|
665
677
|
setInputFocus,
|
|
678
|
+
shouldOptionBeVisible,
|
|
666
679
|
updateSelection,
|
|
667
680
|
],
|
|
668
681
|
);
|
|
@@ -671,8 +684,8 @@ export default function Combobox<M extends boolean>({
|
|
|
671
684
|
* The rendered JSX elements for the options
|
|
672
685
|
*/
|
|
673
686
|
const renderedOptionsJSX = useMemo(
|
|
674
|
-
() =>
|
|
675
|
-
[children,
|
|
687
|
+
() => React.Children.map(children, renderOption),
|
|
688
|
+
[children, renderOption],
|
|
676
689
|
);
|
|
677
690
|
|
|
678
691
|
/**
|
|
@@ -752,15 +765,20 @@ export default function Combobox<M extends boolean>({
|
|
|
752
765
|
ref={clearButtonRef}
|
|
753
766
|
onClick={handleClearButtonClick}
|
|
754
767
|
onFocus={handleClearButtonFocus}
|
|
755
|
-
className={cx(clearButtonStyle
|
|
768
|
+
className={cx(clearButtonStyle)}
|
|
769
|
+
darkMode={darkMode}
|
|
756
770
|
>
|
|
757
771
|
<Icon glyph="XWithCircle" />
|
|
758
772
|
</IconButton>
|
|
759
773
|
)}
|
|
760
774
|
{state === 'error' ? (
|
|
761
|
-
<Icon
|
|
775
|
+
<Icon
|
|
776
|
+
glyph="Warning"
|
|
777
|
+
color={darkMode ? palette.red.light1 : palette.red.base}
|
|
778
|
+
className={endIconStyle(size)}
|
|
779
|
+
/>
|
|
762
780
|
) : (
|
|
763
|
-
<Icon glyph="CaretDown" className={
|
|
781
|
+
<Icon glyph="CaretDown" className={endIconStyle(size)} />
|
|
764
782
|
)}
|
|
765
783
|
</>
|
|
766
784
|
);
|
|
@@ -769,6 +787,8 @@ export default function Combobox<M extends boolean>({
|
|
|
769
787
|
doesSelectionExist,
|
|
770
788
|
disabled,
|
|
771
789
|
state,
|
|
790
|
+
darkMode,
|
|
791
|
+
size,
|
|
772
792
|
updateSelection,
|
|
773
793
|
onClear,
|
|
774
794
|
onFilter,
|
|
@@ -906,62 +926,11 @@ export default function Combobox<M extends boolean>({
|
|
|
906
926
|
setMenuWidth(comboboxRef.current?.clientWidth ?? 0);
|
|
907
927
|
}, [comboboxRef, isOpen, highlightedOption, selection]);
|
|
908
928
|
|
|
909
|
-
// Handler fired when the
|
|
929
|
+
// Handler fired when the menu has finished transitioning in/out
|
|
910
930
|
const handleTransitionEnd = () => {
|
|
911
931
|
setMenuWidth(comboboxRef.current?.clientWidth ?? 0);
|
|
912
932
|
};
|
|
913
933
|
|
|
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
934
|
/**
|
|
966
935
|
*
|
|
967
936
|
* Event Handlers
|
|
@@ -1214,32 +1183,26 @@ export default function Combobox<M extends boolean>({
|
|
|
1214
1183
|
size,
|
|
1215
1184
|
withIcons,
|
|
1216
1185
|
disabled,
|
|
1186
|
+
isOpen,
|
|
1187
|
+
state,
|
|
1188
|
+
searchState,
|
|
1217
1189
|
chipTruncationLocation,
|
|
1218
1190
|
chipCharacterLimit,
|
|
1219
1191
|
inputValue,
|
|
1220
1192
|
}}
|
|
1221
1193
|
>
|
|
1222
1194
|
<div
|
|
1223
|
-
className={cx(
|
|
1224
|
-
comboboxParentStyle({ darkMode, size, overflow }),
|
|
1225
|
-
className,
|
|
1226
|
-
)}
|
|
1195
|
+
className={cx(comboboxParentStyle(size, overflow), className)}
|
|
1227
1196
|
{...rest}
|
|
1228
1197
|
>
|
|
1229
|
-
<div>
|
|
1198
|
+
<div className={labelDescriptionContainerStyle}>
|
|
1230
1199
|
{label && (
|
|
1231
|
-
<Label
|
|
1232
|
-
id={labelId}
|
|
1233
|
-
htmlFor={inputId}
|
|
1234
|
-
className={_tempLabelDescriptionOverrideStyle}
|
|
1235
|
-
>
|
|
1200
|
+
<Label id={labelId} htmlFor={inputId} darkMode={darkMode}>
|
|
1236
1201
|
{label}
|
|
1237
1202
|
</Label>
|
|
1238
1203
|
)}
|
|
1239
1204
|
{description && (
|
|
1240
|
-
<Description
|
|
1241
|
-
{description}
|
|
1242
|
-
</Description>
|
|
1205
|
+
<Description darkMode={darkMode}>{description}</Description>
|
|
1243
1206
|
)}
|
|
1244
1207
|
</div>
|
|
1245
1208
|
|
|
@@ -1252,25 +1215,29 @@ export default function Combobox<M extends boolean>({
|
|
|
1252
1215
|
aria-controls={menuId}
|
|
1253
1216
|
aria-owns={menuId}
|
|
1254
1217
|
tabIndex={-1}
|
|
1255
|
-
className={cx(comboboxStyle, {
|
|
1256
|
-
[comboboxFocusStyle]: focusedElementName === ComboboxElement.Input,
|
|
1257
|
-
})}
|
|
1258
1218
|
onMouseDown={handleInputWrapperMousedown}
|
|
1259
1219
|
onClick={handleComboboxClick}
|
|
1260
1220
|
onFocus={handleComboboxFocus}
|
|
1261
1221
|
onKeyDown={handleKeyDown}
|
|
1262
1222
|
onTransitionEnd={handleTransitionEnd}
|
|
1263
|
-
|
|
1264
|
-
|
|
1223
|
+
className={cx(
|
|
1224
|
+
baseComboboxStyles,
|
|
1225
|
+
comboboxThemeStyles[theme],
|
|
1226
|
+
comboboxSizeStyles(size),
|
|
1227
|
+
{
|
|
1228
|
+
[comboboxDisabledStyles[theme]]: disabled,
|
|
1229
|
+
[comboboxErrorStyles[theme]]: state === State.error,
|
|
1230
|
+
[comboboxFocusStyle[theme]]: isElementFocused(
|
|
1231
|
+
ComboboxElement.Input,
|
|
1232
|
+
),
|
|
1233
|
+
},
|
|
1234
|
+
)}
|
|
1265
1235
|
>
|
|
1266
1236
|
<div
|
|
1267
1237
|
ref={inputWrapperRef}
|
|
1268
1238
|
className={inputWrapperStyle({
|
|
1269
|
-
overflow,
|
|
1270
|
-
isOpen,
|
|
1271
|
-
selection,
|
|
1272
1239
|
size,
|
|
1273
|
-
|
|
1240
|
+
overflow,
|
|
1274
1241
|
})}
|
|
1275
1242
|
>
|
|
1276
1243
|
{renderedChips}
|
|
@@ -1281,7 +1248,18 @@ export default function Combobox<M extends boolean>({
|
|
|
1281
1248
|
aria-labelledby={labelId}
|
|
1282
1249
|
ref={inputRef}
|
|
1283
1250
|
id={inputId}
|
|
1284
|
-
className={
|
|
1251
|
+
className={cx(
|
|
1252
|
+
baseInputElementStyle,
|
|
1253
|
+
inputElementSizeStyle[size],
|
|
1254
|
+
inputElementThemeStyle[theme],
|
|
1255
|
+
inputElementTransitionStyles(isOpen, overflow),
|
|
1256
|
+
{
|
|
1257
|
+
[multiselectInputElementStyle(size, inputValue)]:
|
|
1258
|
+
isMultiselect(selection),
|
|
1259
|
+
[multiselectInputElementPadding(selection)]:
|
|
1260
|
+
isMultiselect(selection),
|
|
1261
|
+
},
|
|
1262
|
+
)}
|
|
1285
1263
|
placeholder={placeholderValue}
|
|
1286
1264
|
disabled={disabled ?? undefined}
|
|
1287
1265
|
onChange={handleInputChange}
|
|
@@ -1293,39 +1271,33 @@ export default function Combobox<M extends boolean>({
|
|
|
1293
1271
|
</div>
|
|
1294
1272
|
|
|
1295
1273
|
{state === 'error' && errorMessage && (
|
|
1296
|
-
<div
|
|
1274
|
+
<div
|
|
1275
|
+
className={cx(
|
|
1276
|
+
errorMessageThemeStyle[theme],
|
|
1277
|
+
errorMessageSizeStyle[size],
|
|
1278
|
+
)}
|
|
1279
|
+
>
|
|
1280
|
+
{errorMessage}
|
|
1281
|
+
</div>
|
|
1297
1282
|
)}
|
|
1298
1283
|
|
|
1299
1284
|
{/******* /
|
|
1300
1285
|
* Menu *
|
|
1301
1286
|
/ *******/}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
justify="middle"
|
|
1287
|
+
|
|
1288
|
+
<ComboboxMenu
|
|
1289
|
+
id={menuId}
|
|
1290
|
+
labelId={labelId}
|
|
1307
1291
|
refEl={comboboxRef}
|
|
1308
|
-
|
|
1309
|
-
|
|
1292
|
+
ref={menuRef}
|
|
1293
|
+
menuWidth={menuWidth}
|
|
1294
|
+
searchLoadingMessage={searchLoadingMessage}
|
|
1295
|
+
searchErrorMessage={searchErrorMessage}
|
|
1296
|
+
searchEmptyMessage={searchEmptyMessage}
|
|
1310
1297
|
{...popoverProps}
|
|
1311
1298
|
>
|
|
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>
|
|
1299
|
+
{renderedOptionsJSX}
|
|
1300
|
+
</ComboboxMenu>
|
|
1329
1301
|
</div>
|
|
1330
1302
|
</ComboboxContext.Provider>
|
|
1331
1303
|
);
|
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}>
|