@kaizen/components 2.0.0 → 2.0.1
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/alpha/README.md +28 -0
- package/alpha/package.json +5 -0
- package/dist/cjs/alpha.cjs +1 -0
- package/dist/cjs/src/Notification/InlineNotification/InlineNotification.cjs +1 -1
- package/dist/cjs/src/__alpha__/SingleSelect/SingleSelect.cjs +35 -74
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/ComboBox/ComboBox.cjs +105 -0
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/ComboBox/ComboBox.module.css.cjs +11 -0
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/ComboBoxTrigger/ComboBoxTrigger.cjs +112 -0
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/ComboBoxTrigger/ComboBoxTrigger.module.css.cjs +16 -0
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/List/List.cjs +35 -10
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/ListItem/ListItem.cjs +61 -8
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/ListItem/ListItem.module.css.cjs +10 -1
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/ListSection/ListSection.cjs +38 -9
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/ListSection/ListSection.module.css.cjs +4 -1
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.cjs +60 -30
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.module.css.cjs +2 -1
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePopoverPositioning.cjs +2 -1
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePositioningStyles.cjs +4 -2
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Select/Select.cjs +87 -0
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Select/Select.module.css.cjs +11 -0
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/SelectTrigger/SelectTrigger.cjs +52 -0
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/SelectTrigger/SelectTrigger.module.css.cjs +13 -0
- package/dist/esm/alpha.mjs +1 -1
- package/dist/esm/src/Notification/InlineNotification/InlineNotification.mjs +1 -1
- package/dist/esm/src/__alpha__/SingleSelect/SingleSelect.mjs +39 -73
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/ComboBox/ComboBox.mjs +96 -0
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/ComboBox/ComboBox.module.css.mjs +9 -0
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/ComboBoxTrigger/ComboBoxTrigger.mjs +103 -0
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/ComboBoxTrigger/ComboBoxTrigger.module.css.mjs +14 -0
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/List/List.mjs +37 -14
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/ListItem/ListItem.mjs +63 -13
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/ListItem/ListItem.module.css.mjs +10 -1
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/ListSection/ListSection.mjs +41 -15
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/ListSection/ListSection.module.css.mjs +4 -1
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.mjs +69 -43
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.module.css.mjs +2 -1
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePopoverPositioning.mjs +2 -1
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePositioningStyles.mjs +4 -2
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Select/Select.mjs +78 -0
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Select/Select.module.css.mjs +9 -0
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/SelectTrigger/SelectTrigger.mjs +43 -0
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/SelectTrigger/SelectTrigger.module.css.mjs +11 -0
- package/dist/styles.css +389 -25
- package/dist/types/__alpha__/SingleSelect/SingleSelect.d.ts +14 -19
- package/dist/types/__alpha__/SingleSelect/_docs/mockData.d.ts +3 -0
- package/dist/types/__alpha__/SingleSelect/context/SingleSelectContext.d.ts +15 -7
- package/dist/types/__alpha__/SingleSelect/subcomponents/ComboBox/ComboBox.d.ts +2 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/ComboBox/index.d.ts +1 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/ComboBoxTrigger/ComboBoxTrigger.d.ts +2 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/ComboBoxTrigger/index.d.ts +1 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/List/List.d.ts +2 -7
- package/dist/types/__alpha__/SingleSelect/subcomponents/ListItem/ListItem.d.ts +2 -7
- package/dist/types/__alpha__/SingleSelect/subcomponents/ListSection/ListSection.d.ts +2 -9
- package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/Popover.d.ts +3 -6
- package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/utils/index.d.ts +1 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/utils/usePopoverPositioning.d.ts +1 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/Popover/utils/usePositioningStyles.d.ts +1 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/Select/Select.d.ts +2 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/Select/index.d.ts +1 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/SelectTrigger/SelectTrigger.d.ts +2 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/SelectTrigger/index.d.ts +1 -0
- package/dist/types/__alpha__/SingleSelect/subcomponents/index.d.ts +4 -1
- package/dist/types/__alpha__/SingleSelect/types.d.ts +68 -11
- package/locales/en.json +9 -1
- package/package.json +9 -2
- package/src/Notification/InlineNotification/InlineNotification.tsx +1 -1
- package/src/__alpha__/SingleSelect/SingleSelect.tsx +35 -88
- package/src/__alpha__/SingleSelect/_docs/SingleSelect.mdx +96 -6
- package/src/__alpha__/SingleSelect/_docs/SingleSelect.spec.stories.tsx +22 -24
- package/src/__alpha__/SingleSelect/_docs/SingleSelect.stickersheet.stories.tsx +389 -33
- package/src/__alpha__/SingleSelect/_docs/SingleSelect.stories.tsx +41 -22
- package/src/__alpha__/SingleSelect/_docs/mockData.ts +20 -14
- package/src/__alpha__/SingleSelect/context/SingleSelectContext.tsx +18 -7
- package/src/__alpha__/SingleSelect/subcomponents/ComboBox/ComboBox.module.css +35 -0
- package/src/__alpha__/SingleSelect/subcomponents/ComboBox/ComboBox.tsx +106 -0
- package/src/__alpha__/SingleSelect/subcomponents/ComboBox/index.ts +1 -0
- package/src/__alpha__/SingleSelect/subcomponents/ComboBoxTrigger/ComboBoxTrigger.module.css +130 -0
- package/src/__alpha__/SingleSelect/subcomponents/ComboBoxTrigger/ComboBoxTrigger.tsx +121 -0
- package/src/__alpha__/SingleSelect/subcomponents/ComboBoxTrigger/index.ts +1 -0
- package/src/__alpha__/SingleSelect/subcomponents/List/List.module.css +5 -0
- package/src/__alpha__/SingleSelect/subcomponents/List/List.tsx +36 -13
- package/src/__alpha__/SingleSelect/subcomponents/ListItem/ListItem.module.css +84 -3
- package/src/__alpha__/SingleSelect/subcomponents/ListItem/ListItem.tsx +67 -11
- package/src/__alpha__/SingleSelect/subcomponents/ListSection/ListSection.module.css +20 -5
- package/src/__alpha__/SingleSelect/subcomponents/ListSection/ListSection.tsx +46 -19
- package/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.module.css +7 -5
- package/src/__alpha__/SingleSelect/subcomponents/Popover/Popover.tsx +90 -37
- package/src/__alpha__/SingleSelect/subcomponents/Popover/utils/index.ts +1 -0
- package/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePopoverPositioning.ts +2 -2
- package/src/__alpha__/SingleSelect/subcomponents/Popover/utils/usePositioningStyles.ts +9 -8
- package/src/__alpha__/SingleSelect/subcomponents/Select/Select.module.css +35 -0
- package/src/__alpha__/SingleSelect/subcomponents/Select/Select.tsx +84 -0
- package/src/__alpha__/SingleSelect/subcomponents/Select/index.ts +1 -0
- package/src/__alpha__/SingleSelect/subcomponents/SelectTrigger/SelectTrigger.module.css +77 -0
- package/src/__alpha__/SingleSelect/subcomponents/SelectTrigger/SelectTrigger.tsx +52 -0
- package/src/__alpha__/SingleSelect/subcomponents/SelectTrigger/index.ts +1 -0
- package/src/__alpha__/SingleSelect/subcomponents/index.ts +4 -1
- package/src/__alpha__/SingleSelect/types.ts +94 -14
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.cjs +0 -57
- package/dist/cjs/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.module.css.cjs +0 -6
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.mjs +0 -49
- package/dist/esm/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.module.css.mjs +0 -4
- package/dist/types/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.d.ts +0 -2
- package/dist/types/__alpha__/SingleSelect/subcomponents/Trigger/index.d.ts +0 -1
- package/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.module.css +0 -19
- package/src/__alpha__/SingleSelect/subcomponents/Trigger/Trigger.tsx +0 -35
- package/src/__alpha__/SingleSelect/subcomponents/Trigger/index.ts +0 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export * from './List';
|
|
2
2
|
export * from './ListSection';
|
|
3
3
|
export * from './ListItem';
|
|
4
|
-
export * from './
|
|
4
|
+
export * from './SelectTrigger';
|
|
5
|
+
export * from './ComboBoxTrigger';
|
|
5
6
|
export * from './Popover';
|
|
7
|
+
export * from './Select';
|
|
8
|
+
export * from './ComboBox';
|
|
@@ -1,25 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import type { DOMAttributes, RefObject } from 'react';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
import { type ComboBoxState, type ComboBoxStateOptions } from '@react-stately/combobox';
|
|
4
|
+
import type { ListState } from '@react-stately/list';
|
|
5
|
+
import { type SelectState, type SelectStateOptions } from '@react-stately/select';
|
|
6
|
+
import { type Key, type Node } from '@react-types/shared';
|
|
7
|
+
import { type FocusableElement } from '@react-types/shared/src/dom';
|
|
8
|
+
import type { AriaButtonProps, AriaListBoxOptions, AriaPopoverProps } from 'react-aria';
|
|
3
9
|
export type SelectItem = {
|
|
4
10
|
label: string;
|
|
5
11
|
value: string;
|
|
12
|
+
key: Key;
|
|
6
13
|
};
|
|
7
14
|
export type SelectSection = {
|
|
8
15
|
label: string;
|
|
9
16
|
options: SelectItem[];
|
|
10
17
|
};
|
|
11
|
-
export type
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
export type SelectLabel = {
|
|
19
|
+
labelHidden: true;
|
|
20
|
+
label: string;
|
|
21
|
+
} | {
|
|
22
|
+
labelHidden?: false;
|
|
23
|
+
label: React.ReactNode;
|
|
24
|
+
};
|
|
25
|
+
export type SelectBaseProps = {
|
|
26
|
+
variant?: 'primary' | 'secondary';
|
|
27
|
+
size?: 'small' | 'medium' | 'large';
|
|
28
|
+
labelPosition?: 'top' | 'side';
|
|
29
|
+
isReadOnly?: boolean;
|
|
30
|
+
} & SelectLabel;
|
|
31
|
+
export type SelectProps<T extends SelectItem> = Omit<SelectStateOptions<T>, 'label' | 'defaultFilter' | 'menuTrigger' | 'allowsCustomValue'> & SelectBaseProps;
|
|
32
|
+
export type ComboBoxProps<T extends SelectItem> = Omit<ComboBoxStateOptions<T>, 'label' | 'defaultFilter' | 'menuTrigger' | 'allowsCustomValue'> & SelectBaseProps;
|
|
33
|
+
export type SingleSelectProps<T extends SelectItem> = (ComboBoxProps<T> & {
|
|
34
|
+
isComboBox?: true;
|
|
35
|
+
}) | (SelectProps<T> & {
|
|
36
|
+
isComboBox?: false;
|
|
37
|
+
});
|
|
38
|
+
export type SelectTriggerProps = {
|
|
39
|
+
triggerProps: AriaButtonProps<'button'>;
|
|
40
|
+
valueProps: DOMAttributes<FocusableElement>;
|
|
41
|
+
buttonRef: React.MutableRefObject<HTMLButtonElement | null>;
|
|
42
|
+
};
|
|
43
|
+
export type ComboBoxTriggerProps = {
|
|
44
|
+
inputProps: React.InputHTMLAttributes<HTMLInputElement>;
|
|
45
|
+
inputRef: React.MutableRefObject<HTMLInputElement | null>;
|
|
46
|
+
buttonProps: AriaButtonProps<'button'>;
|
|
47
|
+
buttonRef: React.MutableRefObject<HTMLButtonElement | null>;
|
|
48
|
+
triggerWrapperRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
49
|
+
clearButtonRef: React.MutableRefObject<HTMLButtonElement | null>;
|
|
50
|
+
};
|
|
51
|
+
export type ChevronButtonProps = AriaButtonProps<'button'> & {
|
|
52
|
+
buttonRef: React.MutableRefObject<HTMLButtonElement | null>;
|
|
15
53
|
};
|
|
16
|
-
export type
|
|
17
|
-
|
|
54
|
+
export type ClearButtonProps = {
|
|
55
|
+
clearButtonRef: React.RefObject<HTMLButtonElement>;
|
|
56
|
+
inputRef: React.RefObject<HTMLInputElement>;
|
|
18
57
|
};
|
|
19
|
-
export type PopoverProps = {
|
|
20
|
-
|
|
58
|
+
export type PopoverProps<T extends SelectItem> = AriaPopoverProps & {
|
|
59
|
+
state: ComboBoxState<T> | SelectState<T>;
|
|
60
|
+
triggerRef: React.RefObject<HTMLElement>;
|
|
21
61
|
popoverRef: React.RefObject<HTMLDivElement>;
|
|
22
|
-
|
|
62
|
+
clearButtonRef?: React.RefObject<HTMLButtonElement>;
|
|
63
|
+
children: React.ReactNode;
|
|
23
64
|
};
|
|
24
65
|
type PositionDataProp = number | string | undefined;
|
|
25
66
|
export type PositionData = {
|
|
@@ -42,4 +83,20 @@ export type UsePopoverPositioningProps = {
|
|
|
42
83
|
offset?: number;
|
|
43
84
|
preferredPlacement?: 'top' | 'bottom';
|
|
44
85
|
};
|
|
86
|
+
export type ListProps<T extends SelectItem> = {
|
|
87
|
+
state: ComboBoxState<T> | SelectState<T>;
|
|
88
|
+
listBoxOptions: AriaListBoxOptions<T>;
|
|
89
|
+
listBoxRef: React.RefObject<HTMLUListElement>;
|
|
90
|
+
};
|
|
91
|
+
export type ListItemProps<T extends SelectItem> = {
|
|
92
|
+
item: Node<T>;
|
|
93
|
+
state: ListState<T>;
|
|
94
|
+
selectedIcon?: 'check' | 'radio';
|
|
95
|
+
selectedPosition?: 'start' | 'end';
|
|
96
|
+
className?: string;
|
|
97
|
+
};
|
|
98
|
+
export type ListSectionProps<T extends SelectItem> = {
|
|
99
|
+
section: Node<T>;
|
|
100
|
+
state: ComboBoxState<T> | SelectState<T>;
|
|
101
|
+
};
|
|
45
102
|
export {};
|
package/locales/en.json
CHANGED
|
@@ -185,8 +185,16 @@
|
|
|
185
185
|
"description": "Prompts user to interact with button to hide information",
|
|
186
186
|
"message": "Hide information:"
|
|
187
187
|
},
|
|
188
|
+
"singleSelect.chevronButton": {
|
|
189
|
+
"description": "Aria label text for the SingleSelect button to open and close suggestions list",
|
|
190
|
+
"message": "Show suggestions for {field}"
|
|
191
|
+
},
|
|
192
|
+
"singleSelect.clearButtonAlt": {
|
|
193
|
+
"description": "Alt text for the clear selection button",
|
|
194
|
+
"message": "Clear {field} selection"
|
|
195
|
+
},
|
|
188
196
|
"splitButton.dropdownButton.label": {
|
|
189
197
|
"description": "Label for a dropdown menu holding additional actions",
|
|
190
198
|
"message": "Additional actions"
|
|
191
199
|
}
|
|
192
|
-
}
|
|
200
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaizen/components",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Kaizen component library",
|
|
5
5
|
"author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
|
|
6
6
|
"homepage": "https://cultureamp.design",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"files": [
|
|
16
16
|
"bin",
|
|
17
17
|
"codemods",
|
|
18
|
+
"alpha",
|
|
18
19
|
"future",
|
|
19
20
|
"next",
|
|
20
21
|
"libs",
|
|
@@ -36,6 +37,11 @@
|
|
|
36
37
|
"import": "./dist/esm/index.mjs",
|
|
37
38
|
"require": "./dist/cjs/index.cjs"
|
|
38
39
|
},
|
|
40
|
+
"./alpha": {
|
|
41
|
+
"types": "./dist/types/__alpha__/index.d.ts",
|
|
42
|
+
"import": "./dist/esm/alpha.mjs",
|
|
43
|
+
"require": "./dist/cjs/alpha.cjs"
|
|
44
|
+
},
|
|
39
45
|
"./next": {
|
|
40
46
|
"types": "./dist/types/__next__/index.d.ts",
|
|
41
47
|
"import": "./dist/esm/next.mjs",
|
|
@@ -86,6 +92,7 @@
|
|
|
86
92
|
"@react-aria/select": "^3.15.7",
|
|
87
93
|
"@react-aria/utils": "^3.29.1",
|
|
88
94
|
"@react-stately/collections": "^3.12.5",
|
|
95
|
+
"@react-stately/combobox": "^3.11.1",
|
|
89
96
|
"@react-stately/datepicker": "^3.14.2",
|
|
90
97
|
"@react-stately/list": "^3.12.3",
|
|
91
98
|
"@react-stately/menu": "^3.9.5",
|
|
@@ -152,8 +159,8 @@
|
|
|
152
159
|
"sass": "1.79.6",
|
|
153
160
|
"serialize-query-params": "^2.0.2",
|
|
154
161
|
"svgo": "^3.3.2",
|
|
155
|
-
"tslib": "^2.8.1",
|
|
156
162
|
"ts-patch": "^3.3.0",
|
|
163
|
+
"tslib": "^2.8.1",
|
|
157
164
|
"tsx": "^4.20.3",
|
|
158
165
|
"@kaizen/design-tokens": "11.0.0"
|
|
159
166
|
},
|
|
@@ -35,7 +35,7 @@ export const InlineNotification = forwardRef<HTMLDivElement, InlineNotificationP
|
|
|
35
35
|
): JSX.Element => (
|
|
36
36
|
<GenericNotification
|
|
37
37
|
style="inline"
|
|
38
|
-
persistent={persistent
|
|
38
|
+
persistent={persistent || hideCloseIcon}
|
|
39
39
|
classNameOverride={classnames(classNameOverride, [isSubtle && styles.subtle])}
|
|
40
40
|
ref={ref}
|
|
41
41
|
{...otherProps}
|
|
@@ -1,92 +1,39 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
children,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const state = useSelectState({
|
|
22
|
-
items,
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
const handleOnSelectionChange = React.useCallback(
|
|
26
|
-
(keys: Selection): void => {
|
|
27
|
-
let key: Key | null = null
|
|
28
|
-
|
|
29
|
-
if (keys instanceof Set && keys.size > 0) {
|
|
30
|
-
key = Array.from(keys)[0]
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
state.setSelectedKey(key)
|
|
34
|
-
if (onSelectionChange) {
|
|
35
|
-
onSelectionChange(key)
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
[state, onSelectionChange],
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
// Cloning children here to allow users to pass in a custom ListItem or ListSection
|
|
42
|
-
// and still have the SingleSelect handle selection state
|
|
43
|
-
const injectedChildren = useMemo(() => {
|
|
44
|
-
if (!isValidElement(children)) return null
|
|
45
|
-
|
|
46
|
-
const selectedKeys: Iterable<Key> = state.selectedKey
|
|
47
|
-
? new Set<Key>([state.selectedKey])
|
|
48
|
-
: new Set()
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Item as StatelyItem, Section } from '@react-stately/collections'
|
|
3
|
+
import { ComboBox, Select } from './subcomponents'
|
|
4
|
+
import {
|
|
5
|
+
type ComboBoxProps,
|
|
6
|
+
type SelectItem,
|
|
7
|
+
type SelectProps,
|
|
8
|
+
type SingleSelectProps,
|
|
9
|
+
} from './types'
|
|
10
|
+
|
|
11
|
+
export const SingleSelect = <T extends SelectItem>(props: SingleSelectProps<T>): JSX.Element => {
|
|
12
|
+
const { isComboBox, children, ...rest } = props
|
|
13
|
+
|
|
14
|
+
if (isComboBox) {
|
|
15
|
+
return <ComboBox {...(rest as ComboBoxProps<T>)}>{children}</ComboBox>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return <Select {...(rest as SelectProps<T>)}>{children}</Select>
|
|
19
|
+
}
|
|
49
20
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
21
|
+
type CustomItemProps = {
|
|
22
|
+
selectedIcon?: 'check' | 'radio'
|
|
23
|
+
selectedPosition?: 'start' | 'end'
|
|
24
|
+
key: string
|
|
25
|
+
children?: React.ReactNode
|
|
26
|
+
[key: string]: any
|
|
27
|
+
}
|
|
57
28
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
setOpen: state.setOpen,
|
|
63
|
-
selectedKey: state.selectedKey,
|
|
64
|
-
items: items,
|
|
65
|
-
anchorName,
|
|
66
|
-
}}
|
|
67
|
-
>
|
|
68
|
-
<RACSelect
|
|
69
|
-
// TODO: allow user to pass in label
|
|
70
|
-
aria-label={'single-select'}
|
|
71
|
-
onSelectionChange={(key) =>
|
|
72
|
-
handleOnSelectionChange(key != null ? new Set([key]) : new Set())
|
|
73
|
-
}
|
|
74
|
-
placeholder=""
|
|
75
|
-
{...restProps}
|
|
76
|
-
>
|
|
77
|
-
<Trigger buttonRef={buttonRef} />
|
|
29
|
+
export const Item = React.forwardRef<HTMLElement, CustomItemProps>((props, ref) => {
|
|
30
|
+
// @ts-expect-error: StatelyItem doesn't know about our internal item props
|
|
31
|
+
return <StatelyItem {...props} ref={ref} />
|
|
32
|
+
})
|
|
78
33
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
</Popover>
|
|
83
|
-
)}
|
|
84
|
-
</RACSelect>
|
|
85
|
-
</SingleSelectContext.Provider>
|
|
86
|
-
)
|
|
87
|
-
}
|
|
34
|
+
// @ts-expect-error: doesn't know that the Item can have this static property
|
|
35
|
+
Item.getCollectionNode = StatelyItem.getCollectionNode
|
|
36
|
+
Item.displayName = 'SingleSelectItem'
|
|
88
37
|
|
|
89
|
-
SingleSelect.
|
|
90
|
-
SingleSelect.
|
|
91
|
-
SingleSelect.ListItem = ListItem
|
|
92
|
-
SingleSelect.ListSection = ListSection
|
|
38
|
+
SingleSelect.Item = Item
|
|
39
|
+
SingleSelect.Section = Section
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Canvas, Controls, DocsStory, Meta } from '@storybook/blocks'
|
|
2
2
|
import { ResourceLinks, KAIOInstallation, AlphaNotice } from '~storybook/components'
|
|
3
|
-
import
|
|
3
|
+
import { Playground } from './SingleSelect.stories'
|
|
4
|
+
import { SingleSelect } from '../index'
|
|
4
5
|
|
|
5
|
-
<Meta
|
|
6
|
+
<Meta title="Components/SingleSelect/SingleSelect (alpha)" component={SingleSelect} />
|
|
6
7
|
|
|
7
8
|
# SingleSelect
|
|
8
9
|
|
|
9
10
|
<ResourceLinks
|
|
10
11
|
sourceCode="https://github.com/cultureamp/kaizen-design-system/tree/main/packages/components/src/__alpha__/SingleSelect"
|
|
11
|
-
figma=""
|
|
12
|
+
figma="https://www.figma.com/design/umhYV8q0yC4qwbCOIfJDY5/-Alpha--Select?node-id=2122-15597&t=EgUdLXABrcuWMIay-0"
|
|
12
13
|
designGuidelines=""
|
|
13
14
|
/>
|
|
14
15
|
|
|
@@ -16,15 +17,104 @@ import * as SingleSelectStories from './SingleSelect.stories'
|
|
|
16
17
|
|
|
17
18
|
<KAIOInstallation exportNames="SingleSelect" isAlpha />
|
|
18
19
|
|
|
20
|
+
## Alpha notice
|
|
21
|
+
|
|
22
|
+
This component is currently in **alpha** and not yet fully feature-complete. The following capabilities are planned or in progress:
|
|
23
|
+
|
|
24
|
+
- Form validation and integration with form libraries
|
|
25
|
+
- Support for passing a custom defaultFilter function from React Aria
|
|
26
|
+
- Full support for asynchronous list loading and dynamic items
|
|
27
|
+
- Ability to use the component in a fully controlled manner (selectedKey, inputValue, etc.)
|
|
28
|
+
- Forwarding refs to the appropriate internal elements
|
|
29
|
+
- Enhanced composability beyond the provided Item and Section components
|
|
30
|
+
|
|
31
|
+
We welcome feedback as we continue to improve the component.
|
|
32
|
+
|
|
19
33
|
## Overview
|
|
20
34
|
|
|
21
|
-
SingleSelect component
|
|
35
|
+
The `SingleSelect` component allows users to select a single item from a dropdown list. It can be configured as a **Select** or **Combobox**. A Select provides a predefined list of options, while a Combobox adds a filterable input to help users find an option quickly.
|
|
22
36
|
|
|
23
|
-
<Canvas of={
|
|
24
|
-
<Controls of={
|
|
37
|
+
<Canvas of={Playground} />
|
|
38
|
+
<Controls of={Playground} />
|
|
25
39
|
|
|
26
40
|
## API
|
|
27
41
|
|
|
42
|
+
## Combobox vs. Select
|
|
43
|
+
|
|
44
|
+
The `SingleSelect` component supports two different behaviors:
|
|
45
|
+
|
|
46
|
+
- **Select**: A classic dropdown menu where the user can only select from a predefined list of options. It's ideal for short, fixed lists.
|
|
47
|
+
- **Combobox**: An input field combined with a dropdown. The user can either type to filter the options or click to open the full list. This is best for long lists or when quick searching is a priority.
|
|
48
|
+
|
|
49
|
+
To use the Combobox behavior, simply add the `isComboBox` prop.
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// Select
|
|
53
|
+
<SingleSelect label="Select an item">
|
|
54
|
+
<SingleSelect.Item key="1">Item 1</SingleSelect.Item>
|
|
55
|
+
<SingleSelect.Item key="2">Item 2</SingleSelect.Item>
|
|
56
|
+
</SingleSelect>
|
|
57
|
+
|
|
58
|
+
// Combobox
|
|
59
|
+
<SingleSelect label="Select an item" isComboBox>
|
|
60
|
+
<SingleSelect.Item key="1">Item 1</SingleSelect.Item>
|
|
61
|
+
<SingleSelect.Item key="2">Item 2</SingleSelect.Item>
|
|
62
|
+
</SingleSelect>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Filtering
|
|
66
|
+
|
|
67
|
+
Children as static elements (leaves)
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
<SingleSelect label="Choose a coffee" isComboBox items={coffeeItems}>
|
|
71
|
+
{(item) => <SingleSelect.Item key={item.key}>{item.label}</SingleSelect.Item>}
|
|
72
|
+
</SingleSelect>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Children as a render function + `items` prop
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
const [filterText, setFilterText] = useState('')
|
|
79
|
+
|
|
80
|
+
<SingleSelect
|
|
81
|
+
label="Choose a coffee"
|
|
82
|
+
isComboBox
|
|
83
|
+
items={coffeeItems.filter((item) => item.label.toLowerCase().includes(filterText.toLowerCase()))}
|
|
84
|
+
onInputChange={setFilterText}
|
|
85
|
+
>
|
|
86
|
+
{(item) => <SingleSelect.Item key={item.key} textValue={item.label}>{item.label}</SingleSelect.Item>}
|
|
87
|
+
</SingleSelect>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Flexible Rendering of items
|
|
91
|
+
|
|
92
|
+
The SingleSelect.Item component is flexible and can accommodate a wide variety of content beyond simple text labels. You can render avatars, icons, or any custom React nodes within an item, while still preserving accessibility by providing the textValue prop.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<SingleSelect label="Coffee" isComboBox description="Avatar and inline">
|
|
96
|
+
{singleMockItems.map((item) => (
|
|
97
|
+
<SingleSelect.Item key={item.key} textValue={item.label} className="flex items-center gap-3">
|
|
98
|
+
<Avatar fullName="Senior Popsicle" size="small" />
|
|
99
|
+
|
|
100
|
+
<Text variant="body" className="flex-shrink-0 whitespace-nowrap mr-2">
|
|
101
|
+
{item.label}
|
|
102
|
+
</Text>
|
|
103
|
+
|
|
104
|
+
<Text variant="body" className="truncate">
|
|
105
|
+
Supporting text
|
|
106
|
+
</Text>
|
|
107
|
+
</SingleSelect.Item>
|
|
108
|
+
))}
|
|
109
|
+
</SingleSelect>
|
|
110
|
+
```
|
|
111
|
+
|
|
28
112
|
## Positioning and z-index Management
|
|
29
113
|
|
|
30
114
|
The SingleSelect component leverages the native Popover API to manage its dropdown functionality. By using popover instead of custom portal logic, the component takes full advantage of CSS layers, ensuring dropdowns appear above other content without manual z-index management.
|
|
115
|
+
|
|
116
|
+
## Known issues
|
|
117
|
+
|
|
118
|
+
### Accessibility
|
|
119
|
+
|
|
120
|
+
Using the default SingleSelect component without combobox, there is a known accessibility issue for keyboard navigation when the component is used as the last focusable element on the page. After the list is opened pressing Tab should close the list, instead Tab brings the user to a unqiue state where they cannot use the escape key to close the list. Instead the user will have to navigate either forward of back again using the Tab key.
|
|
@@ -17,16 +17,9 @@ export default meta
|
|
|
17
17
|
type Story = StoryObj<typeof meta>
|
|
18
18
|
|
|
19
19
|
const args = {
|
|
20
|
+
label: 'Choose a coffee',
|
|
20
21
|
items: singleMockItems,
|
|
21
|
-
children: (
|
|
22
|
-
<SingleSelect.List>
|
|
23
|
-
{singleMockItems.map((item) => (
|
|
24
|
-
<SingleSelect.ListItem key={item.value} id={item.value}>
|
|
25
|
-
{item.label}
|
|
26
|
-
</SingleSelect.ListItem>
|
|
27
|
-
))}
|
|
28
|
-
</SingleSelect.List>
|
|
29
|
-
),
|
|
22
|
+
children: (item: any) => <SingleSelect.Item key={item.key}>{item.label}</SingleSelect.Item>,
|
|
30
23
|
}
|
|
31
24
|
|
|
32
25
|
export const RendersButton: Story = {
|
|
@@ -60,21 +53,6 @@ export const ClosesPopoverOnSelect: Story = {
|
|
|
60
53
|
},
|
|
61
54
|
}
|
|
62
55
|
|
|
63
|
-
export const KeyboardNavigation: Story = {
|
|
64
|
-
args,
|
|
65
|
-
play: async () => {
|
|
66
|
-
const trigger = screen.getByRole('button')
|
|
67
|
-
trigger.focus()
|
|
68
|
-
await userEvent.keyboard('{Enter}')
|
|
69
|
-
await waitFor(() => expect(trigger).toHaveAttribute('aria-expanded', 'true'))
|
|
70
|
-
const options = await screen.findAllByRole('option')
|
|
71
|
-
await userEvent.keyboard('{ArrowDown}')
|
|
72
|
-
expect(options[1]).toHaveAttribute('data-focused', 'true')
|
|
73
|
-
await userEvent.keyboard('{ArrowUp}')
|
|
74
|
-
expect(options[0]).toHaveAttribute('data-focused', 'true')
|
|
75
|
-
},
|
|
76
|
-
}
|
|
77
|
-
|
|
78
56
|
export const KeyboardSelectsItem: Story = {
|
|
79
57
|
args,
|
|
80
58
|
play: async () => {
|
|
@@ -98,3 +76,23 @@ export const KeyboardEscapeClosesPopover: Story = {
|
|
|
98
76
|
await waitFor(() => expect(trigger).toHaveAttribute('aria-expanded', 'false'))
|
|
99
77
|
},
|
|
100
78
|
}
|
|
79
|
+
|
|
80
|
+
export const XButtonClearsSelection: Story = {
|
|
81
|
+
args: { ...args, isComboBox: true },
|
|
82
|
+
play: async () => {
|
|
83
|
+
const input = screen.getByRole('combobox')
|
|
84
|
+
|
|
85
|
+
await userEvent.type(input, 'short')
|
|
86
|
+
const options = await screen.findAllByRole('option')
|
|
87
|
+
await userEvent.click(options[0])
|
|
88
|
+
|
|
89
|
+
const clearButton = await screen.findByRole('button', {
|
|
90
|
+
name: 'Clear Choose a coffee selection',
|
|
91
|
+
})
|
|
92
|
+
await waitFor(() => expect(clearButton).toBeVisible())
|
|
93
|
+
await userEvent.click(clearButton)
|
|
94
|
+
await waitFor(() => {
|
|
95
|
+
expect(input).toHaveValue('')
|
|
96
|
+
})
|
|
97
|
+
},
|
|
98
|
+
}
|