@workday/canvas-kit-docs 12.1.0 → 12.1.2

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 {styled, createComponent, dubLogoBlue} from '@workday/canvas-kit-react/common';
3
- import {colors, depth, space, type} from '@workday/canvas-kit-react/tokens';
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 {TertiaryButton, Hyperlink} from '@workday/canvas-kit-react/button';
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 {SearchForm} from '@workday/canvas-kit-labs-react/search-form';
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
- export default () => (
20
- <GlobalHeader>
21
- <GlobalHeader.Item>
22
- <TertiaryButton aria-label="menu" icon={justifyIcon} />
23
- <Hyperlink>
24
- <WorkdayLogo dangerouslySetInnerHTML={{__html: dubLogoBlue}} />
25
- </Hyperlink>
26
- </GlobalHeader.Item>
27
- <GlobalHeader.Item margin="auto" width="100%" maxWidth={`calc(${space.xxxl} * 6)`}>
28
- <SearchForm onSubmit={() => 1} />
29
- </GlobalHeader.Item>
30
- <GlobalHeader.Item>
31
- <TertiaryButton aria-label="messages" icon={assistantIcon} />
32
- <TertiaryButton aria-label="notifications" icon={notificationsIcon} />
33
- <TertiaryButton aria-label="inbox" icon={inboxIcon} />
34
- <Avatar size="medium" variant="light" />
35
- </GlobalHeader.Item>
36
- </GlobalHeader>
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.xs} ref={ref} {...props} />
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, Element) => <HeaderWrapper ref={ref} as={Element} {...props} />,
173
+ Component: (props, ref) => (
174
+ <header className={styleOverrides.headerWrapper} ref={ref} {...props} />
175
+ ),
49
176
  subComponents: {Item: GlobalHeaderItem},
50
177
  });
51
178
 
52
- const HeaderWrapper = styled('header')({
53
- display: 'flex',
54
- alignItems: 'center',
55
- justifyContent: 'space-between',
56
- boxSizing: 'border-box',
57
- ...type.levels.subtext.large,
58
- WebkitFontSmoothing: 'antialiased',
59
- MozOsxFontSmoothing: 'grayscale',
60
- backgroundColor: colors.frenchVanilla100,
61
- ...depth[1],
62
- padding: space.xxs,
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 WorkdayLogo = styled('span')({lineHeight: 0});
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.0",
3
+ "version": "12.1.2",
4
4
  "description": "Documentation components of Canvas Kit components",
5
5
  "author": "Workday, Inc. (https://www.workday.com)",
6
6
  "license": "Apache-2.0",
@@ -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.0",
48
- "@workday/canvas-kit-preview-react": "^12.1.0",
49
- "@workday/canvas-kit-react": "^12.1.0",
50
- "@workday/canvas-kit-styling": "^12.1.0",
47
+ "@workday/canvas-kit-labs-react": "^12.1.2",
48
+ "@workday/canvas-kit-preview-react": "^12.1.2",
49
+ "@workday/canvas-kit-react": "^12.1.2",
50
+ "@workday/canvas-kit-styling": "^12.1.2",
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": "2ceb174f303a8e0c462440cf1f799821e24556f5"
63
+ "gitHead": "802784764d4cd8f030cf5908d0c050c9e8950b90"
64
64
  }