@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.
- package/CHANGELOG.md +28 -0
- package/dist/{Chip.d.ts → Chip/Chip.d.ts} +1 -1
- package/dist/Chip/Chip.d.ts.map +1 -0
- package/dist/Chip/Chip.styles.d.ts +23 -0
- package/dist/Chip/Chip.styles.d.ts.map +1 -0
- package/dist/Chip/index.d.ts +2 -0
- package/dist/Chip/index.d.ts.map +1 -0
- package/dist/{Combobox.d.ts → Combobox/Combobox.d.ts} +1 -1
- package/dist/Combobox/Combobox.d.ts.map +1 -0
- package/dist/{Combobox.styles.d.ts → Combobox/Combobox.styles.d.ts} +1 -12
- package/dist/Combobox/Combobox.styles.d.ts.map +1 -0
- package/dist/Combobox/index.d.ts +2 -0
- package/dist/Combobox/index.d.ts.map +1 -0
- package/dist/{ComboboxContext.d.ts → ComboboxContext/ComboboxContext.d.ts} +1 -4
- package/dist/ComboboxContext/ComboboxContext.d.ts.map +1 -0
- package/dist/ComboboxContext/index.d.ts +2 -0
- package/dist/ComboboxContext/index.d.ts.map +1 -0
- package/dist/{ComboboxGroup.d.ts → ComboboxGroup/ComboboxGroup.d.ts} +3 -4
- package/dist/ComboboxGroup/ComboboxGroup.d.ts.map +1 -0
- package/dist/ComboboxGroup/ComboboxGroup.styles.d.ts +5 -0
- package/dist/ComboboxGroup/ComboboxGroup.styles.d.ts.map +1 -0
- package/dist/ComboboxGroup/index.d.ts +2 -0
- package/dist/ComboboxGroup/index.d.ts.map +1 -0
- package/dist/ComboboxMenu/ComboboxMenu.d.ts.map +1 -1
- package/dist/ComboboxMenu/index.d.ts +2 -0
- package/dist/ComboboxMenu/index.d.ts.map +1 -0
- package/dist/{ComboboxOption.d.ts → ComboboxOption/ComboboxOption.d.ts} +5 -4
- package/dist/ComboboxOption/ComboboxOption.d.ts.map +1 -0
- package/dist/ComboboxOption/ComboboxOption.styles.d.ts +11 -0
- package/dist/ComboboxOption/ComboboxOption.styles.d.ts.map +1 -0
- package/dist/ComboboxOption/OptionContent.d.ts +4 -0
- package/dist/ComboboxOption/OptionContent.d.ts.map +1 -0
- package/dist/ComboboxOption/index.d.ts +2 -0
- package/dist/ComboboxOption/index.d.ts.map +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{ComboboxTestUtils.d.ts → utils/ComboboxTestUtils.d.ts} +1 -1
- package/dist/utils/ComboboxTestUtils.d.ts.map +1 -0
- package/package.json +11 -10
- package/src/Chip/Chip.styles.ts +150 -0
- package/src/{Chip.tsx → Chip/Chip.tsx} +18 -138
- package/src/Chip/index.ts +1 -0
- package/src/{Combobox.spec.tsx → Combobox/Combobox.spec.tsx} +2 -2
- package/src/{Combobox.story.tsx → Combobox/Combobox.story.tsx} +3 -3
- package/src/{Combobox.styles.ts → Combobox/Combobox.styles.ts} +5 -19
- package/src/{Combobox.tsx → Combobox/Combobox.tsx} +192 -257
- package/src/Combobox/index.ts +1 -0
- package/src/{ComboboxContext.tsx → ComboboxContext/ComboboxContext.tsx} +1 -7
- package/src/ComboboxContext/index.ts +1 -0
- package/src/ComboboxGroup/ComboboxGroup.styles.ts +34 -0
- package/src/ComboboxGroup/ComboboxGroup.tsx +53 -0
- package/src/ComboboxGroup/index.ts +1 -0
- package/src/ComboboxMenu/ComboboxMenu.tsx +3 -2
- package/src/ComboboxMenu/index.ts +1 -0
- package/src/ComboboxOption/ComboboxOption.styles.ts +68 -0
- package/src/ComboboxOption/ComboboxOption.tsx +107 -0
- package/src/ComboboxOption/OptionContent.tsx +87 -0
- package/src/ComboboxOption/index.ts +1 -0
- package/src/index.ts +1 -1
- package/src/{ComboboxTestUtils.tsx → utils/ComboboxTestUtils.tsx} +2 -2
- package/tsconfig.json +3 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/tsdoc.json +540 -3
- package/dist/Chip.d.ts.map +0 -1
- package/dist/Combobox.d.ts.map +0 -1
- package/dist/Combobox.styles.d.ts.map +0 -1
- package/dist/ComboboxContext.d.ts.map +0 -1
- package/dist/ComboboxGroup.d.ts.map +0 -1
- package/dist/ComboboxOption.d.ts.map +0 -1
- package/dist/ComboboxTestUtils.d.ts.map +0 -1
- package/dist/Menu.styles.d.ts +0 -22
- package/dist/Menu.styles.d.ts.map +0 -1
- package/src/ComboboxGroup.tsx +0 -80
- package/src/ComboboxOption.tsx +0 -329
- 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 {
|
|
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 {
|
|
31
|
-
import {
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
<
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
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
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
/>
|
|
1281
|
-
</div>
|
|
1282
|
-
{renderedInputIcons}
|
|
1283
|
-
</div>
|
|
1139
|
+
{description && (
|
|
1140
|
+
<Description darkMode={darkMode}>{description}</Description>
|
|
1141
|
+
)}
|
|
1142
|
+
</div>
|
|
1143
|
+
)}
|
|
1284
1144
|
|
|
1285
|
-
|
|
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
|
-
|
|
1289
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
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 '
|
|
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';
|