@workday/canvas-kit-docs 14.3.8 → 14.3.10
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/es6/lib/ExampleCodeBlock.d.ts.map +1 -1
- package/dist/es6/lib/ExampleCodeBlock.js +12 -3
- package/dist/es6/lib/stackblitzFiles/packageJSONFile.js +5 -5
- package/dist/es6/lib/stackblitzFiles/packageJSONFile.ts +5 -5
- package/dist/es6/mdx/accessibility/examples/Popups/InlinePopupNoPortal.d.ts +2 -0
- package/dist/es6/mdx/accessibility/examples/Popups/InlinePopupNoPortal.d.ts.map +1 -0
- package/dist/es6/mdx/accessibility/examples/Popups/InlinePopupNoPortal.js +62 -0
- package/dist/es6/mdx/accessibility/examples/Popups/InlinePortalPopup.d.ts +7 -0
- package/dist/es6/mdx/accessibility/examples/Popups/InlinePortalPopup.d.ts.map +1 -0
- package/dist/es6/mdx/accessibility/examples/Popups/InlinePortalPopup.js +63 -0
- package/dist/es6/mdx/accessibility/examples/Popups/PopupAriaOwns.d.ts +7 -0
- package/dist/es6/mdx/accessibility/examples/Popups/PopupAriaOwns.d.ts.map +1 -0
- package/dist/es6/mdx/accessibility/examples/Popups/PopupAriaOwns.js +46 -0
- package/dist/mdx/accessibility/InlinePortals.mdx +20 -0
- package/dist/mdx/accessibility/Popups.mdx +71 -0
- package/dist/mdx/react/dialog/Dialog.mdx +51 -20
- package/dist/mdx/react/modal/Modal.mdx +87 -9
- package/dist/mdx/react/modal/examples/FormModal.tsx +26 -1
- package/dist/mdx/react/modal/examples/ReturnFocus.tsx +137 -39
- package/dist/mdx/react/popup/Popup.mdx +55 -28
- package/dist/mdx/react/popup/examples/Basic.tsx +20 -3
- package/dist/mdx/react/popup/examples/FocusRedirect.tsx +24 -9
- package/dist/mdx/react/popup/examples/InitialFocus.tsx +113 -9
- package/dist/mdx/react/popup/examples/InlinePopup.tsx +125 -0
- package/dist/mdx/react/popup/examples/MultiplePopups.tsx +34 -22
- package/lib/ExampleCodeBlock.tsx +12 -3
- package/package.json +6 -6
|
@@ -5,7 +5,17 @@ import {PrimaryButton} from '@workday/canvas-kit-react/button';
|
|
|
5
5
|
import {Flex} from '@workday/canvas-kit-react/layout';
|
|
6
6
|
import {FormField} from '@workday/canvas-kit-react/form-field';
|
|
7
7
|
import {TextInput} from '@workday/canvas-kit-react/text-input';
|
|
8
|
+
import {Select} from '@workday/canvas-kit-react/select';
|
|
8
9
|
import {plusIcon} from '@workday/canvas-system-icons-web';
|
|
10
|
+
import {createStyles} from '@workday/canvas-kit-styling';
|
|
11
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
12
|
+
|
|
13
|
+
const FAVORITE_COLOR_OPTIONS = ['Blue', 'Yellow'];
|
|
14
|
+
|
|
15
|
+
const flexStyles = createStyles({
|
|
16
|
+
gap: system.space.x4,
|
|
17
|
+
padding: system.space.x2,
|
|
18
|
+
});
|
|
9
19
|
|
|
10
20
|
export default () => {
|
|
11
21
|
const model = useModalModel();
|
|
@@ -18,6 +28,8 @@ export default () => {
|
|
|
18
28
|
console.log('form data', {
|
|
19
29
|
first: (event.currentTarget.elements.namedItem('first') as HTMLInputElement).value,
|
|
20
30
|
last: (event.currentTarget.elements.namedItem('last') as HTMLInputElement).value,
|
|
31
|
+
favoriteColor: (event.currentTarget.elements.namedItem('favoriteColor') as HTMLInputElement)
|
|
32
|
+
.value,
|
|
21
33
|
});
|
|
22
34
|
|
|
23
35
|
// if it looks good, submit to the server and close the modal
|
|
@@ -40,8 +52,21 @@ export default () => {
|
|
|
40
52
|
<FormField.Label>Last Name</FormField.Label>
|
|
41
53
|
<FormField.Input as={TextInput} name="last" />
|
|
42
54
|
</FormField>
|
|
55
|
+
<FormField>
|
|
56
|
+
<FormField.Label>Favorite Color</FormField.Label>
|
|
57
|
+
<FormField.Field>
|
|
58
|
+
<Select items={FAVORITE_COLOR_OPTIONS}>
|
|
59
|
+
<FormField.Input as={Select.Input} name="favoriteColor" />
|
|
60
|
+
<Select.Popper>
|
|
61
|
+
<Select.Card>
|
|
62
|
+
<Select.List>{item => <Select.Item>{item}</Select.Item>}</Select.List>
|
|
63
|
+
</Select.Card>
|
|
64
|
+
</Select.Popper>
|
|
65
|
+
</Select>
|
|
66
|
+
</FormField.Field>
|
|
67
|
+
</FormField>
|
|
43
68
|
</Modal.Body>
|
|
44
|
-
<Flex
|
|
69
|
+
<Flex cs={flexStyles}>
|
|
45
70
|
<Modal.CloseButton>Cancel</Modal.CloseButton>
|
|
46
71
|
<PrimaryButton type="submit">Submit</PrimaryButton>
|
|
47
72
|
</Flex>
|
|
@@ -1,60 +1,158 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import {Modal, useModalModel} from '@workday/canvas-kit-react/modal';
|
|
3
3
|
import {DeleteButton} from '@workday/canvas-kit-react/button';
|
|
4
|
-
import {FormField} from '@workday/canvas-kit-react/form-field';
|
|
5
4
|
import {Flex, Box} from '@workday/canvas-kit-react/layout';
|
|
6
|
-
import {
|
|
5
|
+
import {Heading, Text} from '@workday/canvas-kit-react/text';
|
|
6
|
+
import {Tooltip} from '@workday/canvas-kit-react/tooltip';
|
|
7
|
+
import {trashIcon} from '@workday/canvas-system-icons-web';
|
|
8
|
+
import {useUniqueId} from '@workday/canvas-kit-react/common';
|
|
9
|
+
import {createStyles} from '@workday/canvas-kit-styling';
|
|
10
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
11
|
+
|
|
12
|
+
const INITIAL_FILES = ['Resume.docx', 'Cover_Letter.docx', 'References.docx'];
|
|
13
|
+
|
|
14
|
+
const actionStyles = createStyles({
|
|
15
|
+
gap: system.space.x4,
|
|
16
|
+
padding: system.space.x2,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const headingStyles = createStyles({
|
|
20
|
+
marginY: system.space.zero,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const emptyStateStyles = createStyles({
|
|
24
|
+
maxWidth: '28rem',
|
|
25
|
+
outline: 'none',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const listStyles = createStyles({
|
|
29
|
+
flexDirection: 'column',
|
|
30
|
+
gap: system.space.x4,
|
|
31
|
+
marginY: system.space.zero,
|
|
32
|
+
padding: system.space.zero,
|
|
33
|
+
listStyle: 'none',
|
|
34
|
+
maxWidth: '28rem',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const rowStyles = createStyles({
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
justifyContent: 'space-between',
|
|
40
|
+
gap: system.space.x4,
|
|
41
|
+
width: '100%',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function fileNameId(name: string) {
|
|
45
|
+
return `return-focus-file-${name.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Index of a delete button to focus after removing `deletedIndex`, or empty list. */
|
|
49
|
+
function nextListFocusAfterDelete(deletedIndex: number, lengthBeforeDelete: number) {
|
|
50
|
+
if (lengthBeforeDelete <= 1) {
|
|
51
|
+
return 'empty' as const;
|
|
52
|
+
}
|
|
53
|
+
return deletedIndex < lengthBeforeDelete - 1 ? deletedIndex : deletedIndex - 1;
|
|
54
|
+
}
|
|
7
55
|
|
|
8
56
|
export default () => {
|
|
9
|
-
const
|
|
10
|
-
const [
|
|
57
|
+
const [items, setItems] = React.useState<string[]>(() => [...INITIAL_FILES]);
|
|
58
|
+
const [confirmingFileName, setConfirmingFileName] = React.useState<string | null>(null);
|
|
59
|
+
const bodyTextId = useUniqueId();
|
|
60
|
+
|
|
61
|
+
const returnFocusRef = React.useRef<HTMLButtonElement | null>(null);
|
|
62
|
+
const cancelButtonRef = React.useRef<HTMLButtonElement>(null);
|
|
63
|
+
const deleteButtonRefs = React.useRef<(HTMLButtonElement | null)[]>([]);
|
|
64
|
+
const emptyStateRef = React.useRef<HTMLDivElement>(null);
|
|
65
|
+
const pendingDeleteIndexRef = React.useRef<number | null>(null);
|
|
66
|
+
const postDeleteFocusRef = React.useRef<number | 'empty' | null>(null);
|
|
67
|
+
|
|
11
68
|
const model = useModalModel({
|
|
12
|
-
returnFocusRef
|
|
69
|
+
returnFocusRef,
|
|
70
|
+
initialFocusRef: cancelButtonRef,
|
|
13
71
|
});
|
|
14
72
|
|
|
15
|
-
|
|
16
|
-
|
|
73
|
+
React.useEffect(() => {
|
|
74
|
+
if (model.state.visibility === 'hidden') {
|
|
75
|
+
setConfirmingFileName(null);
|
|
76
|
+
pendingDeleteIndexRef.current = null;
|
|
77
|
+
}
|
|
78
|
+
}, [model.state.visibility]);
|
|
79
|
+
|
|
80
|
+
React.useLayoutEffect(() => {
|
|
81
|
+
if (postDeleteFocusRef.current === null) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (postDeleteFocusRef.current === 'empty') {
|
|
85
|
+
emptyStateRef.current?.focus();
|
|
86
|
+
} else {
|
|
87
|
+
deleteButtonRefs.current[postDeleteFocusRef.current]?.focus();
|
|
88
|
+
}
|
|
89
|
+
postDeleteFocusRef.current = null;
|
|
90
|
+
}, [items]);
|
|
91
|
+
|
|
92
|
+
const openDeleteModal = (index: number) => {
|
|
93
|
+
pendingDeleteIndexRef.current = index;
|
|
94
|
+
setConfirmingFileName(items[index]);
|
|
95
|
+
returnFocusRef.current = deleteButtonRefs.current[index];
|
|
96
|
+
model.events.show();
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const handleConfirmDelete = () => {
|
|
100
|
+
const idx = pendingDeleteIndexRef.current;
|
|
101
|
+
if (idx === null) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
postDeleteFocusRef.current = nextListFocusAfterDelete(idx, items.length);
|
|
105
|
+
pendingDeleteIndexRef.current = null;
|
|
106
|
+
setItems(prev => prev.filter((_, i) => i !== idx));
|
|
17
107
|
};
|
|
18
108
|
|
|
19
109
|
return (
|
|
20
110
|
<Modal model={model}>
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
111
|
+
<Heading as="h4" size="small" cs={headingStyles}>
|
|
112
|
+
Uploaded Files
|
|
113
|
+
</Heading>
|
|
114
|
+
<Box>
|
|
115
|
+
{items.length > 0 ? (
|
|
116
|
+
<Flex as="ul" cs={listStyles}>
|
|
117
|
+
{items.map((name, index) => (
|
|
118
|
+
<Flex as="li" key={name} cs={rowStyles}>
|
|
119
|
+
<Text as="span" id={fileNameId(name)}>
|
|
120
|
+
{name}
|
|
121
|
+
</Text>
|
|
122
|
+
<Tooltip title="Delete">
|
|
123
|
+
<DeleteButton
|
|
124
|
+
aria-describedby={fileNameId(name)}
|
|
125
|
+
icon={trashIcon}
|
|
126
|
+
ref={el => {
|
|
127
|
+
deleteButtonRefs.current[index] = el;
|
|
128
|
+
}}
|
|
129
|
+
onClick={() => openDeleteModal(index)}
|
|
130
|
+
/>
|
|
131
|
+
</Tooltip>
|
|
132
|
+
</Flex>
|
|
133
|
+
))}
|
|
134
|
+
</Flex>
|
|
135
|
+
) : (
|
|
136
|
+
<Box ref={emptyStateRef} tabIndex={-1} cs={emptyStateStyles}>
|
|
137
|
+
<Text>No files remaining.</Text>
|
|
138
|
+
</Box>
|
|
139
|
+
)}
|
|
140
|
+
</Box>
|
|
44
141
|
<Modal.Overlay>
|
|
45
|
-
<Modal.Card>
|
|
46
|
-
<Modal.
|
|
47
|
-
<Modal.Heading>Delete Item</Modal.Heading>
|
|
142
|
+
<Modal.Card aria-describedby={bodyTextId}>
|
|
143
|
+
<Modal.Heading>Delete file?</Modal.Heading>
|
|
48
144
|
<Modal.Body>
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
145
|
+
<Text id={bodyTextId}>
|
|
146
|
+
{confirmingFileName
|
|
147
|
+
? `Are you sure you want to delete ${confirmingFileName}?`
|
|
148
|
+
: 'Are you sure you want to delete this file?'}
|
|
149
|
+
</Text>
|
|
52
150
|
</Modal.Body>
|
|
53
|
-
<Flex
|
|
54
|
-
<Modal.CloseButton
|
|
151
|
+
<Flex cs={actionStyles}>
|
|
152
|
+
<Modal.CloseButton ref={cancelButtonRef}>Cancel</Modal.CloseButton>
|
|
153
|
+
<Modal.CloseButton as={DeleteButton} onClick={handleConfirmDelete}>
|
|
55
154
|
Delete
|
|
56
155
|
</Modal.CloseButton>
|
|
57
|
-
<Modal.CloseButton>Cancel</Modal.CloseButton>
|
|
58
156
|
</Flex>
|
|
59
157
|
</Modal.Card>
|
|
60
158
|
</Modal.Overlay>
|
|
@@ -61,52 +61,56 @@ popup behaviors. For accessibility, these behaviors should be included most of t
|
|
|
61
61
|
### Initial Focus
|
|
62
62
|
|
|
63
63
|
If you want focus to move to a specific element when the popup is opened, set the `initialFocusRef`
|
|
64
|
-
of the model.
|
|
65
|
-
the
|
|
66
|
-
|
|
67
|
-
will be placed on the first focusable element when the popup is opened.
|
|
64
|
+
of the model. This is useful for popups that don't have a Close icon button near the top right of
|
|
65
|
+
the popup. In general, we recommend setting focus to the first interactive component inside the
|
|
66
|
+
popup that is the least destructive action.
|
|
68
67
|
|
|
69
68
|
<ExampleCodeBlock code={InitialFocus} />
|
|
70
69
|
|
|
70
|
+
> **Accessibility Note**: When initial focus lands on a control **below** the title (such as the OK
|
|
71
|
+
> button in the example above), assign a unique `id` to supplementary text and pass
|
|
72
|
+
> `aria-describedby` on `Popup.Card`. This augments the included `aria-labelledby` reference to
|
|
73
|
+
> `Popup.Heading` so screen readers can announce both the heading and any supplementary text
|
|
74
|
+
> automatically. When initial focus is on the heading itself, add `tabIndex={-1}` to `Popup.Heading`
|
|
75
|
+
> so the title can receive programmatic focus. Choose where focus goes based on your product and
|
|
76
|
+
> accessibility requirements.
|
|
77
|
+
|
|
71
78
|
### Focus Redirect
|
|
72
79
|
|
|
73
80
|
Focus management is important to accessibility of popup contents. The following example shows
|
|
74
81
|
`useFocusRedirect` being used to manage focus in and out of a Popup. This is very useful for
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
exists in between the buttons while it is opened. Screen readers will navigate the content as if the
|
|
78
|
-
content was not portalled to the bottom of the document body. Focus redirection tries to treat the
|
|
79
|
-
Popup as if it were inline to the document. Tabbing out of the Popup will close the Popup and move
|
|
80
|
-
focus to the next appropriate element.
|
|
81
|
-
|
|
82
|
-
> **Note**: Safari does not support `aria-owns`. This means that the contents of the Popup will
|
|
83
|
-
> appears out of order to Safari + VoiceOver users. We render popups at the bottom of the
|
|
84
|
-
> document.body to ensure proper rendering. You could use `portal=false` on the `Popper` component,
|
|
85
|
-
> but that would risk incorrect rendering in all browsers.
|
|
82
|
+
non-modal popups. Focus redirection tries to treat the Popup as if it were inline to the document.
|
|
83
|
+
Tabbing out of the Popup will close the Popup and move focus to an adjacent focusable element.
|
|
86
84
|
|
|
87
85
|
<ExampleCodeBlock code={FocusRedirect} />
|
|
88
86
|
|
|
87
|
+
> **Accessibility Note**: The `useFocusRedirect` hook **will not** have any effect on the reading
|
|
88
|
+
> order of a screen reader. Screen reader users may get confused or disoriented when popups are
|
|
89
|
+
> portalled to the bottom of the document body. In this example, we're testing the use of
|
|
90
|
+
> `aria-owns` on a sibling `<div>` element pointing to the `Popup.Card` component. This remaps the
|
|
91
|
+
> hierarchy of the accessibility tree (in supported browsers) to address the reading order problem.
|
|
92
|
+
> For more information, see
|
|
93
|
+
> [Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).
|
|
94
|
+
|
|
89
95
|
### Focus Trapping
|
|
90
96
|
|
|
91
97
|
Focus trapping is similar to the [Focus Redirect](#focus-redirect) example, but will trap focus
|
|
92
|
-
inside the popup instead of redirecting focus
|
|
93
|
-
|
|
94
|
-
continue.
|
|
95
|
-
|
|
96
|
-
> **Note**: Using focus trapping outside a Modal context can give users a different experience
|
|
97
|
-
> depending on how they interact with your application. Focus trapping will not prevent mouse users
|
|
98
|
-
> from breaking out of a focus trap, nor will it prevent screen reader users from using virtual
|
|
99
|
-
> cursors from breaking out. Modals should use additional techniques to truely "trap" focus into the
|
|
100
|
-
> Popup to provide a consistent experience for all users.
|
|
98
|
+
inside the popup instead of redirecting focus to adjacent focusable elements. This is necessary for
|
|
99
|
+
modal dialogs where users must focus on the contents of the dialog before proceeding.
|
|
101
100
|
|
|
102
101
|
<ExampleCodeBlock code={FocusTrap} />
|
|
103
102
|
|
|
103
|
+
> **Accessibility Note**: Focus trapping will not prevent mouse users from breaking out of a focus
|
|
104
|
+
> trap, nor will it prevent screen reader users from using virtual reading cursors from breaking
|
|
105
|
+
> out. Consider using [Modal](/components/popups/modal/) instead when you need to focus users'
|
|
106
|
+
> attention on a specific task inside of a popup..
|
|
107
|
+
|
|
104
108
|
### Multiple Popups
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
You can render more than one `Popup` in the same view by giving each its own model. This example
|
|
111
|
+
pairs `Popup` with `useDialogModel` and `useModalModel` so you can compare **focus redirection**
|
|
112
|
+
(Tab / Shift + Tab can move focus out of the first popup) and **focus trapping** (focus stays inside
|
|
113
|
+
the second popup until it closes). Opening one does not close the other.
|
|
110
114
|
|
|
111
115
|
<ExampleCodeBlock code={MultiplePopups} />
|
|
112
116
|
|
|
@@ -123,6 +127,11 @@ preserved.
|
|
|
123
127
|
|
|
124
128
|
<ExampleCodeBlock code={NestedPopups} />
|
|
125
129
|
|
|
130
|
+
> **Accessibility Note**: In this example, observe how users can traverse both opened popups using
|
|
131
|
+
> the keyboard. This is likely to be a confusing experience for users and may necessitate focus
|
|
132
|
+
> trapping inside each popup with careful consideration for setting initial focus and returning
|
|
133
|
+
> focus.
|
|
134
|
+
|
|
126
135
|
### Custom Target
|
|
127
136
|
|
|
128
137
|
It is common to have a custom target for your popup. Use the `as` prop to use your custom component.
|
|
@@ -138,6 +147,12 @@ requires a `label` prop.
|
|
|
138
147
|
|
|
139
148
|
<ExampleCodeBlock code={CustomTarget} />
|
|
140
149
|
|
|
150
|
+
> **Accessibility Note**: Custom targets must be keyboard focusable, otherwise users will not be
|
|
151
|
+
> able to access the popup. Bear in mind that click handlers only work with the keyboard when
|
|
152
|
+
> applied to HTML `<button>` elements and it is **strongly recommended** to base your custom target
|
|
153
|
+
> on a `<button>` element. Otherwise, you will be required to build in your own custom keyboard
|
|
154
|
+
> event handlers for invoking the popup.
|
|
155
|
+
|
|
141
156
|
### Full Screen API
|
|
142
157
|
|
|
143
158
|
By default, popups are created as children of the `document.body` element, but the `PopupStack`
|
|
@@ -175,6 +190,18 @@ The Popup component automatically handles right-to-left rendering.
|
|
|
175
190
|
|
|
176
191
|
<ExampleCodeBlock code={RTL} />
|
|
177
192
|
|
|
193
|
+
## Accessibility
|
|
194
|
+
|
|
195
|
+
Popup content is usually portaled to the bottom of the `document.body`, which can affect **reading
|
|
196
|
+
order for screen readers** and **keyboard focus order**. For more information about Popup
|
|
197
|
+
accessibility, check out our documentation at
|
|
198
|
+
[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).
|
|
199
|
+
|
|
200
|
+
- For non-modal dialogs with `aria-owns` built-in to improve reading order for screen readers (that
|
|
201
|
+
support it), check out the [**Dialog**](/components/popups/dialog/) component.
|
|
202
|
+
- For modal dialogs with built-in overlays and focus traps, check out the
|
|
203
|
+
[**Modal**](/components/popups/modal/) component.
|
|
204
|
+
|
|
178
205
|
## Component API
|
|
179
206
|
|
|
180
207
|
<>
|
|
@@ -6,8 +6,24 @@ import {
|
|
|
6
6
|
useCloseOnOutsideClick,
|
|
7
7
|
useInitialFocus,
|
|
8
8
|
useReturnFocus,
|
|
9
|
+
useFocusRedirect,
|
|
9
10
|
} from '@workday/canvas-kit-react/popup';
|
|
10
11
|
import {Box, Flex} from '@workday/canvas-kit-react/layout';
|
|
12
|
+
import {createStyles, px2rem} from '@workday/canvas-kit-styling';
|
|
13
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
14
|
+
|
|
15
|
+
const cardStyles = createStyles({
|
|
16
|
+
width: px2rem(400),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const bodyStyles = createStyles({
|
|
20
|
+
marginY: system.space.zero,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const flexStyles = createStyles({
|
|
24
|
+
gap: system.space.x4,
|
|
25
|
+
padding: system.space.x2,
|
|
26
|
+
});
|
|
11
27
|
|
|
12
28
|
export default () => {
|
|
13
29
|
const model = usePopupModel();
|
|
@@ -16,6 +32,7 @@ export default () => {
|
|
|
16
32
|
useCloseOnEscape(model);
|
|
17
33
|
useInitialFocus(model);
|
|
18
34
|
useReturnFocus(model);
|
|
35
|
+
useFocusRedirect(model);
|
|
19
36
|
|
|
20
37
|
const handleDelete = () => {
|
|
21
38
|
console.log('Delete Item');
|
|
@@ -25,15 +42,15 @@ export default () => {
|
|
|
25
42
|
<Popup model={model}>
|
|
26
43
|
<Popup.Target as={DeleteButton}>Delete Item</Popup.Target>
|
|
27
44
|
<Popup.Popper placement="top">
|
|
28
|
-
<Popup.Card
|
|
45
|
+
<Popup.Card cs={cardStyles}>
|
|
29
46
|
<Popup.CloseIcon aria-label="Close" />
|
|
30
47
|
<Popup.Heading>Delete Item</Popup.Heading>
|
|
31
48
|
<Popup.Body>
|
|
32
|
-
<Box as="p"
|
|
49
|
+
<Box as="p" cs={bodyStyles}>
|
|
33
50
|
Are you sure you'd like to delete the item titled 'My Item'?
|
|
34
51
|
</Box>
|
|
35
52
|
</Popup.Body>
|
|
36
|
-
<Flex
|
|
53
|
+
<Flex cs={flexStyles}>
|
|
37
54
|
<Popup.CloseButton as={DeleteButton} onClick={handleDelete}>
|
|
38
55
|
Delete
|
|
39
56
|
</Popup.CloseButton>
|
|
@@ -11,6 +11,22 @@ import {
|
|
|
11
11
|
usePopupModel,
|
|
12
12
|
} from '@workday/canvas-kit-react/popup';
|
|
13
13
|
import {Box, Flex} from '@workday/canvas-kit-react/layout';
|
|
14
|
+
import {createStyles, px2rem} from '@workday/canvas-kit-styling';
|
|
15
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
16
|
+
import {useUniqueId} from '@workday/canvas-kit-react/common';
|
|
17
|
+
|
|
18
|
+
const cardStyles = createStyles({
|
|
19
|
+
width: px2rem(400),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const bodyStyles = createStyles({
|
|
23
|
+
marginY: system.space.zero,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const flexStyles = createStyles({
|
|
27
|
+
gap: system.space.x4,
|
|
28
|
+
padding: system.space.x2,
|
|
29
|
+
});
|
|
14
30
|
|
|
15
31
|
export default () => {
|
|
16
32
|
const model = usePopupModel();
|
|
@@ -25,34 +41,33 @@ export default () => {
|
|
|
25
41
|
console.log('Delete Item');
|
|
26
42
|
};
|
|
27
43
|
|
|
28
|
-
const popupId =
|
|
44
|
+
const popupId = useUniqueId();
|
|
29
45
|
const visible = model.state.visibility !== 'hidden';
|
|
30
46
|
React.useLayoutEffect(() => {
|
|
31
47
|
if (visible && model.state.stackRef.current) {
|
|
32
48
|
model.state.stackRef.current.setAttribute('id', popupId);
|
|
33
49
|
}
|
|
34
|
-
}, [model.state.stackRef, visible]);
|
|
50
|
+
}, [model.state.stackRef, visible, popupId]);
|
|
35
51
|
|
|
36
52
|
return (
|
|
37
53
|
<Popup model={model}>
|
|
38
|
-
<Flex
|
|
54
|
+
<Flex cs={flexStyles}>
|
|
39
55
|
<Popup.Target as={DeleteButton}>Delete Item</Popup.Target>
|
|
40
|
-
<div aria-owns={popupId} style={{position: 'absolute'}}
|
|
56
|
+
<div aria-owns={popupId} style={{position: 'absolute'}}></div>
|
|
41
57
|
<Popup.Popper>
|
|
42
|
-
<Popup.Card
|
|
58
|
+
<Popup.Card cs={cardStyles}>
|
|
43
59
|
<Popup.CloseIcon aria-label="Close" />
|
|
44
60
|
<Popup.Heading>Delete Item</Popup.Heading>
|
|
45
61
|
<Popup.Body>
|
|
46
|
-
<Box as="p"
|
|
62
|
+
<Box as="p" cs={bodyStyles}>
|
|
47
63
|
Are you sure you'd like to delete the item titled 'My Item'?
|
|
48
64
|
</Box>
|
|
49
65
|
</Popup.Body>
|
|
50
|
-
<Flex
|
|
66
|
+
<Flex cs={flexStyles}>
|
|
51
67
|
<Popup.CloseButton as={DeleteButton} onClick={handleDelete}>
|
|
52
68
|
Delete
|
|
53
69
|
</Popup.CloseButton>
|
|
54
|
-
|
|
55
|
-
<Popup.CloseButton disabled>Cancel</Popup.CloseButton>
|
|
70
|
+
<Popup.CloseButton>Cancel</Popup.CloseButton>
|
|
56
71
|
</Flex>
|
|
57
72
|
</Popup.Card>
|
|
58
73
|
</Popup.Popper>
|
|
@@ -7,10 +7,37 @@ import {
|
|
|
7
7
|
usePopupModel,
|
|
8
8
|
useInitialFocus,
|
|
9
9
|
useReturnFocus,
|
|
10
|
+
useFocusRedirect,
|
|
10
11
|
} from '@workday/canvas-kit-react/popup';
|
|
11
|
-
import {
|
|
12
|
+
import {Flex} from '@workday/canvas-kit-react/layout';
|
|
13
|
+
import {FormField} from '@workday/canvas-kit-react/form-field';
|
|
14
|
+
import {PrimaryButton} from '@workday/canvas-kit-react/button';
|
|
15
|
+
import {TextInput} from '@workday/canvas-kit-react/text-input';
|
|
16
|
+
import {Text} from '@workday/canvas-kit-react/text';
|
|
17
|
+
import {createStyles, px2rem} from '@workday/canvas-kit-styling';
|
|
18
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
19
|
+
import {useUniqueId} from '@workday/canvas-kit-react/common';
|
|
12
20
|
|
|
13
|
-
|
|
21
|
+
const cardStyles = createStyles({
|
|
22
|
+
width: px2rem(400),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const bodyStyles = createStyles({
|
|
26
|
+
marginY: system.space.zero,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const flexStyles = createStyles({
|
|
30
|
+
gap: system.space.x4,
|
|
31
|
+
padding: system.space.x2,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const columnStyles = createStyles({
|
|
35
|
+
gap: system.space.x4,
|
|
36
|
+
alignItems: 'flex-start',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const InitialFocusOnButton = () => {
|
|
40
|
+
const messageId = useUniqueId();
|
|
14
41
|
const initialFocusRef = React.useRef(null);
|
|
15
42
|
const model = usePopupModel({
|
|
16
43
|
initialFocusRef,
|
|
@@ -20,21 +47,21 @@ export default () => {
|
|
|
20
47
|
useCloseOnEscape(model);
|
|
21
48
|
useInitialFocus(model);
|
|
22
49
|
useReturnFocus(model);
|
|
50
|
+
useFocusRedirect(model);
|
|
23
51
|
|
|
24
52
|
return (
|
|
25
53
|
<Popup model={model}>
|
|
26
|
-
<Popup.Target>
|
|
54
|
+
<Popup.Target>Initial focus: OK button</Popup.Target>
|
|
27
55
|
<Popup.Popper placement={'bottom'}>
|
|
28
|
-
<Popup.Card
|
|
29
|
-
<Popup.CloseIcon aria-label="Close" />
|
|
56
|
+
<Popup.Card cs={cardStyles} aria-describedby={messageId}>
|
|
30
57
|
<Popup.Heading>Confirmation</Popup.Heading>
|
|
31
58
|
<Popup.Body>
|
|
32
|
-
<
|
|
59
|
+
<Text cs={bodyStyles} id={messageId}>
|
|
33
60
|
Your message has been sent!
|
|
34
|
-
</
|
|
61
|
+
</Text>
|
|
35
62
|
</Popup.Body>
|
|
36
|
-
<Flex
|
|
37
|
-
<Popup.CloseButton ref={initialFocusRef}
|
|
63
|
+
<Flex cs={flexStyles}>
|
|
64
|
+
<Popup.CloseButton as={PrimaryButton} ref={initialFocusRef}>
|
|
38
65
|
OK
|
|
39
66
|
</Popup.CloseButton>
|
|
40
67
|
</Flex>
|
|
@@ -43,3 +70,80 @@ export default () => {
|
|
|
43
70
|
</Popup>
|
|
44
71
|
);
|
|
45
72
|
};
|
|
73
|
+
|
|
74
|
+
const InitialFocusOnTextInput = () => {
|
|
75
|
+
const descriptionId = useUniqueId();
|
|
76
|
+
const initialFocusRef = React.useRef<HTMLInputElement>(null);
|
|
77
|
+
const model = usePopupModel({
|
|
78
|
+
initialFocusRef,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
useCloseOnOutsideClick(model);
|
|
82
|
+
useCloseOnEscape(model);
|
|
83
|
+
useInitialFocus(model);
|
|
84
|
+
useReturnFocus(model);
|
|
85
|
+
useFocusRedirect(model);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<Popup model={model}>
|
|
89
|
+
<Popup.Target>Initial focus: text input</Popup.Target>
|
|
90
|
+
<Popup.Popper placement={'bottom'}>
|
|
91
|
+
<Popup.Card cs={cardStyles} aria-describedby={descriptionId}>
|
|
92
|
+
<Popup.Heading>Quick reply</Popup.Heading>
|
|
93
|
+
<Popup.Body>
|
|
94
|
+
<FormField>
|
|
95
|
+
<FormField.Label>Message</FormField.Label>
|
|
96
|
+
<FormField.Input as={TextInput} ref={initialFocusRef} />
|
|
97
|
+
</FormField>
|
|
98
|
+
</Popup.Body>
|
|
99
|
+
<Flex cs={flexStyles}>
|
|
100
|
+
<Popup.CloseButton as={PrimaryButton}>Send</Popup.CloseButton>
|
|
101
|
+
<Popup.CloseButton>Cancel</Popup.CloseButton>
|
|
102
|
+
</Flex>
|
|
103
|
+
</Popup.Card>
|
|
104
|
+
</Popup.Popper>
|
|
105
|
+
</Popup>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const InitialFocusOnHeading = () => {
|
|
110
|
+
const headingFocusRef = React.useRef<HTMLHeadingElement>(null);
|
|
111
|
+
const model = usePopupModel({
|
|
112
|
+
initialFocusRef: headingFocusRef,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
useCloseOnOutsideClick(model);
|
|
116
|
+
useCloseOnEscape(model);
|
|
117
|
+
useInitialFocus(model);
|
|
118
|
+
useReturnFocus(model);
|
|
119
|
+
useFocusRedirect(model);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<Popup model={model}>
|
|
123
|
+
<Popup.Target>Initial focus: heading</Popup.Target>
|
|
124
|
+
<Popup.Popper placement={'bottom'}>
|
|
125
|
+
<Popup.Card cs={cardStyles}>
|
|
126
|
+
<Popup.Heading ref={headingFocusRef} tabIndex={-1}>
|
|
127
|
+
Important notice
|
|
128
|
+
</Popup.Heading>
|
|
129
|
+
<Popup.Body>
|
|
130
|
+
<Text cs={bodyStyles}>Review the summary below before continuing.</Text>
|
|
131
|
+
</Popup.Body>
|
|
132
|
+
<Flex cs={flexStyles}>
|
|
133
|
+
<Popup.CloseButton as={PrimaryButton}>Continue</Popup.CloseButton>
|
|
134
|
+
</Flex>
|
|
135
|
+
</Popup.Card>
|
|
136
|
+
</Popup.Popper>
|
|
137
|
+
</Popup>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export default () => {
|
|
142
|
+
return (
|
|
143
|
+
<Flex cs={columnStyles}>
|
|
144
|
+
<InitialFocusOnButton />
|
|
145
|
+
<InitialFocusOnTextInput />
|
|
146
|
+
<InitialFocusOnHeading />
|
|
147
|
+
</Flex>
|
|
148
|
+
);
|
|
149
|
+
};
|