@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaizen/components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.0",
|
|
4
4
|
"description": "Kaizen component library",
|
|
5
5
|
"author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
|
|
6
6
|
"homepage": "https://cultureamp.design",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"prepublishOnly": "yarn build:components && yarn build:styles && yarn postBuild && yarn dist:clean",
|
|
30
30
|
"build": "yarn clean && yarn prepublishOnly",
|
|
31
|
-
"test": "jest",
|
|
31
|
+
"test": "FORCE_COLOR=1 jest",
|
|
32
32
|
"test:ci": "yarn test --ci",
|
|
33
33
|
"test:treeshake": "agadoo ./dist/esm/index.mjs",
|
|
34
34
|
"clean": "rimraf dist node_modules",
|
|
@@ -82,6 +82,11 @@ Alternatively use the `workingLabelHidden` prop to hide the button label all tog
|
|
|
82
82
|
|
|
83
83
|
<Canvas of={ButtonStories.Working} />
|
|
84
84
|
|
|
85
|
+
### Controlling the working state
|
|
86
|
+
Here is an example of controlling whether the button is 'working' or not, using state.
|
|
87
|
+
|
|
88
|
+
<Canvas of={ButtonStories.ResolveWorking} />
|
|
89
|
+
|
|
85
90
|
### Loading
|
|
86
91
|
|
|
87
92
|
Use the <LinkTo pageId="components-loading-states-loadinginput">LoadingInput</LinkTo> component for button placeholders.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react"
|
|
1
|
+
import React, { useState } from "react"
|
|
2
2
|
import { StoryObj, Meta } from "@storybook/react"
|
|
3
3
|
import { AddIcon, ArrowRightIcon } from "~components/Icon"
|
|
4
4
|
import { LoadingInput } from "~components/Loading"
|
|
@@ -142,3 +142,29 @@ export const NativeFormButton: Story = {
|
|
|
142
142
|
),
|
|
143
143
|
parameters: { controls: { disable: true } },
|
|
144
144
|
}
|
|
145
|
+
|
|
146
|
+
export const ResolveWorking: Story = {
|
|
147
|
+
render: () => {
|
|
148
|
+
const [state, setState] = useState<"Ready" | "Working" | "Completed">(
|
|
149
|
+
"Ready"
|
|
150
|
+
)
|
|
151
|
+
const handleClick = (): void => {
|
|
152
|
+
if (state === "Ready") {
|
|
153
|
+
setState("Working")
|
|
154
|
+
setTimeout(() => setState("Completed"), 3000)
|
|
155
|
+
} else {
|
|
156
|
+
setState("Ready")
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<Button
|
|
162
|
+
label={state}
|
|
163
|
+
working={state === "Working"}
|
|
164
|
+
workingLabel="Button is doing some work"
|
|
165
|
+
workingLabelHidden
|
|
166
|
+
onClick={handleClick}
|
|
167
|
+
/>
|
|
168
|
+
)
|
|
169
|
+
},
|
|
170
|
+
}
|
|
@@ -111,3 +111,72 @@ describe("<GenericButton /> with native HTML `form` attributes", () => {
|
|
|
111
111
|
).toHaveAttribute("form", buttonFormAttributes.form)
|
|
112
112
|
})
|
|
113
113
|
})
|
|
114
|
+
|
|
115
|
+
describe("<GenericButton /> `working` accessible states", () => {
|
|
116
|
+
it("renders a button without aria-live by default", () => {
|
|
117
|
+
const { getByTestId } = render(
|
|
118
|
+
<GenericButton
|
|
119
|
+
data-testid="id--generic-test"
|
|
120
|
+
id="id--button"
|
|
121
|
+
label="button label"
|
|
122
|
+
/>
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const button = getByTestId("id--generic-test")
|
|
126
|
+
// The id is passed to the element not the container so we have to get its parent
|
|
127
|
+
const buttonContainer = button.parentElement
|
|
128
|
+
|
|
129
|
+
expect(buttonContainer).not.toHaveAttribute("aria-live", "")
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it("renders a button with aria-live if working label if provided", () => {
|
|
133
|
+
const { getByTestId } = render(
|
|
134
|
+
<GenericButton
|
|
135
|
+
data-testid="id--generic-test"
|
|
136
|
+
id="id--button"
|
|
137
|
+
label="button label"
|
|
138
|
+
workingLabel="Loading"
|
|
139
|
+
/>
|
|
140
|
+
)
|
|
141
|
+
const button = getByTestId("id--generic-test")
|
|
142
|
+
const buttonContainer = button.parentElement
|
|
143
|
+
|
|
144
|
+
expect(buttonContainer).toHaveAttribute("aria-live", "polite")
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it("renders a link button with aria-live if working label if provided", () => {
|
|
148
|
+
const { getByTestId } = render(
|
|
149
|
+
<GenericButton
|
|
150
|
+
data-testid="id--generic-test"
|
|
151
|
+
id="id--button"
|
|
152
|
+
label="button label"
|
|
153
|
+
workingLabel="Loading"
|
|
154
|
+
href="/"
|
|
155
|
+
/>
|
|
156
|
+
)
|
|
157
|
+
const button = getByTestId("id--generic-test")
|
|
158
|
+
const buttonContainer = button.parentElement
|
|
159
|
+
|
|
160
|
+
expect(buttonContainer).toHaveAttribute("aria-live", "polite")
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it("renders a custom button with aria-live if working label if provided", () => {
|
|
164
|
+
const { getByTestId } = render(
|
|
165
|
+
<GenericButton
|
|
166
|
+
data-testid="id--generic-test"
|
|
167
|
+
id="id--button"
|
|
168
|
+
label="button label"
|
|
169
|
+
workingLabel="Loading"
|
|
170
|
+
component={props => (
|
|
171
|
+
<button type="button" {...props}>
|
|
172
|
+
Custom button
|
|
173
|
+
</button>
|
|
174
|
+
)}
|
|
175
|
+
/>
|
|
176
|
+
)
|
|
177
|
+
const button = getByTestId("id--generic-test")
|
|
178
|
+
const buttonContainer = button.parentElement
|
|
179
|
+
|
|
180
|
+
expect(buttonContainer).toHaveAttribute("aria-live", "polite")
|
|
181
|
+
})
|
|
182
|
+
})
|
|
@@ -103,7 +103,7 @@ describe("<DatePicker />", () => {
|
|
|
103
103
|
})
|
|
104
104
|
expect(arrowButton).toHaveFocus()
|
|
105
105
|
})
|
|
106
|
-
})
|
|
106
|
+
}, 6000)
|
|
107
107
|
|
|
108
108
|
it("should validate and close the calendar when the user presses the Enter key while focus is in the input", async () => {
|
|
109
109
|
render(<DatePickerWrapper disabledDates={[new Date("2022-05-01")]} />)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React from "react"
|
|
2
|
+
import { ToastNotificationsList } from "~components/Notification"
|
|
3
|
+
import { ToastNotificationProvider } from "~components/Notification/ToastNotification/context/ToastNotificationContext"
|
|
2
4
|
import { FontDefinitions } from "./subcomponents/FontDefinitions"
|
|
3
5
|
import { OptionalIntlProvider } from "./subcomponents/OptionalIntlProvider"
|
|
4
6
|
|
|
@@ -13,7 +15,10 @@ export const KaizenProvider = ({
|
|
|
13
15
|
}: KaizenProviderProps): JSX.Element => (
|
|
14
16
|
<OptionalIntlProvider locale={locale}>
|
|
15
17
|
<>
|
|
16
|
-
|
|
18
|
+
<ToastNotificationProvider>
|
|
19
|
+
<ToastNotificationsList />
|
|
20
|
+
{children}
|
|
21
|
+
</ToastNotificationProvider>
|
|
17
22
|
<FontDefinitions />
|
|
18
23
|
</>
|
|
19
24
|
</OptionalIntlProvider>
|
|
@@ -13,10 +13,10 @@ const ContextModalWrapper = ({
|
|
|
13
13
|
<ContextModal
|
|
14
14
|
isOpen={true}
|
|
15
15
|
title="Example modal title"
|
|
16
|
-
onConfirm={()
|
|
17
|
-
onDismiss={()
|
|
16
|
+
onConfirm={() => undefined}
|
|
17
|
+
onDismiss={() => undefined}
|
|
18
18
|
secondaryLabel="Example secondary"
|
|
19
|
-
onSecondaryAction={()
|
|
19
|
+
onSecondaryAction={() => undefined}
|
|
20
20
|
{...props}
|
|
21
21
|
>
|
|
22
22
|
{children}
|
|
@@ -18,6 +18,7 @@ export type ContextModalSecondaryActionProps =
|
|
|
18
18
|
}
|
|
19
19
|
| {
|
|
20
20
|
secondaryLabel?: undefined
|
|
21
|
+
onSecondaryAction?: never
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export type ContextModalProps = Readonly<
|
|
@@ -56,6 +57,7 @@ export const ContextModal = ({
|
|
|
56
57
|
layout = "portrait",
|
|
57
58
|
title,
|
|
58
59
|
onConfirm,
|
|
60
|
+
onDismiss: propsOnDismiss,
|
|
59
61
|
onAfterLeave,
|
|
60
62
|
confirmLabel = "Confirm",
|
|
61
63
|
confirmWorking,
|
|
@@ -63,9 +65,11 @@ export const ContextModal = ({
|
|
|
63
65
|
children,
|
|
64
66
|
contentHeader,
|
|
65
67
|
image,
|
|
68
|
+
secondaryLabel,
|
|
69
|
+
onSecondaryAction,
|
|
66
70
|
...props
|
|
67
71
|
}: ContextModalProps): JSX.Element => {
|
|
68
|
-
const onDismiss = confirmWorking ? undefined :
|
|
72
|
+
const onDismiss = confirmWorking ? undefined : propsOnDismiss
|
|
69
73
|
|
|
70
74
|
const footerActions: ButtonProps[] = []
|
|
71
75
|
|
|
@@ -82,10 +86,10 @@ export const ContextModal = ({
|
|
|
82
86
|
footerActions.push({ ...confirmAction, ...workingProps })
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
if (
|
|
89
|
+
if (secondaryLabel) {
|
|
86
90
|
footerActions.push({
|
|
87
|
-
label:
|
|
88
|
-
onClick:
|
|
91
|
+
label: secondaryLabel,
|
|
92
|
+
onClick: onSecondaryAction,
|
|
89
93
|
disabled: !!confirmWorking,
|
|
90
94
|
})
|
|
91
95
|
}
|
|
@@ -126,7 +130,7 @@ export const ContextModal = ({
|
|
|
126
130
|
{onConfirm != null && (
|
|
127
131
|
<div
|
|
128
132
|
className={
|
|
129
|
-
|
|
133
|
+
secondaryLabel
|
|
130
134
|
? styles.footerWithSecondaryAction
|
|
131
135
|
: styles.footer
|
|
132
136
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState } from "react"
|
|
2
|
-
import { render, waitFor } from "@testing-library/react"
|
|
2
|
+
import { render, waitFor, screen } from "@testing-library/react"
|
|
3
3
|
import userEvent from "@testing-library/user-event"
|
|
4
4
|
import { MultiSelect, MultiSelectProps } from "./MultiSelect"
|
|
5
5
|
|
|
@@ -331,3 +331,58 @@ describe("Removing all options", () => {
|
|
|
331
331
|
})
|
|
332
332
|
})
|
|
333
333
|
})
|
|
334
|
+
|
|
335
|
+
describe("Has validation status", () => {
|
|
336
|
+
it("renders a validation message", () => {
|
|
337
|
+
render(
|
|
338
|
+
<MultiSelectWrapper
|
|
339
|
+
selectedValues={new Set(["waffle"])}
|
|
340
|
+
validationMessage={{
|
|
341
|
+
status: "error",
|
|
342
|
+
message: "No waffles are available",
|
|
343
|
+
}}
|
|
344
|
+
/>
|
|
345
|
+
)
|
|
346
|
+
expect(screen.getByText("No waffles are available")).toBeInTheDocument()
|
|
347
|
+
})
|
|
348
|
+
it("describes the Toggle", () => {
|
|
349
|
+
render(
|
|
350
|
+
<MultiSelectWrapper
|
|
351
|
+
selectedValues={new Set(["waffle"])}
|
|
352
|
+
label="Breakfast menu"
|
|
353
|
+
validationMessage={{
|
|
354
|
+
status: "caution",
|
|
355
|
+
message: "Only four waffles remain",
|
|
356
|
+
}}
|
|
357
|
+
/>
|
|
358
|
+
)
|
|
359
|
+
expect(
|
|
360
|
+
screen.getByRole("button", {
|
|
361
|
+
name: "Breakfast menu",
|
|
362
|
+
description: "Only four waffles remain",
|
|
363
|
+
})
|
|
364
|
+
).toBeInTheDocument()
|
|
365
|
+
})
|
|
366
|
+
it("announces the validation message before the Toggle's description", () => {
|
|
367
|
+
const description = "Choose you breakfast."
|
|
368
|
+
const validationMessage = "Only four waffles remain."
|
|
369
|
+
|
|
370
|
+
render(
|
|
371
|
+
<MultiSelectWrapper
|
|
372
|
+
selectedValues={new Set(["waffle"])}
|
|
373
|
+
label="Breakfast menu"
|
|
374
|
+
description={description}
|
|
375
|
+
validationMessage={{
|
|
376
|
+
status: "caution",
|
|
377
|
+
message: "Only four waffles remain.",
|
|
378
|
+
}}
|
|
379
|
+
/>
|
|
380
|
+
)
|
|
381
|
+
expect(
|
|
382
|
+
screen.getByRole("button", {
|
|
383
|
+
name: "Breakfast menu",
|
|
384
|
+
description: `${validationMessage} ${description}`,
|
|
385
|
+
})
|
|
386
|
+
).toBeInTheDocument()
|
|
387
|
+
})
|
|
388
|
+
})
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "./subcomponents/MultiSelectOptions"
|
|
11
11
|
import { MultiSelectToggle } from "./subcomponents/MultiSelectToggle"
|
|
12
12
|
import { Popover, useFloating } from "./subcomponents/Popover"
|
|
13
|
-
import { MultiSelectOption } from "./types"
|
|
13
|
+
import { MultiSelectOption, ValidationMessage } from "./types"
|
|
14
14
|
import styles from "./MultiSelect.module.scss"
|
|
15
15
|
|
|
16
16
|
export type MultiSelectProps = {
|
|
@@ -24,6 +24,8 @@ export type MultiSelectProps = {
|
|
|
24
24
|
onSelectedValuesChange: MultiSelectOptionsProps["onChange"]
|
|
25
25
|
isOpen: boolean
|
|
26
26
|
onOpenChange: (isOpen: boolean) => void
|
|
27
|
+
/** A status and message to provide context to the validation issue */
|
|
28
|
+
validationMessage?: ValidationMessage
|
|
27
29
|
} & OverrideClassName<HTMLAttributes<HTMLDivElement>>
|
|
28
30
|
|
|
29
31
|
export const MultiSelect = ({
|
|
@@ -36,10 +38,12 @@ export const MultiSelect = ({
|
|
|
36
38
|
isOpen,
|
|
37
39
|
onOpenChange,
|
|
38
40
|
classNameOverride,
|
|
41
|
+
validationMessage,
|
|
39
42
|
...restProps
|
|
40
43
|
}: MultiSelectProps): JSX.Element => {
|
|
41
44
|
const id = propsId ?? useId()
|
|
42
45
|
const descriptionId = `${id}-description`
|
|
46
|
+
const validationId = `${id}-validation-message`
|
|
43
47
|
|
|
44
48
|
const toggleButtonRef = useRef<HTMLButtonElement>(null)
|
|
45
49
|
const { refs } = useFloating()
|
|
@@ -88,20 +92,23 @@ export const MultiSelect = ({
|
|
|
88
92
|
ref={toggleButtonRef}
|
|
89
93
|
id={`${id}--toggle`}
|
|
90
94
|
aria-labelledby={`${id}--label`}
|
|
91
|
-
aria-describedby={descriptionId}
|
|
95
|
+
aria-describedby={`${validationId} ${descriptionId}`}
|
|
92
96
|
aria-controls={`${id}--popover`}
|
|
93
97
|
onClick={handleToggleClick}
|
|
94
98
|
isOpen={isOpen}
|
|
95
99
|
selectedOptions={Array.from(selectedValues).map(
|
|
96
100
|
value => itemsMap[value]
|
|
97
101
|
)}
|
|
102
|
+
status={validationMessage?.status}
|
|
98
103
|
onRemoveOption={handleOnRemoveOption}
|
|
99
104
|
onRemoveAllOptions={handleRemoveAllOptions}
|
|
100
105
|
/>
|
|
101
106
|
</div>
|
|
102
107
|
|
|
108
|
+
{validationMessage && (
|
|
109
|
+
<FieldMessage id={validationId} {...validationMessage} />
|
|
110
|
+
)}
|
|
103
111
|
{description && <FieldMessage id={descriptionId} message={description} />}
|
|
104
|
-
{/* ValidationMessage */}
|
|
105
112
|
|
|
106
113
|
{isOpen && (
|
|
107
114
|
<Popover
|
|
@@ -64,3 +64,13 @@ The open state is controlled by the consumer through the `isOpen` and `onOpenCha
|
|
|
64
64
|
You can add a description which will be linked to the trigger button with aria-describedby.
|
|
65
65
|
|
|
66
66
|
<Canvas of={MultiSelectStories.Description} />
|
|
67
|
+
|
|
68
|
+
### Validation
|
|
69
|
+
|
|
70
|
+
Validation states are defined in the `validationMessage` object, containing a `status` and `message`.
|
|
71
|
+
|
|
72
|
+
<Canvas of={MultiSelectStories.ValidationMessage} />
|
|
73
|
+
|
|
74
|
+
A validation message will render below the MultiSelect and will be read to assistive technologies before the `description` content.
|
|
75
|
+
|
|
76
|
+
<Canvas of={MultiSelectStories.ValidationMessageWithDescription} />
|
|
@@ -51,49 +51,87 @@ const StickerSheetTemplate: StickerSheetStory = {
|
|
|
51
51
|
>(new Set())
|
|
52
52
|
|
|
53
53
|
return (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<StickerSheet.
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
54
|
+
<>
|
|
55
|
+
<StickerSheet
|
|
56
|
+
heading="MultiSelect"
|
|
57
|
+
className={classnames("w-full", IS_CHROMATIC && "pb-160")}
|
|
58
|
+
>
|
|
59
|
+
<StickerSheet.Header
|
|
60
|
+
headings={["Closed", "Open", "No items"]}
|
|
61
|
+
headingsWidth="30%"
|
|
62
|
+
/>
|
|
63
|
+
<StickerSheet.Body>
|
|
64
|
+
<StickerSheet.Row>
|
|
65
|
+
<MultiSelect
|
|
66
|
+
id="id--multi-select-options--closed"
|
|
67
|
+
label="Label"
|
|
68
|
+
description="A short description"
|
|
69
|
+
isOpen={isOpenClosed}
|
|
70
|
+
onOpenChange={setIsOpenClosed}
|
|
71
|
+
onSelectedValuesChange={setSelectedValuesClosed}
|
|
72
|
+
selectedValues={selectedValuesClosed}
|
|
73
|
+
items={options}
|
|
74
|
+
/>
|
|
75
|
+
<MultiSelect
|
|
76
|
+
id="id--multi-select-options--open"
|
|
77
|
+
label="Label"
|
|
78
|
+
description="A short description"
|
|
79
|
+
isOpen={isOpenOpen}
|
|
80
|
+
onOpenChange={setIsOpenOpen}
|
|
81
|
+
onSelectedValuesChange={setSelectedValuesOpen}
|
|
82
|
+
selectedValues={selectedValuesOpen}
|
|
83
|
+
items={options}
|
|
84
|
+
/>
|
|
85
|
+
<MultiSelect
|
|
86
|
+
id="id--multi-select-options--no-items"
|
|
87
|
+
label="Label"
|
|
88
|
+
description="A short description"
|
|
89
|
+
isOpen={isOpenNoItems}
|
|
90
|
+
onOpenChange={setIsOpenNoItems}
|
|
91
|
+
selectedValues={selectedValuesNoItems}
|
|
92
|
+
onSelectedValuesChange={setSelectedValuesNoItems}
|
|
93
|
+
items={[]}
|
|
94
|
+
/>
|
|
95
|
+
</StickerSheet.Row>
|
|
96
|
+
</StickerSheet.Body>
|
|
97
|
+
</StickerSheet>
|
|
98
|
+
<StickerSheet
|
|
99
|
+
heading="Validation"
|
|
100
|
+
className={classnames("w-full", IS_CHROMATIC && "pb-160")}
|
|
101
|
+
>
|
|
102
|
+
<StickerSheet.Header headings={["Error", "Caution"]} />
|
|
103
|
+
<StickerSheet.Body>
|
|
104
|
+
<StickerSheet.Row>
|
|
105
|
+
<MultiSelect
|
|
106
|
+
id="id--multi-select--error"
|
|
107
|
+
label="Label"
|
|
108
|
+
isOpen={false}
|
|
109
|
+
onOpenChange={() => undefined}
|
|
110
|
+
onSelectedValuesChange={() => undefined}
|
|
111
|
+
selectedValues={selectedValuesOpen}
|
|
112
|
+
items={options}
|
|
113
|
+
validationMessage={{
|
|
114
|
+
status: "error",
|
|
115
|
+
message: "There are no waffles left.",
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
<MultiSelect
|
|
119
|
+
id="id--multi-select--caution"
|
|
120
|
+
label="Label"
|
|
121
|
+
isOpen={false}
|
|
122
|
+
onOpenChange={() => undefined}
|
|
123
|
+
onSelectedValuesChange={() => undefined}
|
|
124
|
+
selectedValues={selectedValuesNoItems}
|
|
125
|
+
items={options}
|
|
126
|
+
validationMessage={{
|
|
127
|
+
status: "error",
|
|
128
|
+
message: "There are only four pancakes left.",
|
|
129
|
+
}}
|
|
130
|
+
/>
|
|
131
|
+
</StickerSheet.Row>
|
|
132
|
+
</StickerSheet.Body>
|
|
133
|
+
</StickerSheet>
|
|
134
|
+
</>
|
|
97
135
|
)
|
|
98
136
|
},
|
|
99
137
|
}
|
|
@@ -150,3 +150,24 @@ export const Description: Story = {
|
|
|
150
150
|
description: "Breakfast will be delivered directly to your house",
|
|
151
151
|
},
|
|
152
152
|
}
|
|
153
|
+
|
|
154
|
+
export const ValidationMessage: Story = {
|
|
155
|
+
...MultiSelectTemplate,
|
|
156
|
+
args: {
|
|
157
|
+
validationMessage: {
|
|
158
|
+
status: "error",
|
|
159
|
+
message: "There are no more waffles",
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const ValidationMessageWithDescription: Story = {
|
|
165
|
+
...MultiSelectTemplate,
|
|
166
|
+
args: {
|
|
167
|
+
description: "Breakfast will be delivered to your house.",
|
|
168
|
+
validationMessage: {
|
|
169
|
+
status: "caution",
|
|
170
|
+
message: "Only five more waffles remain.",
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
}
|
|
@@ -18,6 +18,15 @@
|
|
|
18
18
|
border-color: $color-gray-600;
|
|
19
19
|
background-color: $color-gray-200;
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
&.error {
|
|
23
|
+
border: $border-solid-border-style $border-solid-border-width $color-red-500;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&.caution {
|
|
27
|
+
border: $border-solid-border-style $border-solid-border-width
|
|
28
|
+
$color-yellow-600;
|
|
29
|
+
}
|
|
21
30
|
}
|
|
22
31
|
|
|
23
32
|
.toggleButton {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { HTMLAttributes, forwardRef } from "react"
|
|
2
2
|
import classnames from "classnames"
|
|
3
3
|
import { ClearButton } from "~components/ClearButton"
|
|
4
|
+
import { FieldMessageProps } from "~components/FieldMessage"
|
|
4
5
|
import { ChevronDownIcon, ChevronUpIcon } from "~components/Icon"
|
|
5
6
|
import { RemovableTag } from "~components/__future__/Tag"
|
|
6
7
|
import { OverrideClassName } from "~types/OverrideClassName"
|
|
@@ -13,6 +14,7 @@ export type MultiSelectToggleProps = {
|
|
|
13
14
|
["aria-controls"]: string
|
|
14
15
|
selectedOptions: MultiSelectOption[]
|
|
15
16
|
isOpen?: boolean
|
|
17
|
+
status?: FieldMessageProps["status"]
|
|
16
18
|
onRemoveOption: (optionValue: MultiSelectOption["value"]) => void
|
|
17
19
|
onRemoveAllOptions: () => void
|
|
18
20
|
} & OverrideClassName<HTMLAttributes<HTMLDivElement>>
|
|
@@ -32,6 +34,7 @@ export const MultiSelectToggle = forwardRef<
|
|
|
32
34
|
selectedOptions,
|
|
33
35
|
onRemoveOption,
|
|
34
36
|
onRemoveAllOptions,
|
|
37
|
+
status,
|
|
35
38
|
...restProps
|
|
36
39
|
},
|
|
37
40
|
ref
|
|
@@ -43,7 +46,11 @@ export const MultiSelectToggle = forwardRef<
|
|
|
43
46
|
*/}
|
|
44
47
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
|
45
48
|
<div
|
|
46
|
-
className={classnames(
|
|
49
|
+
className={classnames(
|
|
50
|
+
styles.multiSelectToggle,
|
|
51
|
+
classNameOverride,
|
|
52
|
+
status && styles[status]
|
|
53
|
+
)}
|
|
47
54
|
onClick={onClick}
|
|
48
55
|
{...restProps}
|
|
49
56
|
>
|
|
@@ -92,6 +92,23 @@ const StickerSheetTemplate: StickerSheetStory = {
|
|
|
92
92
|
</StickerSheet.Body>
|
|
93
93
|
</StickerSheet>
|
|
94
94
|
|
|
95
|
+
<StickerSheet
|
|
96
|
+
isReversed={isReversed}
|
|
97
|
+
heading="Validation states"
|
|
98
|
+
className="w-full"
|
|
99
|
+
>
|
|
100
|
+
<StickerSheet.Header
|
|
101
|
+
headings={["Error", "Caution"]}
|
|
102
|
+
verticalHeadingsWidth="10rem"
|
|
103
|
+
/>
|
|
104
|
+
<StickerSheet.Body>
|
|
105
|
+
<StickerSheet.Row>
|
|
106
|
+
<MultiSelectToggle status="error" {...defaultProps} />
|
|
107
|
+
<MultiSelectToggle status="caution" {...defaultProps} />
|
|
108
|
+
</StickerSheet.Row>
|
|
109
|
+
</StickerSheet.Body>
|
|
110
|
+
</StickerSheet>
|
|
111
|
+
|
|
95
112
|
<StickerSheet
|
|
96
113
|
isReversed={isReversed}
|
|
97
114
|
heading="Has selected values"
|
package/src/MultiSelect/types.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import { FieldMessageStatus } from "~components/FieldMessage"
|
|
2
|
+
|
|
1
3
|
export type MultiSelectOption = {
|
|
2
4
|
label: string
|
|
3
5
|
// Used for "value" attribute in option - must be unique
|
|
4
6
|
value: string | number
|
|
5
7
|
}
|
|
8
|
+
|
|
9
|
+
export type ValidationMessage = {
|
|
10
|
+
status?: Extract<FieldMessageStatus, "error" | "caution">
|
|
11
|
+
message: string | React.ReactElement
|
|
12
|
+
}
|