@workday/canvas-kit-docs 12.1.1 → 12.1.3
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.
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import Basic from './examples/GlobalHeader';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# GlobalHeader Example
|
|
5
|
+
|
|
6
|
+
Developers building internal Workday applications will likely not need to create this component.
|
|
7
|
+
However, if you're building components to be used outside of Workday, this is a helpful reference
|
|
8
|
+
for building a global navigation header that looks like our internal `GlobalHeader`.
|
|
9
|
+
|
|
10
|
+
<ExampleCodeBlock code={Basic} />
|
|
11
|
+
|
|
12
|
+
## Tooltip usage
|
|
13
|
+
|
|
14
|
+
- The `default` variant Tooltip is used on all of the icon buttons, which will automatically set the
|
|
15
|
+
Tooltip's text to the accessible name. (`aria-label`)
|
|
16
|
+
- The `describe` variant Tooltip is used instead on the "MENU" button because this is a text button.
|
|
17
|
+
The Tooltip's text "Global Navigation" will instead be assigned to the accessible description to
|
|
18
|
+
ensure that the visible button text "MENU" is not overriden.
|
|
19
|
+
|
|
20
|
+
## Count badge usage
|
|
21
|
+
|
|
22
|
+
When `<CountBadge>` is used as a sibling component for button, the `aria-describedby` property is
|
|
23
|
+
set on the button referencing the `id` value of the `<CountBadge>`. This practice helps support
|
|
24
|
+
users depending on screen readers to describe both the name of the button and the value of the
|
|
25
|
+
`<CountBadge>`.
|
|
26
|
+
|
|
27
|
+
When a web app dynamically updates count badges in real-time, consider the following accessibility
|
|
28
|
+
enhancements to support live, real-time announcements for screen readers:
|
|
29
|
+
|
|
30
|
+
- The `<CountBadge>` component is rendered as a child of the `<AriaLiveRegion>` container.
|
|
31
|
+
- The `<AriaLiveRegion>` container is assigned a name by using `aria-labelledby` to reference the
|
|
32
|
+
name of the icon button `"Notifications"`.
|
|
33
|
+
- The `<AccessibleHide>` component is used following the `<CountBadge>` to render a hidden word
|
|
34
|
+
"new" that only screen reader users can access.
|
|
35
|
+
- When the `<CountBadge>` is updated, then screen readers can automatically describe (in real-time)
|
|
36
|
+
the name of the live region, "Notifications" and the text updated inside of it, "1 new".
|
|
@@ -12,6 +12,32 @@ Developers building internal Workday applications will likely not need to create
|
|
|
12
12
|
However, if you're building components to be used outside of Workday, this is a helpful reference
|
|
13
13
|
for building a global navigation header that looks like our internal `GlobalHeader`.
|
|
14
14
|
|
|
15
|
+
## Tooltip usage
|
|
16
|
+
|
|
17
|
+
- The `default` variant Tooltip is used on all of the icon buttons, which will automatically set the
|
|
18
|
+
Tooltip's text to the accessible name. (`aria-label`)
|
|
19
|
+
- The `describe` variant Tooltip is used instead on the "MENU" button because this is a text button.
|
|
20
|
+
The Tooltip's text "Global Navigation" will instead be assigned to the accessible description to
|
|
21
|
+
ensure that the visible button text "MENU" is not overriden.
|
|
22
|
+
|
|
23
|
+
## Count badge usage
|
|
24
|
+
|
|
25
|
+
When `<CountBadge>` is used as a sibling component for button, the `aria-describedby` property is
|
|
26
|
+
set on the button referencing the `id` value of the `<CountBadge>`. This practice helps support
|
|
27
|
+
users depending on screen readers to describe both the name of the button and the value of the
|
|
28
|
+
`<CountBadge>`.
|
|
29
|
+
|
|
30
|
+
When a web app dynamically updates count badges in real-time, consider the following accessibility
|
|
31
|
+
enhancements to support live, real-time announcements for screen readers:
|
|
32
|
+
|
|
33
|
+
- The `<CountBadge>` component is rendered as a child of the `<AriaLiveRegion>` container.
|
|
34
|
+
- The `<AriaLiveRegion>` container is assigned a name by using `aria-labelledby` to reference the
|
|
35
|
+
name of the icon button `"Notifications"`.
|
|
36
|
+
- The `<AccessibleHide>` component is used following the `<CountBadge>` to render a hidden word
|
|
37
|
+
"new" that only screen reader users can access.
|
|
38
|
+
- When the `<CountBadge>` is updated, then screen readers can automatically describe (in real-time)
|
|
39
|
+
the name of the live region, "Notifications" and the text updated inside of it, "1 new".
|
|
40
|
+
|
|
15
41
|
<ExampleCodeBlock code={GlobalHeaderBasic} />
|
|
16
42
|
|
|
17
43
|
## Page Header
|
|
@@ -1,65 +1,295 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
AccessibleHide,
|
|
4
|
+
AriaLiveRegion,
|
|
5
|
+
composeHooks,
|
|
6
|
+
createComponent,
|
|
7
|
+
createElemPropsHook,
|
|
8
|
+
createSubcomponent,
|
|
9
|
+
ExtractProps,
|
|
10
|
+
useUniqueId,
|
|
11
|
+
} from '@workday/canvas-kit-react/common';
|
|
12
|
+
import {base, system} from '@workday/canvas-tokens-web';
|
|
13
|
+
import {calc, createStyles, px2rem} from '@workday/canvas-kit-styling';
|
|
5
14
|
import {
|
|
6
15
|
notificationsIcon,
|
|
7
16
|
inboxIcon,
|
|
8
17
|
justifyIcon,
|
|
9
18
|
assistantIcon,
|
|
19
|
+
searchIcon,
|
|
10
20
|
} from '@workday/canvas-system-icons-web';
|
|
11
21
|
|
|
12
|
-
import {
|
|
22
|
+
import {SecondaryButton, TertiaryButton} from '@workday/canvas-kit-react/button';
|
|
13
23
|
import {Avatar} from '@workday/canvas-kit-react/avatar';
|
|
14
24
|
import {Flex, FlexProps} from '@workday/canvas-kit-react/layout';
|
|
15
|
-
import {
|
|
25
|
+
import {LoadReturn} from '@workday/canvas-kit-react/collection';
|
|
26
|
+
import {Tooltip} from '@workday/canvas-kit-react/tooltip';
|
|
27
|
+
import {
|
|
28
|
+
Combobox,
|
|
29
|
+
useComboboxModel,
|
|
30
|
+
useComboboxInput,
|
|
31
|
+
useComboboxLoader,
|
|
32
|
+
} from '@workday/canvas-kit-react/combobox';
|
|
33
|
+
import {InputGroup, TextInput} from '@workday/canvas-kit-react/text-input';
|
|
34
|
+
import {StyledMenuItem} from '@workday/canvas-kit-react/menu';
|
|
35
|
+
import {SystemIcon} from '@workday/canvas-kit-react/icon';
|
|
36
|
+
import {CountBadge} from '@workday/canvas-kit-react/badge';
|
|
16
37
|
|
|
17
38
|
interface HeaderItemProps extends FlexProps {}
|
|
39
|
+
interface LiveCountBadgeProps extends FlexProps {
|
|
40
|
+
cnt: number;
|
|
41
|
+
}
|
|
18
42
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
const tasks = ['Request Time Off', 'Create Expense Report', 'Change Benefits'];
|
|
44
|
+
|
|
45
|
+
const styleOverrides = {
|
|
46
|
+
headerWrapper: createStyles({
|
|
47
|
+
display: 'flex',
|
|
48
|
+
alignItems: 'center',
|
|
49
|
+
justifyContent: 'space-between',
|
|
50
|
+
boxSizing: 'border-box',
|
|
51
|
+
...system.type.subtext.large,
|
|
52
|
+
WebkitFontSmoothing: 'antialiased',
|
|
53
|
+
MozOsxFontSmoothing: 'grayscale',
|
|
54
|
+
backgroundColor: system.color.bg.default,
|
|
55
|
+
padding: system.space.x1,
|
|
56
|
+
}),
|
|
57
|
+
inputGroupInner: createStyles({
|
|
58
|
+
marginLeft: '1rem',
|
|
59
|
+
width: px2rem(20),
|
|
60
|
+
transition: 'opacity 100ms ease',
|
|
61
|
+
}),
|
|
62
|
+
comboboxContainer: createStyles({
|
|
63
|
+
margin: 'auto',
|
|
64
|
+
width: '100%',
|
|
65
|
+
maxWidth: calc.multiply(system.space.x20, 6),
|
|
66
|
+
}),
|
|
67
|
+
comboboxInput: createStyles({
|
|
68
|
+
borderRadius: px2rem(1000),
|
|
69
|
+
width: '20rem',
|
|
70
|
+
}),
|
|
71
|
+
comboboxMenuList: createStyles({
|
|
72
|
+
maxHeight: px2rem(200),
|
|
73
|
+
}),
|
|
74
|
+
menuButtonStyles: createStyles({
|
|
75
|
+
textDecoration: 'none',
|
|
76
|
+
color: base.blackPepper500,
|
|
77
|
+
}),
|
|
78
|
+
notificationContainerStyles: createStyles({
|
|
79
|
+
boxSizing: 'border-box',
|
|
80
|
+
position: 'relative',
|
|
81
|
+
}),
|
|
82
|
+
countBadgeStyles: createStyles({
|
|
83
|
+
boxSizing: 'border-box',
|
|
84
|
+
position: 'absolute',
|
|
85
|
+
top: calc.negate(system.space.x1),
|
|
86
|
+
insetInlineEnd: calc.negate(system.space.x1),
|
|
87
|
+
}),
|
|
88
|
+
actionButtonStyles: createStyles({
|
|
89
|
+
gap: system.space.x4,
|
|
90
|
+
margin: system.space.x4,
|
|
91
|
+
}),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const useAutocompleteInput = composeHooks(
|
|
95
|
+
createElemPropsHook(useComboboxModel)(model => {
|
|
96
|
+
return {
|
|
97
|
+
onKeyPress(event: React.KeyboardEvent) {
|
|
98
|
+
model.events.show(event);
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}),
|
|
102
|
+
useComboboxInput
|
|
37
103
|
);
|
|
38
104
|
|
|
105
|
+
const AutoCompleteInput = createSubcomponent(TextInput)({
|
|
106
|
+
modelHook: useComboboxModel,
|
|
107
|
+
elemPropsHook: useAutocompleteInput,
|
|
108
|
+
})<ExtractProps<typeof Combobox.Input, never>>((elemProps, Element) => {
|
|
109
|
+
return <Combobox.Input as={Element} {...elemProps} />;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export default () => {
|
|
113
|
+
const [notifications, setNotifications] = React.useState(0);
|
|
114
|
+
|
|
115
|
+
function handleAdd() {
|
|
116
|
+
setNotifications(prev => prev + 1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function handleClear() {
|
|
120
|
+
setNotifications(0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<>
|
|
125
|
+
<GlobalHeader>
|
|
126
|
+
<GlobalHeader.Item>
|
|
127
|
+
<Tooltip title="Global Navigation" type="describe">
|
|
128
|
+
<TertiaryButton icon={justifyIcon} cs={styleOverrides.menuButtonStyles}>
|
|
129
|
+
MENU
|
|
130
|
+
</TertiaryButton>
|
|
131
|
+
</Tooltip>
|
|
132
|
+
<Tooltip title="Workday Home">
|
|
133
|
+
<TertiaryButton>
|
|
134
|
+
<img src="https://design.workday.com/images/ck-dub-logo-blue.svg" alt="" />
|
|
135
|
+
</TertiaryButton>
|
|
136
|
+
</Tooltip>
|
|
137
|
+
</GlobalHeader.Item>
|
|
138
|
+
<GlobalHeader.Item cs={styleOverrides.comboboxContainer}>
|
|
139
|
+
<Autocomplete aria-label="Search Workday" />
|
|
140
|
+
</GlobalHeader.Item>
|
|
141
|
+
<GlobalHeader.Item>
|
|
142
|
+
<Tooltip title="Assistant">
|
|
143
|
+
<TertiaryButton icon={assistantIcon} />
|
|
144
|
+
</Tooltip>
|
|
145
|
+
|
|
146
|
+
<NotificationLiveBadge cnt={notifications} />
|
|
147
|
+
|
|
148
|
+
<Tooltip title="My Tasks">
|
|
149
|
+
<TertiaryButton icon={inboxIcon} />
|
|
150
|
+
</Tooltip>
|
|
151
|
+
<Tooltip title="Profile">
|
|
152
|
+
<Avatar />
|
|
153
|
+
</Tooltip>
|
|
154
|
+
</GlobalHeader.Item>
|
|
155
|
+
</GlobalHeader>
|
|
156
|
+
<Flex cs={styleOverrides.actionButtonStyles}>
|
|
157
|
+
<SecondaryButton onClick={handleAdd}>Add notification</SecondaryButton>
|
|
158
|
+
<TertiaryButton onClick={handleClear}>Clear</TertiaryButton>
|
|
159
|
+
</Flex>
|
|
160
|
+
</>
|
|
161
|
+
);
|
|
162
|
+
};
|
|
163
|
+
|
|
39
164
|
const GlobalHeaderItem = createComponent('div')({
|
|
40
165
|
displayName: 'GlobalHeader.Item',
|
|
41
166
|
Component: ({gap = 's', ...props}: HeaderItemProps, ref) => (
|
|
42
|
-
<Flex gap={gap} alignItems="center" marginX={space.
|
|
167
|
+
<Flex gap={gap} alignItems="center" marginX={system.space.x3} ref={ref} {...props} />
|
|
43
168
|
),
|
|
44
169
|
});
|
|
45
170
|
|
|
46
171
|
const GlobalHeader = createComponent('header')({
|
|
47
172
|
displayName: 'GlobalHeader',
|
|
48
|
-
Component: (props, ref
|
|
173
|
+
Component: (props, ref) => (
|
|
174
|
+
<header className={styleOverrides.headerWrapper} ref={ref} {...props} />
|
|
175
|
+
),
|
|
49
176
|
subComponents: {Item: GlobalHeaderItem},
|
|
50
177
|
});
|
|
51
178
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
179
|
+
const Autocomplete = createComponent('div')({
|
|
180
|
+
displayName: 'Autocomplete',
|
|
181
|
+
Component: props => {
|
|
182
|
+
const [searchText, setSearchText] = React.useState('');
|
|
183
|
+
|
|
184
|
+
function handleChange(e) {
|
|
185
|
+
setSearchText(e.target.value);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const {model, loader} = useComboboxLoader(
|
|
189
|
+
{
|
|
190
|
+
// You can start with any number that makes sense.
|
|
191
|
+
total: 0,
|
|
192
|
+
|
|
193
|
+
// Pick whatever number makes sense for your API
|
|
194
|
+
pageSize: 20,
|
|
195
|
+
|
|
196
|
+
// A load function that will be called by the loader. You must return a promise that returns
|
|
197
|
+
// an object like `{items: [], total: 0}`. The `items` will be merged into the loader's cache
|
|
198
|
+
async load({pageNumber, pageSize, filter}) {
|
|
199
|
+
return new Promise<LoadReturn<string>>(resolve => {
|
|
200
|
+
// simulate a server response by resolving after a period of time
|
|
201
|
+
setTimeout(() => {
|
|
202
|
+
// simulate paging and filtering based on pre-computed items
|
|
203
|
+
const start = (pageNumber - 1) * pageSize;
|
|
204
|
+
const end = start + pageSize;
|
|
205
|
+
const filteredTasks = tasks.filter(i => {
|
|
206
|
+
if (searchText.trim() === '' || typeof searchText !== 'string') {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
return i.toLowerCase().includes(searchText.trim().toLowerCase());
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const total = filteredTasks.length;
|
|
213
|
+
const items = filteredTasks.slice(start, end);
|
|
214
|
+
|
|
215
|
+
resolve({
|
|
216
|
+
items,
|
|
217
|
+
total,
|
|
218
|
+
});
|
|
219
|
+
}, 300);
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
onShow() {
|
|
223
|
+
// The `shouldLoad` cancels while the combobox menu is hidden, so let's load when it is
|
|
224
|
+
// visible
|
|
225
|
+
loader.load();
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
useComboboxModel
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<Combobox model={model}>
|
|
233
|
+
<InputGroup>
|
|
234
|
+
<InputGroup.InnerStart cs={styleOverrides.inputGroupInner}>
|
|
235
|
+
<SystemIcon icon={searchIcon} />
|
|
236
|
+
</InputGroup.InnerStart>
|
|
237
|
+
<InputGroup.Input
|
|
238
|
+
as={AutoCompleteInput}
|
|
239
|
+
cs={styleOverrides.comboboxInput}
|
|
240
|
+
onChange={handleChange}
|
|
241
|
+
value={searchText}
|
|
242
|
+
{...props}
|
|
243
|
+
/>
|
|
244
|
+
</InputGroup>
|
|
245
|
+
<Combobox.Menu.Popper>
|
|
246
|
+
<Combobox.Menu.Card>
|
|
247
|
+
{model.state.items.length === 0 ? (
|
|
248
|
+
<StyledMenuItem as="span">No Results Found</StyledMenuItem>
|
|
249
|
+
) : (
|
|
250
|
+
model.state.items.length > 0 && (
|
|
251
|
+
<Combobox.Menu.List maxHeight={px2rem(200)}>
|
|
252
|
+
{item => <Combobox.Menu.Item>{item}</Combobox.Menu.Item>}
|
|
253
|
+
</Combobox.Menu.List>
|
|
254
|
+
)
|
|
255
|
+
)}
|
|
256
|
+
</Combobox.Menu.Card>
|
|
257
|
+
</Combobox.Menu.Popper>
|
|
258
|
+
</Combobox>
|
|
259
|
+
);
|
|
260
|
+
},
|
|
63
261
|
});
|
|
64
262
|
|
|
65
|
-
const
|
|
263
|
+
const NotificationLiveBadge = createComponent('span')({
|
|
264
|
+
displayName: 'NotificationLiveBadge',
|
|
265
|
+
Component: ({cnt = 0, ...props}: LiveCountBadgeProps) => {
|
|
266
|
+
const btnId = useUniqueId();
|
|
267
|
+
const badgeId = useUniqueId();
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<Flex cs={styleOverrides.notificationContainerStyles}>
|
|
271
|
+
<Tooltip title="Notifications">
|
|
272
|
+
<TertiaryButton
|
|
273
|
+
id={btnId}
|
|
274
|
+
icon={notificationsIcon}
|
|
275
|
+
aria-describedby={cnt > 0 ? badgeId : undefined}
|
|
276
|
+
{...props}
|
|
277
|
+
/>
|
|
278
|
+
</Tooltip>
|
|
279
|
+
<AriaLiveRegion aria-labelledby={btnId}>
|
|
280
|
+
{cnt > 0 && (
|
|
281
|
+
<>
|
|
282
|
+
<CountBadge
|
|
283
|
+
id={badgeId}
|
|
284
|
+
count={cnt}
|
|
285
|
+
limit={100}
|
|
286
|
+
cs={styleOverrides.countBadgeStyles}
|
|
287
|
+
/>
|
|
288
|
+
<AccessibleHide>New</AccessibleHide>
|
|
289
|
+
</>
|
|
290
|
+
)}
|
|
291
|
+
</AriaLiveRegion>
|
|
292
|
+
</Flex>
|
|
293
|
+
);
|
|
294
|
+
},
|
|
295
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workday/canvas-kit-docs",
|
|
3
|
-
"version": "12.1.
|
|
3
|
+
"version": "12.1.3",
|
|
4
4
|
"description": "Documentation components of Canvas Kit components",
|
|
5
5
|
"author": "Workday, Inc. (https://www.workday.com)",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"build:docs": "node ./utils/build-docs.js",
|
|
28
28
|
"build:rebuild": "npm-run-all clean build",
|
|
29
29
|
"build:specs": "node ./utils/build-specifications.js",
|
|
30
|
-
"build": "npm-run-all
|
|
30
|
+
"build": "npm-run-all build:es6 build:mdx build:specs build:docs",
|
|
31
31
|
"depcheck": "node ../../utils/check-dependencies-exist.js",
|
|
32
32
|
"typecheck:src": "tsc -p . --noEmit --incremental false"
|
|
33
33
|
},
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@emotion/styled": "^11.6.0",
|
|
46
46
|
"@storybook/csf": "0.0.1",
|
|
47
|
-
"@workday/canvas-kit-labs-react": "^12.1.
|
|
48
|
-
"@workday/canvas-kit-preview-react": "^12.1.
|
|
49
|
-
"@workday/canvas-kit-react": "^12.1.
|
|
50
|
-
"@workday/canvas-kit-styling": "^12.1.
|
|
47
|
+
"@workday/canvas-kit-labs-react": "^12.1.3",
|
|
48
|
+
"@workday/canvas-kit-preview-react": "^12.1.3",
|
|
49
|
+
"@workday/canvas-kit-react": "^12.1.3",
|
|
50
|
+
"@workday/canvas-kit-styling": "^12.1.3",
|
|
51
51
|
"@workday/canvas-system-icons-web": "^3.0.0",
|
|
52
52
|
"@workday/canvas-tokens-web": "^2.0.1",
|
|
53
53
|
"markdown-to-jsx": "^7.2.0",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"mkdirp": "^1.0.3",
|
|
61
61
|
"typescript": "5.0"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "b0a90152015ec3a58183521456a9c5550070b9a0"
|
|
64
64
|
}
|