@kaizen/components 0.0.0-canary-v2-20250901045936 → 0.0.0-canary-debug-tab-20251015223744
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/Modal/GenericModal/GenericModal.cjs +33 -65
- package/dist/cjs/src/Modal/GenericModal/GenericModal.module.scss.cjs +1 -3
- package/dist/cjs/src/Notification/InlineNotification/InlineNotification.cjs +1 -1
- package/dist/cjs/src/SingleSelect/subcomponents/ListBox/ListBox.cjs +6 -2
- package/dist/cjs/src/Tabs/subcomponents/TabList/TabList.cjs +29 -21
- 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/Modal/GenericModal/GenericModal.mjs +34 -65
- package/dist/esm/src/Modal/GenericModal/GenericModal.module.scss.mjs +1 -3
- package/dist/esm/src/Notification/InlineNotification/InlineNotification.mjs +1 -1
- package/dist/esm/src/SingleSelect/subcomponents/ListBox/ListBox.mjs +6 -2
- package/dist/esm/src/Tabs/subcomponents/TabList/TabList.mjs +29 -21
- 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 +443 -79
- 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/ar.json +9 -1
- package/locales/bg.json +9 -1
- package/locales/cs.json +9 -1
- package/locales/cy.json +9 -1
- package/locales/da.json +9 -1
- package/locales/de.json +9 -1
- package/locales/el.json +9 -1
- package/locales/en-GB.json +9 -1
- package/locales/en.json +9 -1
- package/locales/es-419.json +9 -1
- package/locales/es.json +9 -1
- package/locales/et.json +9 -1
- package/locales/fi.json +9 -1
- package/locales/fr-CA.json +9 -1
- package/locales/fr.json +9 -1
- package/locales/he.json +9 -1
- package/locales/hi.json +9 -1
- package/locales/ht.json +9 -1
- package/locales/hu.json +9 -1
- package/locales/id.json +9 -1
- package/locales/it.json +9 -1
- package/locales/ja.json +9 -1
- package/locales/km-KH.json +9 -1
- package/locales/ko.json +9 -1
- package/locales/lt.json +9 -1
- package/locales/lv.json +9 -1
- package/locales/mi.json +10 -2
- package/locales/ms.json +9 -1
- package/locales/nb.json +9 -1
- package/locales/nl.json +9 -1
- package/locales/pl.json +9 -1
- package/locales/pt-BR.json +9 -1
- package/locales/pt.json +9 -1
- package/locales/ro.json +9 -1
- package/locales/ru.json +9 -1
- package/locales/si-LK.json +9 -1
- package/locales/sk.json +9 -1
- package/locales/sr.json +9 -1
- package/locales/sv.json +9 -1
- package/locales/th.json +9 -1
- package/locales/tl.json +9 -1
- package/locales/tr.json +9 -1
- package/locales/uk.json +9 -1
- package/locales/vi.json +9 -1
- package/locales/zh-TW.json +9 -1
- package/locales/zh.json +9 -1
- package/package.json +10 -3
- package/src/Modal/GenericModal/GenericModal.spec.tsx +1 -1
- package/src/Modal/GenericModal/GenericModal.tsx +38 -70
- package/src/Notification/InlineNotification/InlineNotification.tsx +1 -1
- package/src/RichTextEditor/RichTextEditor/RichTextEditor.spec.stories.tsx +10 -3
- package/src/SingleSelect/subcomponents/ListBox/ListBox.tsx +2 -2
- package/src/Tabs/subcomponents/TabList/TabList.tsx +40 -30
- 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
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { type StoryObj } from '@storybook/react'
|
|
3
|
-
import { expect, userEvent, within } from '@storybook/test'
|
|
3
|
+
import { expect, userEvent, waitFor, within } from '@storybook/test'
|
|
4
4
|
import { type EditorContentArray } from '../types'
|
|
5
5
|
import { RichTextEditor, type RichTextEditorProps } from './RichTextEditor'
|
|
6
6
|
|
|
@@ -149,8 +149,9 @@ export const CreateALink: Story = {
|
|
|
149
149
|
name: 'Create a link',
|
|
150
150
|
play: async (context) => {
|
|
151
151
|
const { canvasElement, step } = context
|
|
152
|
-
const { getByRole, getByText } = within(canvasElement)
|
|
152
|
+
const { getByRole, getByText, queryByRole, findByRole } = within(canvasElement)
|
|
153
153
|
const editor = getByRole('textbox')
|
|
154
|
+
|
|
154
155
|
await step('Focus on editor', async () => {
|
|
155
156
|
await userEvent.click(editor)
|
|
156
157
|
expect(editor).toHaveFocus()
|
|
@@ -185,8 +186,14 @@ export const CreateALink: Story = {
|
|
|
185
186
|
await userEvent.keyboard('{Tab}{Enter}')
|
|
186
187
|
})
|
|
187
188
|
|
|
189
|
+
await step('The Link Modal closes', async () => {
|
|
190
|
+
await waitFor(() => {
|
|
191
|
+
expect(queryByRole('dialog')).not.toBeInTheDocument()
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
188
195
|
await step('Link exists in the RTE', async () => {
|
|
189
|
-
const link =
|
|
196
|
+
const link = await findByRole('link', { name: 'Link' })
|
|
190
197
|
expect(link).toBeInTheDocument()
|
|
191
198
|
})
|
|
192
199
|
},
|
|
@@ -70,10 +70,10 @@ export const ListBox = <Option extends SingleSelectOption>({
|
|
|
70
70
|
const focusToElement = safeQuerySelector(`[data-key='${optionKey}']`)
|
|
71
71
|
|
|
72
72
|
if (focusToElement) {
|
|
73
|
-
focusToElement.focus()
|
|
73
|
+
focusToElement.focus({ preventScroll: true })
|
|
74
74
|
} else {
|
|
75
75
|
// If an element is not found, focus on the listbox. This ensures the list can still be navigated to via keyboard if the keys do not align to the data attributes of the list items.
|
|
76
|
-
ref.current?.focus()
|
|
76
|
+
ref.current?.focus({ preventScroll: true })
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
// Only run this effect for checking the first successful render
|
|
@@ -66,39 +66,49 @@ export const TabList = (props: TabListProps): JSX.Element => {
|
|
|
66
66
|
return
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (!entries[0].isIntersecting) {
|
|
72
|
-
setLeftArrowEnabled(true)
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
setLeftArrowEnabled(false)
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
threshold: 0.8,
|
|
79
|
-
root: containerElement,
|
|
80
|
-
},
|
|
81
|
-
)
|
|
82
|
-
firstTabObserver.observe(isRTL ? tabs[tabs.length - 1] : tabs[0])
|
|
69
|
+
let firstTabObserver: IntersectionObserver | null = null
|
|
70
|
+
let lastTabObserver: IntersectionObserver | null = null
|
|
83
71
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
72
|
+
requestAnimationFrame(() => {
|
|
73
|
+
const tabList = tabListRef.current
|
|
74
|
+
if (!tabList) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
firstTabObserver = new IntersectionObserver(
|
|
79
|
+
(entries) => {
|
|
80
|
+
if (!entries[0].isIntersecting) {
|
|
81
|
+
setLeftArrowEnabled(true)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
setLeftArrowEnabled(false)
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
threshold: 0.8,
|
|
88
|
+
root: containerElement,
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
firstTabObserver.observe(isRTL ? tabs[tabs.length - 1] : tabs[0])
|
|
92
|
+
|
|
93
|
+
lastTabObserver = new IntersectionObserver(
|
|
94
|
+
(entries) => {
|
|
95
|
+
if (!entries[0].isIntersecting) {
|
|
96
|
+
setRightArrowEnabled(true)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
setRightArrowEnabled(false)
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
threshold: 0.8,
|
|
103
|
+
root: containerElement,
|
|
104
|
+
},
|
|
105
|
+
)
|
|
106
|
+
lastTabObserver.observe(isRTL ? tabs[0] : tabs[tabs.length - 1])
|
|
107
|
+
})
|
|
98
108
|
|
|
99
109
|
return () => {
|
|
100
|
-
firstTabObserver
|
|
101
|
-
lastTabObserver
|
|
110
|
+
firstTabObserver?.disconnect()
|
|
111
|
+
lastTabObserver?.disconnect()
|
|
102
112
|
}
|
|
103
113
|
}, [isDocumentReady, containerElement, isRTL, tabListContext?.collection.size])
|
|
104
114
|
|
|
@@ -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
|
+
}
|