@kaizen/components 1.42.7 → 1.44.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/LikertScaleLegacy/LikertScaleLegacy.cjs +6 -4
- package/dist/cjs/LikertScaleLegacy/LikertScaleLegacy.module.scss.cjs +2 -0
- package/dist/cjs/Modal/ConfirmationModal/ConfirmationModal.cjs +4 -2
- package/dist/cjs/Modal/ContextModal/ContextModal.cjs +4 -2
- package/dist/cjs/Modal/GenericModal/GenericModal.cjs +12 -9
- package/dist/cjs/Modal/InputEditModal/InputEditModal.cjs +4 -2
- package/dist/cjs/RichTextEditor/utils/plugins/LinkManager/components/LinkModal/LinkModal.cjs +5 -1
- package/dist/cjs/index.css +3 -3
- package/dist/esm/LikertScaleLegacy/LikertScaleLegacy.mjs +6 -4
- package/dist/esm/LikertScaleLegacy/LikertScaleLegacy.module.scss.mjs +2 -0
- package/dist/esm/Modal/ConfirmationModal/ConfirmationModal.mjs +4 -2
- package/dist/esm/Modal/ContextModal/ContextModal.mjs +4 -2
- package/dist/esm/Modal/GenericModal/GenericModal.mjs +12 -9
- package/dist/esm/Modal/InputEditModal/InputEditModal.mjs +4 -2
- package/dist/esm/RichTextEditor/utils/plugins/LinkManager/components/LinkModal/LinkModal.mjs +5 -1
- package/dist/esm/index.css +3 -3
- package/dist/styles.css +1 -1
- package/dist/types/LikertScaleLegacy/LikertScaleLegacy.d.ts +3 -2
- package/dist/types/LikertScaleLegacy/types.d.ts +1 -0
- package/dist/types/Modal/ConfirmationModal/ConfirmationModal.d.ts +4 -1
- package/dist/types/Modal/ContextModal/ContextModal.d.ts +4 -1
- package/dist/types/Modal/GenericModal/GenericModal.d.ts +4 -1
- package/dist/types/Modal/InputEditModal/InputEditModal.d.ts +4 -1
- package/package.json +3 -3
- package/src/LikertScaleLegacy/LikertScaleLegacy.module.scss +66 -17
- package/src/LikertScaleLegacy/LikertScaleLegacy.tsx +6 -1
- package/src/LikertScaleLegacy/_docs/LikertScaleLegacy.stickersheet.stories.tsx +26 -4
- package/src/LikertScaleLegacy/types.ts +2 -0
- package/src/Modal/ConfirmationModal/ConfirmationModal.tsx +5 -0
- package/src/Modal/ContextModal/ContextModal.tsx +5 -0
- package/src/Modal/GenericModal/GenericModal.tsx +18 -10
- package/src/Modal/GenericModal/_docs/GenericModal.spec.stories.tsx +124 -0
- package/src/Modal/InputEditModal/InputEditModal.tsx +5 -0
- package/src/Modal/InputEditModal/_docs/InputEditModal.mdx +11 -0
- package/src/Modal/InputEditModal/_docs/InputEditModal.stories.tsx +63 -1
- package/src/RichTextEditor/utils/plugins/LinkManager/components/LinkModal/LinkModal.tsx +1 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { Scale, ScaleItem } from "./types";
|
|
2
|
+
import { Scale, ScaleItem, ColorSchema } from "./types";
|
|
3
3
|
export type LikertScaleProps = {
|
|
4
4
|
labelId: string;
|
|
5
5
|
scale: Scale;
|
|
@@ -10,6 +10,7 @@ export type LikertScaleProps = {
|
|
|
10
10
|
automationId?: string;
|
|
11
11
|
"data-testid"?: string;
|
|
12
12
|
reversed?: boolean;
|
|
13
|
+
colorSchema?: ColorSchema | "classical";
|
|
13
14
|
validationMessage?: string;
|
|
14
15
|
status?: "default" | "error";
|
|
15
16
|
onSelect: (value: ScaleItem | null) => void;
|
|
@@ -18,4 +19,4 @@ export type LikertScaleProps = {
|
|
|
18
19
|
* {@link https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3082060201/Likert+Scale Guidance} |
|
|
19
20
|
* {@link https://cultureamp.design/?path=/docs/components-likertscalelegacy--docs Storybook}
|
|
20
21
|
*/
|
|
21
|
-
export declare const LikertScaleLegacy: ({ scale, selectedItem, reversed, "data-testid": dataTestId, onSelect, validationMessage, status, labelId, }: LikertScaleProps) => JSX.Element;
|
|
22
|
+
export declare const LikertScaleLegacy: ({ scale, selectedItem, reversed, colorSchema, "data-testid": dataTestId, onSelect, validationMessage, status, labelId, }: LikertScaleProps) => JSX.Element;
|
|
@@ -13,6 +13,9 @@ export type ConfirmationModalProps = {
|
|
|
13
13
|
title: string;
|
|
14
14
|
onConfirm?: () => void;
|
|
15
15
|
onDismiss: () => void;
|
|
16
|
+
/** A callback that is triggered after the modal is opened. */
|
|
17
|
+
onAfterEnter?: () => void;
|
|
18
|
+
/** A callback that is triggered after the modal is closed. */
|
|
16
19
|
onAfterLeave?: () => void;
|
|
17
20
|
confirmLabel?: string;
|
|
18
21
|
dismissLabel?: string;
|
|
@@ -32,7 +35,7 @@ type Mood = "positive" | "informative" | "negative" | "cautionary" | "assertive"
|
|
|
32
35
|
* {@link https://cultureamp.design/?path=/docs/components-modals-confirmationmodal--docs Storybook}
|
|
33
36
|
*/
|
|
34
37
|
export declare const ConfirmationModal: {
|
|
35
|
-
({ isOpen, isProminent, unpadded, mood, title, onConfirm, onAfterLeave, confirmLabel, dismissLabel, confirmWorking, onDismiss: propsOnDismiss, children, ...props }: ConfirmationModalProps): JSX.Element;
|
|
38
|
+
({ isOpen, isProminent, unpadded, mood, title, onConfirm, onAfterLeave, onAfterEnter, confirmLabel, dismissLabel, confirmWorking, onDismiss: propsOnDismiss, children, ...props }: ConfirmationModalProps): JSX.Element;
|
|
36
39
|
displayName: string;
|
|
37
40
|
};
|
|
38
41
|
export {};
|
|
@@ -16,6 +16,9 @@ export type ContextModalProps = Readonly<{
|
|
|
16
16
|
title: string;
|
|
17
17
|
onConfirm?: () => void;
|
|
18
18
|
onDismiss: () => void;
|
|
19
|
+
/** A callback that is triggered after the modal is opened. */
|
|
20
|
+
onAfterEnter?: () => void;
|
|
21
|
+
/** A callback that is triggered after the modal is closed. */
|
|
19
22
|
onAfterLeave?: () => void;
|
|
20
23
|
confirmLabel?: string;
|
|
21
24
|
confirmWorking?: {
|
|
@@ -36,6 +39,6 @@ export type ContextModalProps = Readonly<{
|
|
|
36
39
|
* {@link https://cultureamp.design/?path=/docs/components-modals--contextmodal--docs Storybook}
|
|
37
40
|
*/
|
|
38
41
|
export declare const ContextModal: {
|
|
39
|
-
({ isOpen, unpadded, layout, title, onConfirm, onDismiss: propsOnDismiss, onAfterLeave, confirmLabel, confirmWorking, renderBackground, children, contentHeader, image, secondaryLabel, onSecondaryAction, ...props }: ContextModalProps): JSX.Element;
|
|
42
|
+
({ isOpen, unpadded, layout, title, onConfirm, onDismiss: propsOnDismiss, onAfterLeave, onAfterEnter, confirmLabel, confirmWorking, renderBackground, children, contentHeader, image, secondaryLabel, onSecondaryAction, ...props }: ContextModalProps): JSX.Element;
|
|
40
43
|
displayName: string;
|
|
41
44
|
};
|
|
@@ -6,9 +6,12 @@ export type GenericModalProps = {
|
|
|
6
6
|
focusLockDisabled?: boolean;
|
|
7
7
|
onEscapeKeyup?: (event: KeyboardEvent) => void;
|
|
8
8
|
onOutsideModalClick?: (event: React.MouseEvent) => void;
|
|
9
|
+
/** A callback that is triggered after the modal is opened. */
|
|
10
|
+
onAfterEnter?: () => void;
|
|
11
|
+
/** A callback that is triggered after the modal is closed. */
|
|
9
12
|
onAfterLeave?: () => void;
|
|
10
13
|
};
|
|
11
14
|
export declare const GenericModal: {
|
|
12
|
-
({ id: propsId, children, isOpen, focusLockDisabled, onEscapeKeyup, onOutsideModalClick, onAfterLeave: propsOnAfterLeave, }: GenericModalProps): JSX.Element;
|
|
15
|
+
({ id: propsId, children, isOpen, focusLockDisabled, onEscapeKeyup, onOutsideModalClick, onAfterEnter, onAfterLeave: propsOnAfterLeave, }: GenericModalProps): JSX.Element;
|
|
13
16
|
displayName: string;
|
|
14
17
|
};
|
|
@@ -7,6 +7,9 @@ export type InputEditModalProps = {
|
|
|
7
7
|
onSubmit: () => void;
|
|
8
8
|
onSecondaryAction?: () => void;
|
|
9
9
|
onDismiss: () => void;
|
|
10
|
+
/** A callback that is triggered after the modal is opened. */
|
|
11
|
+
onAfterEnter?: () => void;
|
|
12
|
+
/** A callback that is triggered after the modal is closed. */
|
|
10
13
|
onAfterLeave?: () => void;
|
|
11
14
|
localeDirection?: "rtl" | "ltr";
|
|
12
15
|
submitLabel?: string;
|
|
@@ -27,6 +30,6 @@ export type InputEditModalProps = {
|
|
|
27
30
|
* {@link https://cultureamp.design/?path=/docs/components-modals-inputeditmodal--docs Storybook}
|
|
28
31
|
*/
|
|
29
32
|
export declare const InputEditModal: {
|
|
30
|
-
({ isOpen, mood, title, onSubmit, onSecondaryAction, onAfterLeave, localeDirection, submitLabel, dismissLabel, secondaryLabel, submitWorking, children, unpadded, onDismiss: propsOnDismiss, ...props }: InputEditModalProps): JSX.Element;
|
|
33
|
+
({ isOpen, mood, title, onSubmit, onSecondaryAction, onAfterLeave, localeDirection, submitLabel, dismissLabel, secondaryLabel, submitWorking, children, unpadded, onDismiss: propsOnDismiss, onAfterEnter, ...props }: InputEditModalProps): JSX.Element;
|
|
31
34
|
displayName: string;
|
|
32
35
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaizen/components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.44.0",
|
|
4
4
|
"description": "Kaizen component library",
|
|
5
5
|
"author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
|
|
6
6
|
"homepage": "https://cultureamp.design",
|
|
@@ -108,8 +108,8 @@
|
|
|
108
108
|
"ts-node": "^10.9.2",
|
|
109
109
|
"ts-patch": "^3.1.2",
|
|
110
110
|
"typescript-transform-paths": "^3.4.7",
|
|
111
|
-
"@kaizen/
|
|
112
|
-
"@kaizen/
|
|
111
|
+
"@kaizen/tailwind": "1.2.6",
|
|
112
|
+
"@kaizen/design-tokens": "10.3.20"
|
|
113
113
|
},
|
|
114
114
|
"peerDependencies": {
|
|
115
115
|
"@cultureamp/i18n-react-intl": "^2.5.5",
|
|
@@ -23,11 +23,17 @@ $block-height: 35px;
|
|
|
23
23
|
$desktop-rater-width: 220px;
|
|
24
24
|
$desktop-rater-height: 63px;
|
|
25
25
|
|
|
26
|
-
$first: $color-yellow-300;
|
|
27
|
-
$second: $color-yellow-400;
|
|
28
|
-
$third: $color-orange-400;
|
|
29
|
-
$fourth: $color-orange-500;
|
|
30
|
-
$fifth: $color-red-500;
|
|
26
|
+
$classical-first: $color-yellow-300;
|
|
27
|
+
$classical-second: $color-yellow-400;
|
|
28
|
+
$classical-third: $color-orange-400;
|
|
29
|
+
$classical-fourth: $color-orange-500;
|
|
30
|
+
$classical-fifth: $color-red-500;
|
|
31
|
+
|
|
32
|
+
$blue-first: $color-blue-100;
|
|
33
|
+
$blue-second: $color-blue-200;
|
|
34
|
+
$blue-third: $color-blue-300;
|
|
35
|
+
$blue-fourth: $color-blue-400;
|
|
36
|
+
$blue-fifth: $color-blue-500;
|
|
31
37
|
|
|
32
38
|
@mixin pop {
|
|
33
39
|
-webkit-animation: pop cubic-bezier(0, 0.94, 0.32, 1) 0.7s 1;
|
|
@@ -212,45 +218,88 @@ $fifth: $color-red-500;
|
|
|
212
218
|
left: 100%;
|
|
213
219
|
}
|
|
214
220
|
|
|
215
|
-
&.suggested,
|
|
216
|
-
&.selected {
|
|
221
|
+
&.suggested.classicalColorSchema,
|
|
222
|
+
&.selected.classicalColorSchema {
|
|
217
223
|
.field1 {
|
|
218
|
-
background-color: $first;
|
|
224
|
+
background-color: $classical-first;
|
|
219
225
|
|
|
220
226
|
&::after {
|
|
221
|
-
background-color: $first;
|
|
227
|
+
background-color: $classical-first;
|
|
222
228
|
}
|
|
223
229
|
}
|
|
224
230
|
|
|
225
231
|
.field2 {
|
|
226
|
-
background-color: $second;
|
|
232
|
+
background-color: $classical-second;
|
|
227
233
|
|
|
228
234
|
&::after {
|
|
229
|
-
background-color: $second;
|
|
235
|
+
background-color: $classical-second;
|
|
230
236
|
}
|
|
231
237
|
}
|
|
232
238
|
|
|
233
239
|
.field3 {
|
|
234
|
-
background-color: $third;
|
|
240
|
+
background-color: $classical-third;
|
|
235
241
|
|
|
236
242
|
&::after {
|
|
237
|
-
background-color: $third;
|
|
243
|
+
background-color: $classical-third;
|
|
238
244
|
}
|
|
239
245
|
}
|
|
240
246
|
|
|
241
247
|
.field4 {
|
|
242
|
-
background-color: $fourth;
|
|
248
|
+
background-color: $classical-fourth;
|
|
243
249
|
|
|
244
250
|
&::after {
|
|
245
|
-
background-color: $fourth;
|
|
251
|
+
background-color: $classical-fourth;
|
|
246
252
|
}
|
|
247
253
|
}
|
|
248
254
|
|
|
249
255
|
.field5 {
|
|
250
|
-
background-color: $fifth;
|
|
256
|
+
background-color: $classical-fifth;
|
|
251
257
|
|
|
252
258
|
&::after {
|
|
253
|
-
background-color: $fifth;
|
|
259
|
+
background-color: $classical-fifth;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
&.suggested.blueColorSchema,
|
|
265
|
+
&.selected.blueColorSchema {
|
|
266
|
+
.field1 {
|
|
267
|
+
background-color: $blue-first;
|
|
268
|
+
|
|
269
|
+
&::after {
|
|
270
|
+
background-color: $blue-first;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.field2 {
|
|
275
|
+
background-color: $blue-second;
|
|
276
|
+
|
|
277
|
+
&::after {
|
|
278
|
+
background-color: $blue-second;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.field3 {
|
|
283
|
+
background-color: $blue-third;
|
|
284
|
+
|
|
285
|
+
&::after {
|
|
286
|
+
background-color: $blue-third;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.field4 {
|
|
291
|
+
background-color: $blue-fourth;
|
|
292
|
+
|
|
293
|
+
&::after {
|
|
294
|
+
background-color: $blue-fourth;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.field5 {
|
|
299
|
+
background-color: $blue-fifth;
|
|
300
|
+
|
|
301
|
+
&::after {
|
|
302
|
+
background-color: $blue-fifth;
|
|
254
303
|
}
|
|
255
304
|
}
|
|
256
305
|
}
|
|
@@ -3,7 +3,7 @@ import classnames from "classnames"
|
|
|
3
3
|
import { FieldMessage } from "~components/FieldMessage"
|
|
4
4
|
import { CheckIcon } from "~components/Icon"
|
|
5
5
|
import { Text } from "~components/Text"
|
|
6
|
-
import { ScaleValue, Scale, ScaleItem } from "./types"
|
|
6
|
+
import { ScaleValue, Scale, ScaleItem, ColorSchema } from "./types"
|
|
7
7
|
import determineSelectionFromKeyPress from "./utils/determineSelectionFromKeyPress"
|
|
8
8
|
import styles from "./LikertScaleLegacy.module.scss"
|
|
9
9
|
|
|
@@ -22,6 +22,7 @@ export type LikertScaleProps = {
|
|
|
22
22
|
automationId?: string
|
|
23
23
|
"data-testid"?: string
|
|
24
24
|
reversed?: boolean
|
|
25
|
+
colorSchema?: ColorSchema | "classical"
|
|
25
26
|
validationMessage?: string
|
|
26
27
|
status?: "default" | "error"
|
|
27
28
|
onSelect: (value: ScaleItem | null) => void
|
|
@@ -39,6 +40,7 @@ export const LikertScaleLegacy = ({
|
|
|
39
40
|
scale,
|
|
40
41
|
selectedItem,
|
|
41
42
|
reversed,
|
|
43
|
+
colorSchema = "classical",
|
|
42
44
|
"data-testid": dataTestId,
|
|
43
45
|
onSelect,
|
|
44
46
|
validationMessage,
|
|
@@ -156,6 +158,9 @@ export const LikertScaleLegacy = ({
|
|
|
156
158
|
<div
|
|
157
159
|
className={classnames(
|
|
158
160
|
styles.likertItem,
|
|
161
|
+
colorSchema == "blue"
|
|
162
|
+
? styles.blueColorSchema
|
|
163
|
+
: styles.classicalColorSchema,
|
|
159
164
|
styles[`likertItem${item.value}`],
|
|
160
165
|
isSelected && styles.selected,
|
|
161
166
|
isSuggested && styles.suggested,
|
|
@@ -43,7 +43,7 @@ const scale: Scale = [
|
|
|
43
43
|
]
|
|
44
44
|
|
|
45
45
|
const StickerSheetTemplate: StickerSheetStory = {
|
|
46
|
-
render: ({ isReversed }) => (
|
|
46
|
+
render: ({ isReversed, colorSchema }) => (
|
|
47
47
|
<StickerSheet isReversed={isReversed}>
|
|
48
48
|
<StickerSheet.Body>
|
|
49
49
|
<StickerSheet.Row rowTitle="Not rated">
|
|
@@ -53,6 +53,7 @@ const StickerSheetTemplate: StickerSheetStory = {
|
|
|
53
53
|
selectedItem={scale[0]}
|
|
54
54
|
onSelect={(): void => undefined}
|
|
55
55
|
reversed={isReversed}
|
|
56
|
+
colorSchema={colorSchema}
|
|
56
57
|
/>
|
|
57
58
|
</StickerSheet.Row>
|
|
58
59
|
<StickerSheet.Row rowTitle="Strongly disagree">
|
|
@@ -62,6 +63,7 @@ const StickerSheetTemplate: StickerSheetStory = {
|
|
|
62
63
|
selectedItem={scale[1]}
|
|
63
64
|
onSelect={(): void => undefined}
|
|
64
65
|
reversed={isReversed}
|
|
66
|
+
colorSchema={colorSchema}
|
|
65
67
|
/>
|
|
66
68
|
</StickerSheet.Row>
|
|
67
69
|
<StickerSheet.Row rowTitle="Disagree">
|
|
@@ -71,6 +73,7 @@ const StickerSheetTemplate: StickerSheetStory = {
|
|
|
71
73
|
selectedItem={scale[2]}
|
|
72
74
|
onSelect={(): void => undefined}
|
|
73
75
|
reversed={isReversed}
|
|
76
|
+
colorSchema={colorSchema}
|
|
74
77
|
/>
|
|
75
78
|
</StickerSheet.Row>
|
|
76
79
|
<StickerSheet.Row rowTitle="Neither agree or disagree">
|
|
@@ -80,6 +83,7 @@ const StickerSheetTemplate: StickerSheetStory = {
|
|
|
80
83
|
selectedItem={scale[3]}
|
|
81
84
|
onSelect={(): void => undefined}
|
|
82
85
|
reversed={isReversed}
|
|
86
|
+
colorSchema={colorSchema}
|
|
83
87
|
/>
|
|
84
88
|
</StickerSheet.Row>
|
|
85
89
|
<StickerSheet.Row rowTitle="Agree">
|
|
@@ -89,6 +93,7 @@ const StickerSheetTemplate: StickerSheetStory = {
|
|
|
89
93
|
selectedItem={scale[4]}
|
|
90
94
|
onSelect={(): void => undefined}
|
|
91
95
|
reversed={isReversed}
|
|
96
|
+
colorSchema={colorSchema}
|
|
92
97
|
/>
|
|
93
98
|
</StickerSheet.Row>
|
|
94
99
|
<StickerSheet.Row rowTitle="Strongly agree">
|
|
@@ -98,6 +103,7 @@ const StickerSheetTemplate: StickerSheetStory = {
|
|
|
98
103
|
selectedItem={scale[5]}
|
|
99
104
|
onSelect={(): void => undefined}
|
|
100
105
|
reversed={isReversed}
|
|
106
|
+
colorSchema={colorSchema}
|
|
101
107
|
/>
|
|
102
108
|
</StickerSheet.Row>
|
|
103
109
|
<StickerSheet.Row rowTitle="Validation">
|
|
@@ -107,6 +113,7 @@ const StickerSheetTemplate: StickerSheetStory = {
|
|
|
107
113
|
selectedItem={scale[0]}
|
|
108
114
|
onSelect={(): void => undefined}
|
|
109
115
|
reversed={isReversed}
|
|
116
|
+
colorSchema={colorSchema}
|
|
110
117
|
validationMessage="Error message here"
|
|
111
118
|
status="error"
|
|
112
119
|
/>
|
|
@@ -118,18 +125,33 @@ const StickerSheetTemplate: StickerSheetStory = {
|
|
|
118
125
|
|
|
119
126
|
export const StickerSheetDefault: StickerSheetStory = {
|
|
120
127
|
...StickerSheetTemplate,
|
|
121
|
-
name: "Sticker Sheet (Default)",
|
|
128
|
+
name: "Sticker Sheet (Default - Classical)",
|
|
122
129
|
}
|
|
123
130
|
|
|
124
|
-
export const
|
|
131
|
+
export const StickerBlueSheetDefault: StickerSheetStory = {
|
|
125
132
|
...StickerSheetTemplate,
|
|
126
|
-
name: "Sticker Sheet (
|
|
133
|
+
name: "Sticker Sheet (Blue)",
|
|
134
|
+
args: { colorSchema: "blue" },
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const StickerSheetClassicalReversed: StickerSheetStory = {
|
|
138
|
+
...StickerSheetTemplate,
|
|
139
|
+
name: "Sticker Sheet (Classical Reversed)",
|
|
127
140
|
parameters: {
|
|
128
141
|
backgrounds: { default: "Purple 700" },
|
|
129
142
|
},
|
|
130
143
|
args: { isReversed: true },
|
|
131
144
|
}
|
|
132
145
|
|
|
146
|
+
export const StickerSheetBlueReversed: StickerSheetStory = {
|
|
147
|
+
...StickerSheetTemplate,
|
|
148
|
+
name: "Sticker Sheet (Blue Reversed)",
|
|
149
|
+
parameters: {
|
|
150
|
+
backgrounds: { default: "Purple 700" },
|
|
151
|
+
},
|
|
152
|
+
args: { isReversed: true, colorSchema: "blue" },
|
|
153
|
+
}
|
|
154
|
+
|
|
133
155
|
export const StickerSheetRTL: StickerSheetStory = {
|
|
134
156
|
...StickerSheetTemplate,
|
|
135
157
|
name: "Sticker Sheet (RTL)",
|
|
@@ -39,6 +39,9 @@ export type ConfirmationModalProps = {
|
|
|
39
39
|
title: string
|
|
40
40
|
onConfirm?: () => void
|
|
41
41
|
onDismiss: () => void
|
|
42
|
+
/** A callback that is triggered after the modal is opened. */
|
|
43
|
+
onAfterEnter?: () => void
|
|
44
|
+
/** A callback that is triggered after the modal is closed. */
|
|
42
45
|
onAfterLeave?: () => void
|
|
43
46
|
confirmLabel?: string
|
|
44
47
|
dismissLabel?: string
|
|
@@ -99,6 +102,7 @@ export const ConfirmationModal = ({
|
|
|
99
102
|
title,
|
|
100
103
|
onConfirm,
|
|
101
104
|
onAfterLeave,
|
|
105
|
+
onAfterEnter,
|
|
102
106
|
confirmLabel = "Confirm",
|
|
103
107
|
dismissLabel = "Cancel",
|
|
104
108
|
confirmWorking,
|
|
@@ -134,6 +138,7 @@ export const ConfirmationModal = ({
|
|
|
134
138
|
onEscapeKeyup={onDismiss}
|
|
135
139
|
onOutsideModalClick={onDismiss}
|
|
136
140
|
onAfterLeave={onAfterLeave}
|
|
141
|
+
onAfterEnter={onAfterEnter}
|
|
137
142
|
>
|
|
138
143
|
<div className={styles.modal} data-modal {...props}>
|
|
139
144
|
<ModalHeader onDismiss={onDismiss}>
|
|
@@ -32,6 +32,9 @@ export type ContextModalProps = Readonly<
|
|
|
32
32
|
title: string
|
|
33
33
|
onConfirm?: () => void
|
|
34
34
|
onDismiss: () => void
|
|
35
|
+
/** A callback that is triggered after the modal is opened. */
|
|
36
|
+
onAfterEnter?: () => void
|
|
37
|
+
/** A callback that is triggered after the modal is closed. */
|
|
35
38
|
onAfterLeave?: () => void
|
|
36
39
|
confirmLabel?: string
|
|
37
40
|
confirmWorking?: { label: string; labelHidden?: boolean }
|
|
@@ -59,6 +62,7 @@ export const ContextModal = ({
|
|
|
59
62
|
onConfirm,
|
|
60
63
|
onDismiss: propsOnDismiss,
|
|
61
64
|
onAfterLeave,
|
|
65
|
+
onAfterEnter,
|
|
62
66
|
confirmLabel = "Confirm",
|
|
63
67
|
confirmWorking,
|
|
64
68
|
renderBackground,
|
|
@@ -100,6 +104,7 @@ export const ContextModal = ({
|
|
|
100
104
|
onEscapeKeyup={onDismiss}
|
|
101
105
|
onOutsideModalClick={onDismiss}
|
|
102
106
|
onAfterLeave={onAfterLeave}
|
|
107
|
+
onAfterEnter={onAfterEnter}
|
|
103
108
|
>
|
|
104
109
|
<div className={styles.modal} data-modal {...props}>
|
|
105
110
|
{renderBackground && renderBackground()}
|
|
@@ -13,6 +13,9 @@ export type GenericModalProps = {
|
|
|
13
13
|
focusLockDisabled?: boolean
|
|
14
14
|
onEscapeKeyup?: (event: KeyboardEvent) => void
|
|
15
15
|
onOutsideModalClick?: (event: React.MouseEvent) => void
|
|
16
|
+
/** A callback that is triggered after the modal is opened. */
|
|
17
|
+
onAfterEnter?: () => void
|
|
18
|
+
/** A callback that is triggered after the modal is closed. */
|
|
16
19
|
onAfterLeave?: () => void
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -23,6 +26,7 @@ export const GenericModal = ({
|
|
|
23
26
|
focusLockDisabled,
|
|
24
27
|
onEscapeKeyup,
|
|
25
28
|
onOutsideModalClick,
|
|
29
|
+
onAfterEnter,
|
|
26
30
|
onAfterLeave: propsOnAfterLeave,
|
|
27
31
|
}: GenericModalProps): JSX.Element => {
|
|
28
32
|
const reactId = useId()
|
|
@@ -50,18 +54,19 @@ export const GenericModal = ({
|
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
const
|
|
54
|
-
if
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (labelElement) {
|
|
58
|
-
labelElement.focus()
|
|
59
|
-
}
|
|
57
|
+
const focusOnAccessibleLabel = (): void => {
|
|
58
|
+
// Check if focus already exists within the modal
|
|
59
|
+
if (modalLayer?.contains(document.activeElement)) {
|
|
60
|
+
return
|
|
60
61
|
}
|
|
62
|
+
|
|
63
|
+
const labelElement: HTMLElement | null =
|
|
64
|
+
document.getElementById(labelledByID)
|
|
65
|
+
|
|
66
|
+
labelElement?.focus()
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
const a11yWarn = (): void => {
|
|
64
|
-
if (!modalLayer) return
|
|
65
70
|
// Ensure that consumers have provided an element that labels the modal
|
|
66
71
|
// to meet ARIA accessibility guidelines.
|
|
67
72
|
if (!document.getElementById(labelledByID)) {
|
|
@@ -86,8 +91,11 @@ export const GenericModal = ({
|
|
|
86
91
|
|
|
87
92
|
const onAfterEnterHandler = (): void => {
|
|
88
93
|
scrollModalToTop()
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
if (modalLayer) {
|
|
95
|
+
onAfterEnter?.()
|
|
96
|
+
focusOnAccessibleLabel()
|
|
97
|
+
a11yWarn()
|
|
98
|
+
}
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
const onBeforeEnterHandler = (): void => {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { action } from "@storybook/addon-actions"
|
|
3
|
+
import { Meta, StoryObj } from "@storybook/react"
|
|
4
|
+
import { expect, userEvent, within, waitFor } from "@storybook/test"
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
GenericModal,
|
|
8
|
+
ModalAccessibleLabel,
|
|
9
|
+
ModalBody,
|
|
10
|
+
ModalHeader,
|
|
11
|
+
} from "../index"
|
|
12
|
+
|
|
13
|
+
const meta: Meta<typeof GenericModal> = {
|
|
14
|
+
title: "Components/Modals/Generic Modal/Tests",
|
|
15
|
+
component: GenericModal,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default meta
|
|
19
|
+
|
|
20
|
+
type Story = StoryObj<typeof GenericModal>
|
|
21
|
+
|
|
22
|
+
export const TestBase: Story = {
|
|
23
|
+
render: ({ isOpen: propsIsOpen, ...args }) => {
|
|
24
|
+
const [isOpen, setIsOpen] = React.useState<boolean>(propsIsOpen)
|
|
25
|
+
const handleDismiss = (): void => setIsOpen(false)
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
className="border border-gray-500"
|
|
32
|
+
onClick={() => setIsOpen(true)}
|
|
33
|
+
>
|
|
34
|
+
Open Modal
|
|
35
|
+
</button>
|
|
36
|
+
<GenericModal
|
|
37
|
+
{...args}
|
|
38
|
+
isOpen={isOpen}
|
|
39
|
+
onOutsideModalClick={handleDismiss}
|
|
40
|
+
onEscapeKeyup={handleDismiss}
|
|
41
|
+
id="GenericModalTestId"
|
|
42
|
+
>
|
|
43
|
+
<ModalHeader>
|
|
44
|
+
<ModalAccessibleLabel>Test Modal</ModalAccessibleLabel>
|
|
45
|
+
</ModalHeader>
|
|
46
|
+
<ModalBody>
|
|
47
|
+
<form>
|
|
48
|
+
<label htmlFor="modal-input-play-test">Add link</label>
|
|
49
|
+
<input type="text" id="modal-input-play-test" />
|
|
50
|
+
</form>
|
|
51
|
+
</ModalBody>
|
|
52
|
+
</GenericModal>
|
|
53
|
+
</>
|
|
54
|
+
)
|
|
55
|
+
},
|
|
56
|
+
play: async ({ canvasElement, step }) => {
|
|
57
|
+
const { getByRole } = within(canvasElement)
|
|
58
|
+
|
|
59
|
+
const openModalButton = getByRole("button", { name: "Open Modal" })
|
|
60
|
+
|
|
61
|
+
await step("Open modal", async () => {
|
|
62
|
+
await userEvent.click(openModalButton)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
await step("Default focus is shifted to the Accessible title", async () => {
|
|
66
|
+
await waitFor(() => {
|
|
67
|
+
// document has to be use as Modal will append to document body
|
|
68
|
+
expect(document.activeElement).toHaveTextContent("Test Modal")
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const ModalAccessibleLabelRetainsFocus: Story = {
|
|
75
|
+
...TestBase,
|
|
76
|
+
name: "ModalAccessibleLabel retains focus if onAfterEnter is called",
|
|
77
|
+
args: {
|
|
78
|
+
onAfterEnter: () => action("openCallBack"),
|
|
79
|
+
},
|
|
80
|
+
play: async ({ canvasElement, step }) => {
|
|
81
|
+
const { getByRole } = within(canvasElement)
|
|
82
|
+
|
|
83
|
+
const openModalButton = getByRole("button", { name: "Open Modal" })
|
|
84
|
+
|
|
85
|
+
await step("Open modal", async () => {
|
|
86
|
+
await userEvent.click(openModalButton)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
await step("Accessible title still has focus", async () => {
|
|
90
|
+
await waitFor(() => {
|
|
91
|
+
expect(document.activeElement).toHaveTextContent("Test Modal")
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const TriggerOnAfterEnterFocus: Story = {
|
|
98
|
+
...TestBase,
|
|
99
|
+
args: {
|
|
100
|
+
onAfterEnter: () =>
|
|
101
|
+
document.getElementById("modal-input-play-test")?.focus(),
|
|
102
|
+
},
|
|
103
|
+
name: "onAfterEnter can shift focus to internal elements of the modal",
|
|
104
|
+
play: async ({ canvasElement, step }) => {
|
|
105
|
+
const canvas = within(canvasElement)
|
|
106
|
+
const openModalButton = canvas.getByRole("button", { name: "Open Modal" })
|
|
107
|
+
|
|
108
|
+
await step("Open modal", async () => {
|
|
109
|
+
await userEvent.click(openModalButton)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
await step("Expect activeElement to be the Input", async () => {
|
|
113
|
+
await waitFor(() => {
|
|
114
|
+
expect(document.activeElement).toHaveAccessibleName("Add link")
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
await step("Expect to be able to type without shifting focus", async () => {
|
|
119
|
+
await userEvent.keyboard(
|
|
120
|
+
"All lorem and no ipsum make dolar a dull boy..."
|
|
121
|
+
)
|
|
122
|
+
})
|
|
123
|
+
},
|
|
124
|
+
}
|
|
@@ -19,6 +19,9 @@ export type InputEditModalProps = {
|
|
|
19
19
|
onSubmit: () => void
|
|
20
20
|
onSecondaryAction?: () => void
|
|
21
21
|
onDismiss: () => void
|
|
22
|
+
/** A callback that is triggered after the modal is opened. */
|
|
23
|
+
onAfterEnter?: () => void
|
|
24
|
+
/** A callback that is triggered after the modal is closed. */
|
|
22
25
|
onAfterLeave?: () => void
|
|
23
26
|
localeDirection?: "rtl" | "ltr"
|
|
24
27
|
submitLabel?: string
|
|
@@ -51,6 +54,7 @@ export const InputEditModal = ({
|
|
|
51
54
|
children,
|
|
52
55
|
unpadded = false,
|
|
53
56
|
onDismiss: propsOnDismiss,
|
|
57
|
+
onAfterEnter,
|
|
54
58
|
...props
|
|
55
59
|
}: InputEditModalProps): JSX.Element => {
|
|
56
60
|
const onDismiss = submitWorking ? undefined : propsOnDismiss
|
|
@@ -79,6 +83,7 @@ export const InputEditModal = ({
|
|
|
79
83
|
isOpen={isOpen}
|
|
80
84
|
onEscapeKeyup={onDismiss}
|
|
81
85
|
onAfterLeave={onAfterLeave}
|
|
86
|
+
onAfterEnter={onAfterEnter}
|
|
82
87
|
>
|
|
83
88
|
<div className={styles.modal} dir={localeDirection} data-modal {...props}>
|
|
84
89
|
<ModalHeader onDismiss={onDismiss}>
|
|
@@ -39,3 +39,14 @@ When your modal is preforming a destructive action eg. delete customer data.
|
|
|
39
39
|
### Unpadded
|
|
40
40
|
|
|
41
41
|
<Canvas of={InputEditModalStories.Unpadded} />
|
|
42
|
+
|
|
43
|
+
## onAfterEnter and shifting focus
|
|
44
|
+
|
|
45
|
+
It is an important accessibility consideration that any time we shift focus on the page, no important content is skipped that may provide context to assistive technologies. This is why the default behaviour for our modals is to shift focus to the accessible title.
|
|
46
|
+
|
|
47
|
+
There are instances, such as single input modals, where shifting focus may not impact the users context. In these instances, we can leverage the `onAfterEnter` callback to shift focus to an input.
|
|
48
|
+
|
|
49
|
+
<Canvas of={InputEditModalStories.OnAfterEnter} sourceState="shown" />
|
|
50
|
+
|
|
51
|
+
As both the button and input label have clear intent and the focus does not skip past crucial content, this should provide enough context for an end user.
|
|
52
|
+
|