@kaizen/components 1.35.1 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Button/GenericButton/GenericButton.cjs +1 -1
- package/dist/cjs/Button/GenericButton/GenericButton.cjs.map +1 -1
- package/dist/cjs/KaizenProvider/KaizenProvider.cjs +6 -1
- package/dist/cjs/KaizenProvider/KaizenProvider.cjs.map +1 -1
- package/dist/cjs/Modal/ContextModal/ContextModal.cjs +9 -6
- package/dist/cjs/Modal/ContextModal/ContextModal.cjs.map +1 -1
- package/dist/cjs/MultiSelect/subcomponents/Popover/Popover.cjs +1 -1
- package/dist/cjs/MultiSelect/subcomponents/Popover/Popover.cjs.map +1 -1
- package/dist/cjs/Notification/ToastNotification/ToastNotification/ToastNotification.cjs +33 -0
- package/dist/cjs/Notification/ToastNotification/ToastNotification/ToastNotification.cjs.map +1 -0
- package/dist/cjs/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.cjs +36 -0
- package/dist/cjs/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.cjs.map +1 -0
- package/dist/cjs/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.module.scss.cjs +7 -0
- package/dist/cjs/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.module.scss.cjs.map +1 -0
- package/dist/cjs/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.cjs +42 -0
- package/dist/cjs/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.cjs.map +1 -0
- package/dist/cjs/Notification/ToastNotification/context/ToastNotificationContext.cjs +72 -0
- package/dist/cjs/Notification/ToastNotification/context/ToastNotificationContext.cjs.map +1 -0
- package/dist/cjs/Notification/ToastNotification/hooks/useToastNotification.cjs +9 -0
- package/dist/cjs/Notification/ToastNotification/hooks/useToastNotification.cjs.map +1 -0
- package/dist/cjs/RichTextEditor/RichTextEditor/RichTextEditor.cjs +6 -2
- package/dist/cjs/RichTextEditor/RichTextEditor/RichTextEditor.cjs.map +1 -1
- package/dist/cjs/__future__/Select/Select.cjs +24 -2
- package/dist/cjs/__future__/Select/Select.cjs.map +1 -1
- package/dist/cjs/dts/Modal/ContextModal/ContextModal.d.ts +2 -1
- package/dist/cjs/dts/MultiSelect/MultiSelect.d.ts +4 -2
- package/dist/cjs/dts/MultiSelect/subcomponents/MultiSelectToggle/MultiSelectToggle.d.ts +3 -0
- package/dist/cjs/dts/MultiSelect/types.d.ts +6 -0
- package/dist/cjs/dts/Notification/ToastNotification/ToastNotification/ToastNotification.d.ts +16 -0
- package/dist/cjs/dts/Notification/ToastNotification/ToastNotification/index.d.ts +1 -0
- package/dist/cjs/dts/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.d.ts +4 -0
- package/dist/cjs/dts/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.d.ts +12 -0
- package/dist/cjs/dts/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/index.d.ts +1 -0
- package/dist/cjs/dts/Notification/ToastNotification/context/ToastNotificationContext.d.ts +21 -0
- package/dist/cjs/dts/Notification/ToastNotification/hooks/useToastNotification.d.ts +2 -0
- package/dist/cjs/dts/Notification/ToastNotification/index.d.ts +3 -2
- package/dist/cjs/dts/Notification/ToastNotification/types.d.ts +1 -9
- package/dist/cjs/dts/Notification/index.d.ts +1 -0
- package/dist/cjs/dts/RichTextEditor/RichTextEditor/RichTextEditor.d.ts +1 -1
- package/dist/cjs/dts/__future__/Select/Select.d.ts +5 -1
- package/dist/cjs/index.cjs +6 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.css +4 -3
- package/dist/esm/Button/GenericButton/GenericButton.mjs +1 -1
- package/dist/esm/Button/GenericButton/GenericButton.mjs.map +1 -1
- package/dist/esm/KaizenProvider/KaizenProvider.mjs +6 -1
- package/dist/esm/KaizenProvider/KaizenProvider.mjs.map +1 -1
- package/dist/esm/Modal/ContextModal/ContextModal.mjs +9 -6
- package/dist/esm/Modal/ContextModal/ContextModal.mjs.map +1 -1
- package/dist/esm/MultiSelect/subcomponents/Popover/Popover.mjs +1 -1
- package/dist/esm/MultiSelect/subcomponents/Popover/Popover.mjs.map +1 -1
- package/dist/esm/Notification/ToastNotification/ToastNotification/ToastNotification.mjs +31 -0
- package/dist/esm/Notification/ToastNotification/ToastNotification/ToastNotification.mjs.map +1 -0
- package/dist/esm/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.mjs +34 -0
- package/dist/esm/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.mjs.map +1 -0
- package/dist/esm/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.module.scss.mjs +5 -0
- package/dist/esm/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.module.scss.mjs.map +1 -0
- package/dist/esm/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.mjs +40 -0
- package/dist/esm/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.mjs.map +1 -0
- package/dist/esm/Notification/ToastNotification/context/ToastNotificationContext.mjs +69 -0
- package/dist/esm/Notification/ToastNotification/context/ToastNotificationContext.mjs.map +1 -0
- package/dist/esm/Notification/ToastNotification/hooks/useToastNotification.mjs +7 -0
- package/dist/esm/Notification/ToastNotification/hooks/useToastNotification.mjs.map +1 -0
- package/dist/esm/RichTextEditor/RichTextEditor/RichTextEditor.mjs +6 -2
- package/dist/esm/RichTextEditor/RichTextEditor/RichTextEditor.mjs.map +1 -1
- package/dist/esm/__future__/Select/Select.mjs +25 -3
- package/dist/esm/__future__/Select/Select.mjs.map +1 -1
- package/dist/esm/dts/Modal/ContextModal/ContextModal.d.ts +2 -1
- package/dist/esm/dts/MultiSelect/MultiSelect.d.ts +4 -2
- package/dist/esm/dts/MultiSelect/subcomponents/MultiSelectToggle/MultiSelectToggle.d.ts +3 -0
- package/dist/esm/dts/MultiSelect/types.d.ts +6 -0
- package/dist/esm/dts/Notification/ToastNotification/ToastNotification/ToastNotification.d.ts +16 -0
- package/dist/esm/dts/Notification/ToastNotification/ToastNotification/index.d.ts +1 -0
- package/dist/esm/dts/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.d.ts +4 -0
- package/dist/esm/dts/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.d.ts +12 -0
- package/dist/esm/dts/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/index.d.ts +1 -0
- package/dist/esm/dts/Notification/ToastNotification/context/ToastNotificationContext.d.ts +21 -0
- package/dist/esm/dts/Notification/ToastNotification/hooks/useToastNotification.d.ts +2 -0
- package/dist/esm/dts/Notification/ToastNotification/index.d.ts +3 -2
- package/dist/esm/dts/Notification/ToastNotification/types.d.ts +1 -9
- package/dist/esm/dts/Notification/index.d.ts +1 -0
- package/dist/esm/dts/RichTextEditor/RichTextEditor/RichTextEditor.d.ts +1 -1
- package/dist/esm/dts/__future__/Select/Select.d.ts +5 -1
- package/dist/esm/index.css +5 -4
- package/dist/esm/index.mjs +3 -0
- package/dist/esm/index.mjs.map +1 -1
- package/dist/index.d.ts +50 -3
- package/dist/styles.css +1 -1
- package/package.json +2 -2
- package/src/Button/Button/_docs/Button.mdx +5 -0
- package/src/Button/Button/_docs/Button.stories.tsx +27 -1
- package/src/Button/GenericButton/GenericButton.spec.tsx +69 -0
- package/src/Button/GenericButton/GenericButton.tsx +1 -1
- package/src/DatePicker/DatePicker.spec.tsx +1 -1
- package/src/KaizenProvider/KaizenProvider.tsx +6 -1
- package/src/Modal/ContextModal/ContextModal.spec.tsx +3 -3
- package/src/Modal/ContextModal/ContextModal.tsx +9 -5
- package/src/MultiSelect/MultiSelect.spec.tsx +56 -1
- package/src/MultiSelect/MultiSelect.tsx +10 -3
- package/src/MultiSelect/_docs/MultiSelect.mdx +10 -0
- package/src/MultiSelect/_docs/MultiSelect.stickersheet.stories.tsx +81 -43
- package/src/MultiSelect/_docs/MultiSelect.stories.tsx +21 -0
- package/src/MultiSelect/subcomponents/MultiSelectToggle/MultiSelectToggle.module.scss +9 -0
- package/src/MultiSelect/subcomponents/MultiSelectToggle/MultiSelectToggle.tsx +8 -1
- package/src/MultiSelect/subcomponents/MultiSelectToggle/_docs/MultiSelectToggle.stickersheet.stories.tsx +17 -0
- package/src/MultiSelect/subcomponents/Popover/Popover.tsx +1 -1
- package/src/MultiSelect/types.ts +7 -0
- package/src/Notification/ToastNotification/ToastNotification/ToastNotification.spec.tsx +33 -0
- package/src/Notification/ToastNotification/ToastNotification/ToastNotification.tsx +48 -0
- package/src/Notification/ToastNotification/ToastNotification/index.ts +1 -0
- package/src/Notification/ToastNotification/{subcomponents/ToastNotificationsList → ToastNotificationsList}/ToastNotificationsList.module.scss +1 -1
- package/src/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.tsx +40 -0
- package/src/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.tsx +49 -0
- package/src/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/index.ts +1 -0
- package/src/Notification/ToastNotification/_docs/ToastNotification.mdx +19 -14
- package/src/Notification/ToastNotification/_docs/ToastNotification.stickersheet.stories.tsx +33 -70
- package/src/Notification/ToastNotification/_docs/ToastNotification.stories.tsx +123 -93
- package/src/Notification/ToastNotification/context/ToastNotificationContext.tsx +96 -0
- package/src/Notification/ToastNotification/hooks/useToastNotification.ts +9 -0
- package/src/Notification/ToastNotification/index.ts +3 -2
- package/src/Notification/ToastNotification/types.ts +1 -18
- package/src/Notification/index.ts +1 -0
- package/src/RichTextEditor/RichTextEditor/RichTextEditor.tsx +6 -1
- package/src/RichTextEditor/utils/commands/addMark.spec.ts +0 -1
- package/src/Tooltip/Tooltip.spec.tsx +6 -1
- package/src/__future__/Select/Select.spec.tsx +109 -14
- package/src/__future__/Select/Select.tsx +32 -3
- package/src/__future__/Select/_docs/Select.mdx +8 -0
- package/src/__future__/Select/_docs/Select.stories.tsx +29 -0
- package/dist/cjs/dts/Notification/ToastNotification/ToastNotification.d.ts +0 -14
- package/dist/cjs/dts/Notification/ToastNotification/subcomponents/ToastNotificationManager/ToastNotificationManager.d.ts +0 -7
- package/dist/cjs/dts/Notification/ToastNotification/subcomponents/ToastNotificationManager/index.d.ts +0 -1
- package/dist/cjs/dts/Notification/ToastNotification/subcomponents/ToastNotificationsList/ToastNotificationsList.d.ts +0 -11
- package/dist/cjs/dts/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/ToastNotificationsListContainer.d.ts +0 -7
- package/dist/cjs/dts/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/index.d.ts +0 -1
- package/dist/esm/dts/Notification/ToastNotification/ToastNotification.d.ts +0 -14
- package/dist/esm/dts/Notification/ToastNotification/subcomponents/ToastNotificationManager/ToastNotificationManager.d.ts +0 -7
- package/dist/esm/dts/Notification/ToastNotification/subcomponents/ToastNotificationManager/index.d.ts +0 -1
- package/dist/esm/dts/Notification/ToastNotification/subcomponents/ToastNotificationsList/ToastNotificationsList.d.ts +0 -11
- package/dist/esm/dts/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/ToastNotificationsListContainer.d.ts +0 -7
- package/dist/esm/dts/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/index.d.ts +0 -1
- package/src/Notification/ToastNotification/ToastNotification.spec.tsx +0 -31
- package/src/Notification/ToastNotification/ToastNotification.tsx +0 -43
- package/src/Notification/ToastNotification/subcomponents/ToastNotificationManager/ToastNotificationManager.spec.tsx +0 -144
- package/src/Notification/ToastNotification/subcomponents/ToastNotificationManager/ToastNotificationManager.tsx +0 -135
- package/src/Notification/ToastNotification/subcomponents/ToastNotificationManager/index.ts +0 -1
- package/src/Notification/ToastNotification/subcomponents/ToastNotificationsList/ToastNotificationsList.tsx +0 -40
- package/src/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/ToastNotificationsListContainer.spec.tsx +0 -73
- package/src/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/ToastNotificationsListContainer.tsx +0 -31
- package/src/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/index.ts +0 -1
- /package/dist/cjs/dts/Notification/ToastNotification/{subcomponents/ToastNotificationsList → ToastNotificationsList}/index.d.ts +0 -0
- /package/dist/esm/dts/Notification/ToastNotification/{subcomponents/ToastNotificationsList → ToastNotificationsList}/index.d.ts +0 -0
- /package/src/Notification/ToastNotification/{subcomponents/ToastNotificationsList → ToastNotificationsList}/index.ts +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React, { useContext, useState } from "react"
|
|
2
|
+
import { v4 as uuidv4 } from "uuid"
|
|
3
|
+
import { ToastNotificationObj } from "../types"
|
|
4
|
+
|
|
5
|
+
type ToastNotificationObjOptionalId = Omit<ToastNotificationObj, "id"> & {
|
|
6
|
+
id?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type ToastNotificationContextValue = {
|
|
10
|
+
notifications: ToastNotificationObj[]
|
|
11
|
+
addToastNotification: (notification: ToastNotificationObjOptionalId) => void
|
|
12
|
+
updateToastNotification: (notification: ToastNotificationObj) => void
|
|
13
|
+
removeToastNotification: (notificationId: string) => void
|
|
14
|
+
clearToastNotifications: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ToastNotificationContext =
|
|
18
|
+
React.createContext<ToastNotificationContextValue | null>(null)
|
|
19
|
+
|
|
20
|
+
export const useToastNotificationContext =
|
|
21
|
+
(): ToastNotificationContextValue => {
|
|
22
|
+
const context = useContext(ToastNotificationContext)
|
|
23
|
+
|
|
24
|
+
if (!context) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
"useToastNotificationContext must be used within the ToastNotificationContext.Provider"
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return context
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type ToastNotificationProviderProps = {
|
|
34
|
+
children: React.ReactNode
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const ToastNotificationProvider = ({
|
|
38
|
+
children,
|
|
39
|
+
}: ToastNotificationProviderProps): JSX.Element | null => {
|
|
40
|
+
const [notifications, setNotifications] = useState<ToastNotificationObj[]>([])
|
|
41
|
+
|
|
42
|
+
const addToastNotification: ToastNotificationContextValue["addToastNotification"] =
|
|
43
|
+
notification => {
|
|
44
|
+
const uuid = uuidv4()
|
|
45
|
+
const notificationWithId = { id: uuid, ...notification }
|
|
46
|
+
|
|
47
|
+
const notificationExists = notifications.find(
|
|
48
|
+
({ id }) => id === notification.id
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if (!notificationExists) {
|
|
52
|
+
setNotifications(existing => [...existing, notificationWithId])
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const updateToastNotification = (
|
|
57
|
+
notification: ToastNotificationObj
|
|
58
|
+
): void => {
|
|
59
|
+
const notificationIndex = notifications.findIndex(
|
|
60
|
+
({ id }) => id === notification.id
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const copy = notifications.slice()
|
|
64
|
+
copy.splice(notificationIndex, 1, notification) // Mutation to insert notification over itself
|
|
65
|
+
setNotifications(copy)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const removeToastNotification = (notificationID: string): void => {
|
|
69
|
+
const notificationIndex = notifications.findIndex(
|
|
70
|
+
({ id }) => id === notificationID
|
|
71
|
+
)
|
|
72
|
+
const copy = notifications.slice()
|
|
73
|
+
copy.splice(notificationIndex, 1) // Mutation
|
|
74
|
+
setNotifications(copy)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const clearToastNotifications = (): void => {
|
|
78
|
+
setNotifications([])
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const value = {
|
|
82
|
+
notifications,
|
|
83
|
+
addToastNotification,
|
|
84
|
+
updateToastNotification,
|
|
85
|
+
removeToastNotification,
|
|
86
|
+
clearToastNotifications,
|
|
87
|
+
} satisfies ToastNotificationContextValue
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<ToastNotificationContext.Provider value={value}>
|
|
91
|
+
{children}
|
|
92
|
+
</ToastNotificationContext.Provider>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
ToastNotificationProvider.displayName = "ToastNotificationProvider"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ToastNotificationContextValue,
|
|
3
|
+
useToastNotificationContext,
|
|
4
|
+
} from "../context/ToastNotificationContext"
|
|
5
|
+
|
|
6
|
+
export const useToastNotification = (): ToastNotificationContextValue => {
|
|
7
|
+
const context = useToastNotificationContext()
|
|
8
|
+
return context
|
|
9
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { DataAttributes } from "~types/DataAttributes"
|
|
2
2
|
import { NotificationType } from "../types"
|
|
3
3
|
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
export type ToastNotification = {
|
|
4
|
+
export type ToastNotificationObj = {
|
|
7
5
|
id: string
|
|
8
6
|
type: NotificationType
|
|
9
7
|
title: string
|
|
@@ -15,18 +13,3 @@ export type ToastNotification = {
|
|
|
15
13
|
*/
|
|
16
14
|
persistent?: boolean
|
|
17
15
|
} & DataAttributes
|
|
18
|
-
|
|
19
|
-
export type ToastNotificationWithOptionals = Modify<
|
|
20
|
-
ToastNotification,
|
|
21
|
-
{
|
|
22
|
-
id?: string
|
|
23
|
-
}
|
|
24
|
-
>
|
|
25
|
-
|
|
26
|
-
export type AddToastNotification = (
|
|
27
|
-
notification: ToastNotificationWithOptionals
|
|
28
|
-
) => void
|
|
29
|
-
|
|
30
|
-
export type RemoveToastNotification = (notificationId: string) => void
|
|
31
|
-
|
|
32
|
-
export type ClearToastNotifications = () => void
|
|
@@ -74,6 +74,7 @@ export const RichTextEditor = ({
|
|
|
74
74
|
defaultValue,
|
|
75
75
|
labelText,
|
|
76
76
|
"aria-labelledby": labelledBy,
|
|
77
|
+
"aria-describedby": describedBy,
|
|
77
78
|
classNameOverride,
|
|
78
79
|
controls,
|
|
79
80
|
rows = 3,
|
|
@@ -144,7 +145,11 @@ export const RichTextEditor = ({
|
|
|
144
145
|
: ""
|
|
145
146
|
const descriptionAria = description ? `${editorId}-rte-description` : ""
|
|
146
147
|
|
|
147
|
-
const ariaDescribedBy =
|
|
148
|
+
const ariaDescribedBy = classnames(
|
|
149
|
+
validationMessageAria,
|
|
150
|
+
descriptionAria,
|
|
151
|
+
describedBy
|
|
152
|
+
)
|
|
148
153
|
|
|
149
154
|
return (
|
|
150
155
|
<>
|
|
@@ -48,6 +48,8 @@ describe("<Tooltip />", () => {
|
|
|
48
48
|
// Non-semantic elements without roles should not have aria-description on them.
|
|
49
49
|
// They won't read to all screen readers as expected and may be reported in Storybook's accessibility tab (which uses Axe under the hood)
|
|
50
50
|
it("doesn't add an accessible description when wrapping a non-semantic element", async () => {
|
|
51
|
+
const warn = jest.spyOn(console, "warn").mockImplementation()
|
|
52
|
+
|
|
51
53
|
render(
|
|
52
54
|
<Tooltip
|
|
53
55
|
text="Tooltip popup description for div"
|
|
@@ -62,6 +64,9 @@ describe("<Tooltip />", () => {
|
|
|
62
64
|
expect(screen.getByText("Non semantic element")).not.toHaveAttribute(
|
|
63
65
|
"aria-describedby"
|
|
64
66
|
)
|
|
67
|
+
expect(warn).toHaveBeenCalledWith(
|
|
68
|
+
"<Tooltip /> is not directly wrapping a semantic element, screen reader users will not be able to access the tooltip info. To ensure accessibility, Tooltip should be wrapping a semantic and focusable element directly."
|
|
69
|
+
)
|
|
65
70
|
})
|
|
66
71
|
})
|
|
67
72
|
})
|
|
@@ -74,7 +79,7 @@ describe("<Tooltip />", () => {
|
|
|
74
79
|
isInitiallyVisible
|
|
75
80
|
position="below"
|
|
76
81
|
>
|
|
77
|
-
<div role="textbox" contentEditable="true" aria-multiline="true"
|
|
82
|
+
<div role="textbox" contentEditable="true" aria-multiline="true" />
|
|
78
83
|
</Tooltip>
|
|
79
84
|
)
|
|
80
85
|
await waitFor(() => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react"
|
|
2
|
-
import { render, waitFor } from "@testing-library/react"
|
|
2
|
+
import { render, waitFor, screen, within } from "@testing-library/react"
|
|
3
3
|
import userEvent from "@testing-library/user-event"
|
|
4
4
|
import { Select, SelectProps } from "./Select"
|
|
5
5
|
import { singleMockItems } from "./_docs/mockData"
|
|
@@ -17,7 +17,6 @@ const SelectWrapper = ({
|
|
|
17
17
|
)
|
|
18
18
|
return (
|
|
19
19
|
<Select
|
|
20
|
-
id="id--select"
|
|
21
20
|
label="Mock Label"
|
|
22
21
|
items={items}
|
|
23
22
|
description="This is a description"
|
|
@@ -33,14 +32,35 @@ const SelectWrapper = ({
|
|
|
33
32
|
|
|
34
33
|
describe("<Select />", () => {
|
|
35
34
|
describe("Trigger", () => {
|
|
36
|
-
it("
|
|
35
|
+
it("has the label as the accessible name", () => {
|
|
36
|
+
const { getByRole } = render(<SelectWrapper />)
|
|
37
|
+
const menu = getByRole("combobox", {
|
|
38
|
+
name: "Mock Label",
|
|
39
|
+
})
|
|
40
|
+
expect(menu).toBeInTheDocument()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it("has the value when an item is selected", () => {
|
|
37
44
|
const { getByRole } = render(<SelectWrapper selectedKey="batch-brew" />)
|
|
38
45
|
const menu = getByRole("combobox", {
|
|
39
|
-
name: "
|
|
46
|
+
name: "Mock Label",
|
|
40
47
|
})
|
|
41
48
|
expect(menu).toHaveTextContent("Batch brew")
|
|
42
49
|
})
|
|
43
50
|
|
|
51
|
+
it("allows more aria-labelledby references to be sent in", () => {
|
|
52
|
+
const { getByRole } = render(
|
|
53
|
+
<>
|
|
54
|
+
<div id="extra-label">extra label stuff</div>
|
|
55
|
+
<SelectWrapper aria-labelledby="extra-label" />
|
|
56
|
+
</>
|
|
57
|
+
)
|
|
58
|
+
const menu = getByRole("combobox", {
|
|
59
|
+
name: "Mock Label extra label stuff",
|
|
60
|
+
})
|
|
61
|
+
expect(menu).toBeInTheDocument()
|
|
62
|
+
})
|
|
63
|
+
|
|
44
64
|
describe("when uncontrolled", () => {
|
|
45
65
|
it("does not show the menu initially", () => {
|
|
46
66
|
const { queryByRole } = render(<SelectWrapper />)
|
|
@@ -76,7 +96,7 @@ describe("<Select />", () => {
|
|
|
76
96
|
/>
|
|
77
97
|
)
|
|
78
98
|
const trigger = getByRole("combobox", {
|
|
79
|
-
name: "
|
|
99
|
+
name: "Mock Label",
|
|
80
100
|
})
|
|
81
101
|
await user.click(trigger)
|
|
82
102
|
await waitFor(() => {
|
|
@@ -93,7 +113,7 @@ describe("<Select />", () => {
|
|
|
93
113
|
<SelectWrapper selectedKey="batch-brew" />
|
|
94
114
|
)
|
|
95
115
|
const trigger = getByRole("combobox", {
|
|
96
|
-
name: "
|
|
116
|
+
name: "Mock Label",
|
|
97
117
|
})
|
|
98
118
|
await user.click(trigger)
|
|
99
119
|
await waitFor(() => {
|
|
@@ -108,7 +128,7 @@ describe("<Select />", () => {
|
|
|
108
128
|
<SelectWrapper selectedKey="batch-brew" defaultOpen />
|
|
109
129
|
)
|
|
110
130
|
const trigger = getByRole("combobox", {
|
|
111
|
-
name: "
|
|
131
|
+
name: "Mock Label",
|
|
112
132
|
})
|
|
113
133
|
|
|
114
134
|
await user.click(trigger)
|
|
@@ -147,7 +167,7 @@ describe("<Select />", () => {
|
|
|
147
167
|
<SelectWrapper selectedKey="batch-brew" />
|
|
148
168
|
)
|
|
149
169
|
const trigger = getByRole("combobox", {
|
|
150
|
-
name: "
|
|
170
|
+
name: "Mock Label",
|
|
151
171
|
})
|
|
152
172
|
await user.tab()
|
|
153
173
|
await waitFor(() => {
|
|
@@ -160,7 +180,7 @@ describe("<Select />", () => {
|
|
|
160
180
|
<SelectWrapper selectedKey="batch-brew" />
|
|
161
181
|
)
|
|
162
182
|
const trigger = getByRole("combobox", {
|
|
163
|
-
name: "
|
|
183
|
+
name: "Mock Label",
|
|
164
184
|
})
|
|
165
185
|
await user.tab()
|
|
166
186
|
await waitFor(() => {
|
|
@@ -305,9 +325,7 @@ describe("<Select />", () => {
|
|
|
305
325
|
})
|
|
306
326
|
await user.keyboard("{Enter}")
|
|
307
327
|
|
|
308
|
-
await user.click(
|
|
309
|
-
getByRole("combobox", { name: "Short black Mock Label" })
|
|
310
|
-
)
|
|
328
|
+
await user.click(getByRole("combobox", { name: "Mock Label" }))
|
|
311
329
|
await waitFor(() => {
|
|
312
330
|
expect(
|
|
313
331
|
getByRole("option", { name: "Short black", selected: true })
|
|
@@ -320,7 +338,7 @@ describe("<Select />", () => {
|
|
|
320
338
|
const { getByRole } = render(
|
|
321
339
|
<SelectWrapper onSelectionChange={spy} defaultOpen />
|
|
322
340
|
)
|
|
323
|
-
const trigger = getByRole("combobox", { name: "
|
|
341
|
+
const trigger = getByRole("combobox", { name: "Mock Label" })
|
|
324
342
|
|
|
325
343
|
await user.tab()
|
|
326
344
|
await waitFor(() => {
|
|
@@ -332,9 +350,86 @@ describe("<Select />", () => {
|
|
|
332
350
|
await user.keyboard("{Enter}")
|
|
333
351
|
await waitFor(() => {
|
|
334
352
|
expect(spy).toHaveBeenCalledTimes(1)
|
|
335
|
-
expect(trigger).toHaveAccessibleName("
|
|
353
|
+
expect(trigger).toHaveAccessibleName("Mock Label")
|
|
336
354
|
})
|
|
337
355
|
})
|
|
338
356
|
})
|
|
339
357
|
})
|
|
358
|
+
|
|
359
|
+
describe("Popover portal", () => {
|
|
360
|
+
it("has accessible trigger controls", async () => {
|
|
361
|
+
render(<SelectWrapper isOpen />)
|
|
362
|
+
|
|
363
|
+
const trigger = screen.getByRole("combobox", {
|
|
364
|
+
name: "Mock Label",
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
await waitFor(() => {
|
|
368
|
+
expect(trigger).toHaveAttribute("aria-controls")
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
it("will portal to the document body by default", async () => {
|
|
373
|
+
render(<SelectWrapper selectedKey="batch-brew" isOpen />)
|
|
374
|
+
|
|
375
|
+
const popover = screen.getByRole("dialog")
|
|
376
|
+
// expected div that FocusOn adds to the popover
|
|
377
|
+
const popoverFocusWrapper = popover.parentNode
|
|
378
|
+
|
|
379
|
+
await waitFor(() => {
|
|
380
|
+
const expectedBodyTag = popoverFocusWrapper?.parentNode
|
|
381
|
+
expect(expectedBodyTag?.nodeName).toEqual("BODY")
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it("will render as a descendant of the element matching the id", async () => {
|
|
386
|
+
const SelectWithPortal = (): JSX.Element => {
|
|
387
|
+
const portalContainerId = "id--portal-container"
|
|
388
|
+
return (
|
|
389
|
+
<>
|
|
390
|
+
<div
|
|
391
|
+
id={portalContainerId}
|
|
392
|
+
data-testid="id--portal-container-test"
|
|
393
|
+
></div>
|
|
394
|
+
<SelectWrapper
|
|
395
|
+
selectedKey="batch-brew"
|
|
396
|
+
isOpen
|
|
397
|
+
portalContainerId={portalContainerId}
|
|
398
|
+
/>
|
|
399
|
+
</>
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
render(<SelectWithPortal />)
|
|
403
|
+
|
|
404
|
+
await waitFor(() => {
|
|
405
|
+
const newPortalRegion = screen.getByTestId("id--portal-container-test")
|
|
406
|
+
const popover = within(newPortalRegion).getByRole("dialog")
|
|
407
|
+
|
|
408
|
+
expect(popover).toBeInTheDocument()
|
|
409
|
+
})
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
it("will portal to the document body if the id does not match", async () => {
|
|
413
|
+
const SelectWithPortal = (): JSX.Element => {
|
|
414
|
+
const expectedContainerId = "id--portal-container"
|
|
415
|
+
return (
|
|
416
|
+
<>
|
|
417
|
+
<div id="id--wrong-id"></div>
|
|
418
|
+
<SelectWrapper
|
|
419
|
+
selectedKey="batch-brew"
|
|
420
|
+
isOpen
|
|
421
|
+
portalContainerId={expectedContainerId}
|
|
422
|
+
/>
|
|
423
|
+
</>
|
|
424
|
+
)
|
|
425
|
+
}
|
|
426
|
+
render(<SelectWithPortal />)
|
|
427
|
+
|
|
428
|
+
await waitFor(() => {
|
|
429
|
+
const popover = within(document.body).getByRole("dialog")
|
|
430
|
+
|
|
431
|
+
expect(popover).toBeInTheDocument()
|
|
432
|
+
})
|
|
433
|
+
})
|
|
434
|
+
})
|
|
340
435
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useId } from "react"
|
|
1
|
+
import React, { useEffect, useId, useState } from "react"
|
|
2
2
|
import { UseFloatingReturn } from "@floating-ui/react-dom"
|
|
3
3
|
import { useButton } from "@react-aria/button"
|
|
4
4
|
import { HiddenSelect, useSelect } from "@react-aria/select"
|
|
@@ -67,6 +67,10 @@ export type SelectProps<Option extends SelectOption = SelectOption> = {
|
|
|
67
67
|
* @deprecated: Either define `disabled` in your `Option` (in `items`), or use `disabledKeys`
|
|
68
68
|
*/
|
|
69
69
|
disabledValues?: Key[]
|
|
70
|
+
/**
|
|
71
|
+
* Creates a portal for the Popover to the matching element id
|
|
72
|
+
*/
|
|
73
|
+
portalContainerId?: string
|
|
70
74
|
} & OverrideClassName<Omit<AriaSelectProps<Option>, OmittedAriaSelectProps>>
|
|
71
75
|
|
|
72
76
|
/**
|
|
@@ -89,13 +93,14 @@ export const Select = <Option extends SelectOption = SelectOption>({
|
|
|
89
93
|
description,
|
|
90
94
|
placeholder,
|
|
91
95
|
isDisabled,
|
|
96
|
+
portalContainerId,
|
|
92
97
|
...restProps
|
|
93
98
|
}: SelectProps<Option>): JSX.Element => {
|
|
94
99
|
const { refs } = useFloating<HTMLButtonElement>()
|
|
95
100
|
const triggerRef = refs.reference
|
|
96
|
-
|
|
97
101
|
const id = propsId ?? useId()
|
|
98
102
|
const descriptionId = `${id}--description`
|
|
103
|
+
const popoverId = `${id}--popover`
|
|
99
104
|
|
|
100
105
|
const disabledKeys = getDisabledKeysFromItems(items)
|
|
101
106
|
|
|
@@ -116,13 +121,26 @@ export const Select = <Option extends SelectOption = SelectOption>({
|
|
|
116
121
|
|
|
117
122
|
const {
|
|
118
123
|
labelProps,
|
|
119
|
-
triggerProps,
|
|
124
|
+
triggerProps: reactAriaTriggerProps,
|
|
120
125
|
valueProps,
|
|
121
126
|
menuProps,
|
|
122
127
|
errorMessageProps,
|
|
123
128
|
descriptionProps,
|
|
124
129
|
} = useSelect(ariaSelectProps, state, triggerRef)
|
|
125
130
|
|
|
131
|
+
// Hack incoming:
|
|
132
|
+
// react-aria/useSelect wants to prefix the combobox's accessible name with the value of the select.
|
|
133
|
+
// We use role=combobox, meaning screen readers will read the value.
|
|
134
|
+
// So we're modifying the `aria-labelledby` property to remove the value element id.
|
|
135
|
+
// Issue: https://github.com/adobe/react-spectrum/issues/4091
|
|
136
|
+
const reactAriaLabelledBy = reactAriaTriggerProps["aria-labelledby"]
|
|
137
|
+
const triggerProps = {
|
|
138
|
+
...reactAriaTriggerProps,
|
|
139
|
+
"aria-labelledby": reactAriaLabelledBy?.substring(
|
|
140
|
+
reactAriaLabelledBy.indexOf(" ") + 1
|
|
141
|
+
),
|
|
142
|
+
}
|
|
143
|
+
|
|
126
144
|
const { buttonProps } = useButton(triggerProps, triggerRef)
|
|
127
145
|
const selectToggleProps = {
|
|
128
146
|
...buttonProps,
|
|
@@ -138,6 +156,15 @@ export const Select = <Option extends SelectOption = SelectOption>({
|
|
|
138
156
|
ref: refs.setReference,
|
|
139
157
|
}
|
|
140
158
|
|
|
159
|
+
const [portalContainer, setPortalContainer] = useState<HTMLElement>()
|
|
160
|
+
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (portalContainerId) {
|
|
163
|
+
const portalElement = document.getElementById(portalContainerId)
|
|
164
|
+
portalElement && setPortalContainer(portalElement)
|
|
165
|
+
}
|
|
166
|
+
}, [])
|
|
167
|
+
|
|
141
168
|
return (
|
|
142
169
|
<div
|
|
143
170
|
className={classnames(
|
|
@@ -160,6 +187,8 @@ export const Select = <Option extends SelectOption = SelectOption>({
|
|
|
160
187
|
)}
|
|
161
188
|
{state.isOpen && (
|
|
162
189
|
<Popover
|
|
190
|
+
id={popoverId}
|
|
191
|
+
portalContainer={portalContainer}
|
|
163
192
|
refs={refs}
|
|
164
193
|
focusOnProps={{
|
|
165
194
|
onEscapeKey: state.close,
|
|
@@ -98,3 +98,11 @@ Add validation messages using `status` and `validationMessage`.
|
|
|
98
98
|
|
|
99
99
|
Set `isFullWidth` to `true` to have the Select span the full width of its container.
|
|
100
100
|
<Canvas of={SelectStories.FullWidth} />
|
|
101
|
+
|
|
102
|
+
### Portals
|
|
103
|
+
|
|
104
|
+
By default, the Select's popover will attach itself to the `body` of the document using React's `createPortal`.
|
|
105
|
+
|
|
106
|
+
You can change the default behaviour by providing a `portalContainerId` to attach this to different element in the DOM. This can help to resolve issues that may arise with `z-index` or having a Select in a modal.
|
|
107
|
+
|
|
108
|
+
<Canvas of={SelectStories.PortalContainer} />
|
|
@@ -161,3 +161,32 @@ export const Validation: Story = {
|
|
|
161
161
|
export const FullWidth: Story = {
|
|
162
162
|
args: { isFullWidth: true },
|
|
163
163
|
}
|
|
164
|
+
|
|
165
|
+
export const PortalContainer: Story = {
|
|
166
|
+
render: args => {
|
|
167
|
+
const portalContainerId = "id--portal-container"
|
|
168
|
+
return (
|
|
169
|
+
<>
|
|
170
|
+
<div
|
|
171
|
+
id={portalContainerId}
|
|
172
|
+
className="flex gap-24 bg-gray-200 p-12 overflow-hidden h-[200px] relative"
|
|
173
|
+
>
|
|
174
|
+
<Select
|
|
175
|
+
{...args}
|
|
176
|
+
label="Default"
|
|
177
|
+
selectedKey="batch-brew"
|
|
178
|
+
id="id--select-default"
|
|
179
|
+
/>
|
|
180
|
+
<Select
|
|
181
|
+
{...args}
|
|
182
|
+
label="Inner portal"
|
|
183
|
+
selectedKey="batch-brew"
|
|
184
|
+
id="id--select-inner"
|
|
185
|
+
portalContainerId={portalContainerId}
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
</>
|
|
189
|
+
)
|
|
190
|
+
},
|
|
191
|
+
parameters: { docs: { source: { type: "code" } } },
|
|
192
|
+
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { ToastNotificationWithOptionals } from "./types";
|
|
3
|
-
export type ToastNotificationProps = Omit<ToastNotificationWithOptionals, "message" | "persistent"> & {
|
|
4
|
-
/**
|
|
5
|
-
* Removes the dismiss trigger. functions the same as `persistent` in `addToastNotification`. If this is true you will need to manage the removal of notifications manually.
|
|
6
|
-
* @default false
|
|
7
|
-
*/
|
|
8
|
-
hideCloseIcon?: boolean;
|
|
9
|
-
children: React.ReactNode;
|
|
10
|
-
};
|
|
11
|
-
export declare const ToastNotification: {
|
|
12
|
-
({ id: propsId, hideCloseIcon, type, title, onHide, children, ...restProps }: ToastNotificationProps): null;
|
|
13
|
-
displayName: string;
|
|
14
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { AddToastNotification, ClearToastNotifications, RemoveToastNotification } from "../../types";
|
|
2
|
-
/**
|
|
3
|
-
* Export the curried API methods
|
|
4
|
-
*/
|
|
5
|
-
export declare const addToastNotification: AddToastNotification;
|
|
6
|
-
export declare const clearToastNotifications: ClearToastNotifications;
|
|
7
|
-
export declare const removeToastNotification: RemoveToastNotification;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./ToastNotificationManager";
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { HTMLAttributes } from "react";
|
|
2
|
-
import { OverrideClassName } from "../../../../types/OverrideClassName";
|
|
3
|
-
import { RemoveToastNotification, ToastNotification } from "../../types";
|
|
4
|
-
export type ToastNotificationsListProps = {
|
|
5
|
-
notifications: ToastNotification[];
|
|
6
|
-
onHide: RemoveToastNotification;
|
|
7
|
-
} & OverrideClassName<HTMLAttributes<HTMLDivElement>>;
|
|
8
|
-
export declare const ToastNotificationsList: {
|
|
9
|
-
({ notifications, onHide: defaultOnHide, }: ToastNotificationsListProps): JSX.Element;
|
|
10
|
-
displayName: string;
|
|
11
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { RemoveToastNotification, ToastNotification } from "../../types";
|
|
3
|
-
export type ToastNotificationsListContainerProps = {
|
|
4
|
-
removeToastNotification: RemoveToastNotification;
|
|
5
|
-
registerSetNotificationsCallback: (callback: React.Dispatch<React.SetStateAction<ToastNotification[]>>) => void;
|
|
6
|
-
};
|
|
7
|
-
export declare const ToastNotificationsListContainer: ({ removeToastNotification, registerSetNotificationsCallback, }: ToastNotificationsListContainerProps) => JSX.Element;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./ToastNotificationsListContainer";
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { ToastNotificationWithOptionals } from "./types";
|
|
3
|
-
export type ToastNotificationProps = Omit<ToastNotificationWithOptionals, "message" | "persistent"> & {
|
|
4
|
-
/**
|
|
5
|
-
* Removes the dismiss trigger. functions the same as `persistent` in `addToastNotification`. If this is true you will need to manage the removal of notifications manually.
|
|
6
|
-
* @default false
|
|
7
|
-
*/
|
|
8
|
-
hideCloseIcon?: boolean;
|
|
9
|
-
children: React.ReactNode;
|
|
10
|
-
};
|
|
11
|
-
export declare const ToastNotification: {
|
|
12
|
-
({ id: propsId, hideCloseIcon, type, title, onHide, children, ...restProps }: ToastNotificationProps): null;
|
|
13
|
-
displayName: string;
|
|
14
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { AddToastNotification, ClearToastNotifications, RemoveToastNotification } from "../../types";
|
|
2
|
-
/**
|
|
3
|
-
* Export the curried API methods
|
|
4
|
-
*/
|
|
5
|
-
export declare const addToastNotification: AddToastNotification;
|
|
6
|
-
export declare const clearToastNotifications: ClearToastNotifications;
|
|
7
|
-
export declare const removeToastNotification: RemoveToastNotification;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./ToastNotificationManager";
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { HTMLAttributes } from "react";
|
|
2
|
-
import { OverrideClassName } from "../../../../types/OverrideClassName";
|
|
3
|
-
import { RemoveToastNotification, ToastNotification } from "../../types";
|
|
4
|
-
export type ToastNotificationsListProps = {
|
|
5
|
-
notifications: ToastNotification[];
|
|
6
|
-
onHide: RemoveToastNotification;
|
|
7
|
-
} & OverrideClassName<HTMLAttributes<HTMLDivElement>>;
|
|
8
|
-
export declare const ToastNotificationsList: {
|
|
9
|
-
({ notifications, onHide: defaultOnHide, }: ToastNotificationsListProps): JSX.Element;
|
|
10
|
-
displayName: string;
|
|
11
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { RemoveToastNotification, ToastNotification } from "../../types";
|
|
3
|
-
export type ToastNotificationsListContainerProps = {
|
|
4
|
-
removeToastNotification: RemoveToastNotification;
|
|
5
|
-
registerSetNotificationsCallback: (callback: React.Dispatch<React.SetStateAction<ToastNotification[]>>) => void;
|
|
6
|
-
};
|
|
7
|
-
export declare const ToastNotificationsListContainer: ({ removeToastNotification, registerSetNotificationsCallback, }: ToastNotificationsListContainerProps) => JSX.Element;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./ToastNotificationsListContainer";
|