@workday/canvas-kit-docs 5.3.0-next.18 → 5.3.0-next.32

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.
@@ -5185,6 +5185,22 @@ module.exports = {specifications: [
5185
5185
  "type": "it",
5186
5186
  "name": "should not show the \"More\" button"
5187
5187
  },
5188
+ {
5189
+ "type": "describe",
5190
+ "name": "when the \"First Tab\" is focused",
5191
+ "children": [
5192
+ {
5193
+ "type": "describe",
5194
+ "name": "when the Tab key is pressed",
5195
+ "children": [
5196
+ {
5197
+ "type": "it",
5198
+ "name": "should focus on the tab panel"
5199
+ }
5200
+ ]
5201
+ }
5202
+ ]
5203
+ },
5188
5204
  {
5189
5205
  "type": "describe",
5190
5206
  "name": "when tab list container is only 500px wide",
@@ -5197,6 +5213,22 @@ module.exports = {specifications: [
5197
5213
  "type": "it",
5198
5214
  "name": "should show the \"More\" button"
5199
5215
  },
5216
+ {
5217
+ "type": "describe",
5218
+ "name": "when the \"First Tab\" is focused",
5219
+ "children": [
5220
+ {
5221
+ "type": "describe",
5222
+ "name": "when the Tab key is pressed",
5223
+ "children": [
5224
+ {
5225
+ "type": "it",
5226
+ "name": "should focus on the \"More\" button"
5227
+ }
5228
+ ]
5229
+ }
5230
+ ]
5231
+ },
5200
5232
  {
5201
5233
  "type": "describe",
5202
5234
  "name": "when the \"More\" button is clicked",
@@ -5981,7 +6013,7 @@ module.exports = {specifications: [
5981
6013
  },
5982
6014
  {
5983
6015
  "type": "describe",
5984
- "name": "when the \"Delete\" button is hovered",
6016
+ "name": "when the \"Some Text\" text is hovered",
5985
6017
  "children": [
5986
6018
  {
5987
6019
  "type": "it",
@@ -5185,6 +5185,22 @@ module.exports = {specifications: [
5185
5185
  "type": "it",
5186
5186
  "name": "should not show the \"More\" button"
5187
5187
  },
5188
+ {
5189
+ "type": "describe",
5190
+ "name": "when the \"First Tab\" is focused",
5191
+ "children": [
5192
+ {
5193
+ "type": "describe",
5194
+ "name": "when the Tab key is pressed",
5195
+ "children": [
5196
+ {
5197
+ "type": "it",
5198
+ "name": "should focus on the tab panel"
5199
+ }
5200
+ ]
5201
+ }
5202
+ ]
5203
+ },
5188
5204
  {
5189
5205
  "type": "describe",
5190
5206
  "name": "when tab list container is only 500px wide",
@@ -5197,6 +5213,22 @@ module.exports = {specifications: [
5197
5213
  "type": "it",
5198
5214
  "name": "should show the \"More\" button"
5199
5215
  },
5216
+ {
5217
+ "type": "describe",
5218
+ "name": "when the \"First Tab\" is focused",
5219
+ "children": [
5220
+ {
5221
+ "type": "describe",
5222
+ "name": "when the Tab key is pressed",
5223
+ "children": [
5224
+ {
5225
+ "type": "it",
5226
+ "name": "should focus on the \"More\" button"
5227
+ }
5228
+ ]
5229
+ }
5230
+ ]
5231
+ },
5200
5232
  {
5201
5233
  "type": "describe",
5202
5234
  "name": "when the \"More\" button is clicked",
@@ -5981,7 +6013,7 @@ module.exports = {specifications: [
5981
6013
  },
5982
6014
  {
5983
6015
  "type": "describe",
5984
- "name": "when the \"Delete\" button is hovered",
6016
+ "name": "when the \"Some Text\" text is hovered",
5985
6017
  "children": [
5986
6018
  {
5987
6019
  "type": "it",
@@ -10,6 +10,7 @@ any questions about the update.
10
10
  - [Canvas Kit Preview](#canvas-kit-preview)
11
11
  - [Type Deprecations and Hierarchy Updates](#type-deprecations-and-hierarchy-updates)
12
12
  - [Canvas Kit CSS Maintenance Mode](#canvas-kit-css-maintenance-mode)
13
+ - [Prop Interfaces](#prop-interfaces)
13
14
  - [Component Changes](#component-changes)
14
15
  - [Component Promotions](#component-promotions)
15
16
  - [Core](#core)
@@ -423,6 +424,59 @@ provide more focused support and to dedicate our efforts to making bigger and be
423
424
  our most used components: Canvas Kit React. If you have questions or concerns, please
424
425
  [let us know](https://github.com/Workday/canvas-kit/issues/new?labels=&template=question.md).
425
426
 
427
+ ### Prop Interfaces
428
+
429
+ Many components were updated to be polymorphic using the `createComponent` utility function. Most
430
+ components in Canvas Kit extend from an HTML interface and spread extra props onto the HTML element.
431
+ Since these components are now polymorphic, the exported props no longer extend from an HTML
432
+ interface since the HTML interface is now determined by an optional `as` prop. It is common to wrap
433
+ Canvas Kit components with your own component and extend from the Canvas Kit component's prop
434
+ interface. To support this use-case in addition to polymorphic prop interfaces, `ExtractProp` was
435
+ introduced. `ExtractProp` understands these polymorphic components and will return the base props in
436
+ addition to the HTML interface. There is an optional second argument that can override the default
437
+ HTML interface if your wrapper component uses the `as`.
438
+
439
+ ```tsx
440
+ // v4
441
+ import {TextInput, TextInputProps} from '@workday/canvas-kit-react-text-input';
442
+
443
+ const FancyTextInput: React.FC<TextInputProps> = props => <TextInput {...props} />;
444
+
445
+ // v5
446
+ import {TextInput} from '@workday/canvas-kit-react/text-input';
447
+ import {ExtractProps} from '@workday/canvas-kit-react/common';
448
+
449
+ const FancyTextInput: React.FC<ExtractProps<typeof TextInput>> = props => {};
450
+
451
+ // v5 via createComponent
452
+ import {TextInput} from '@workday/canvas-kit-react/text-input';
453
+ import {createComponent} from '@workday/canvas-kit-react/common';
454
+
455
+ const FancyTextInput = createComponent(TextInput)({
456
+ displayName: 'FancyTextInput',
457
+ Component((props) => <TextInput {...props} />)
458
+ })
459
+ ```
460
+
461
+ Components that made this change:
462
+
463
+ - Button
464
+ - IconButton
465
+ - Card
466
+ - Hyperlink
467
+ - Select
468
+ - TextArea
469
+ - TextInput
470
+ - Checkbox
471
+ - Radio
472
+ - ColorInput
473
+ - ColorPreview
474
+ - Modal
475
+ - Popup
476
+ - Skeleton
477
+ - Tabs
478
+ - Toast
479
+
426
480
  ## Component Changes
427
481
 
428
482
  ### Component Promotions
@@ -799,20 +853,25 @@ Button prop interface and accesses properties like `onClick`, you'll need to pro
799
853
  attribute yourself in order to avoid TypeScript issues (this doesn't affect runtime). This is not
800
854
  code-moddable since intent cannot be pre-determined.
801
855
 
856
+ #### Props
857
+
858
+ The exported props no longer extend from the `HTMLButtonElement` interface. Use
859
+ [ExtractProps](#prop-interfaces) instead.
860
+
802
861
  ```tsx
803
862
  interface MyButtonProps extends ButtonProps {}
804
863
 
805
864
  // onClick no longer exists in `ButtonProps`, so TypeScript will complain about onClick not
806
865
  // existing in `MyButtonProps` (`onClick` does exist as a prop on `<Button>`, however)
807
866
  const MyButton = ({children, onClick}: MyButtonProps) => (
808
- <Button onClick={onClick}>{children}</Button>
867
+ <SecondaryButton onClick={onClick}>{children}</SecondaryButton>
809
868
  );
810
869
 
811
870
  // After
812
- interface MyButtonProps extends ButtonProps, React.ButtonHTMLAttributes<HTMLButtonElement> {}
871
+ interface MyButtonProps extends ExtractProps<typeof SecondaryButton> {}
813
872
 
814
873
  // After (alternate fix)
815
- interface MyButtonProps extends ButtonProps {
874
+ interface MyButtonProps extends ExtractProps<> {
816
875
  onClick?: React.MouseEventHandler<HTMLButtonElement>;
817
876
  }
818
877
  ```
@@ -881,6 +940,21 @@ const props = {
881
940
  import {Card} from './Card' // where `Card` is a re-exported Canvas Kit `Card`
882
941
  ```
883
942
 
943
+ #### Props
944
+
945
+ The exported props no longer extend from the `HTMLDivElement` interface. Use
946
+ [ExtractProps](#prop-interfaces) instead.
947
+
948
+ ```tsx
949
+ // NOT handled by the codemod
950
+
951
+ // v4
952
+ interface MyCard extends CardProps {}
953
+
954
+ // v5
955
+ interface MyCard extends ExtractProps<typeof Card>
956
+ ```
957
+
884
958
  ---
885
959
 
886
960
  ### Inputs
@@ -924,14 +998,14 @@ element that `inputRef` was applied to previously. Select and Select (Preview) d
924
998
  `inputRef` in v4, but now support `ref` in v5. See each component's documentation for information on
925
999
  which element `ref` is forwarded to for that particular component.
926
1000
 
1001
+ #### Props
1002
+
927
1003
  Input component prop interfaces no longer extend directly from their underlying element interface
928
1004
  (e.g. `TextInputProps` no longer extends from `React.InputHTMLAttributes<HTMLInputElement>`).
929
1005
  `createComponent` returns a component that determines the element interface via the `as` prop. This
930
1006
  is why input component props no longer contain an element interface directly. If you extend from an
931
1007
  input component prop interface, or have code that uses an input component prop interface and
932
- accesses properties like `onClick`, you'll need to provide the input attribute yourself in order to
933
- avoid TypeScript issues (this doesn't affect runtime). This is not code-moddable since intent cannot
934
- be pre-determined.
1008
+ accesses properties like `onClick`, you'll need to use [ExtractProps](#prop-interfaces) instead.
935
1009
 
936
1010
  ```tsx
937
1011
  interface MyTextInputProps extends TextInputProps {}
@@ -941,7 +1015,7 @@ interface MyTextInputProps extends TextInputProps {}
941
1015
  const MyTextInput = ({onClick}: MyTextInputProps) => <TextInput onClick={onClick} />;
942
1016
 
943
1017
  // Fix
944
- interface MyTextInputProps extends TextInputProps, React.InputHTMLAttributes<HTMLInputElement> {}
1018
+ interface MyTextInputProps extends ExtractProps<typeof TextInput> {}
945
1019
 
946
1020
  // Alternate fix
947
1021
  interface MyTextInputProps extends TextInputProps {
@@ -216,6 +216,9 @@ const CustomGlobalHeader = props => {
216
216
  };
217
217
  ```
218
218
 
219
+ You may continue to use this component exactly as you did in v5, but note that we plan to hard-deprecate it in Canvas Kit v7.
220
+ If you would like to migrate away from this deprecated component now, you can reference [this example](https://workday.github.io/canvas-kit/?path=/story/examples-global-header-react--basic) instead.
221
+
219
222
  ### Page Header
220
223
 
221
224
  We are [soft-deprecating](#soft-deprecation) `PageHeader`. It has been renamed to
@@ -267,6 +270,9 @@ export const CustomPageHeader = (props: CustomPageHeaderProps) => {
267
270
  };
268
271
  ```
269
272
 
273
+ You may continue to use this component exactly as you did in v5, but note that we plan to hard-deprecate it in Canvas Kit v7.
274
+ If you would like to migrate away from this deprecated component now, you can reference [this example](https://workday.github.io/canvas-kit/?path=/story/examples-page-header-react--basic) instead.
275
+
270
276
  ## Component Migrations
271
277
 
272
278
  ### Search Bar
@@ -29,7 +29,7 @@ do. A "test" simply has to pass. A specification requires meaning.
29
29
  test('SomeComponent should render correctly', async () => {
30
30
  const {getByTestId} = render(<SomeComponent data-testid="test" text="foo" />);
31
31
 
32
- const component = await getByTestId('test');
32
+ const component = getByTestId('test');
33
33
 
34
34
  expect(component.textContent).toEqual('foo');
35
35
  expect(component.getAttribute('aria-label')).toEqual('foo');
@@ -53,14 +53,14 @@ describe('SomeComponent', () => {
53
53
  it('should render the "text" prop as the text', async () => {
54
54
  const {getByTestId} = render(<SomeComponent data-testid="test" text="foo" />);
55
55
 
56
- const component = await getByTestId('test');
56
+ const component = getByTestId('test');
57
57
  expect(component).toHaveTextContent('foo');
58
58
  });
59
59
 
60
60
  it('should render the "text" prop as an aria-label for accessibility', async () => {
61
61
  const {getByTestId} = render(<SomeComponent data-testid="test" text="foo" />);
62
62
 
63
- const component = await getByTestId('test');
63
+ const component = getByTestId('test');
64
64
  expect(component).toHaveAttribute('aria-label', 'foo');
65
65
  });
66
66
  });
@@ -124,8 +124,8 @@ const {getByTestId} = render(
124
124
  </SomeComponent>
125
125
  );
126
126
 
127
- const component = await getByTestId('container');
128
- const child = await getByTestId('test');
127
+ const component = getByTestId('container');
128
+ const child = getByTestId('test');
129
129
 
130
130
  expect(component).toContainElement(child);
131
131
  ```
@@ -136,6 +136,30 @@ specification is met). Also this example will have a more useful failure message
136
136
  `.toContainElement` has the context that it is expecting an element in another element vs a match of
137
137
  a string.
138
138
 
139
+ ### Snapshot tests
140
+
141
+ Canvas Kit does not contain DOM-based snapshot tests and uses [Visual Tests](#visual-tests) instead.
142
+ DOM snapshots failures are often difficult to parse. Humans tend to be better at noticing and
143
+ discerning visual changes than changes to a DOM structure.
144
+
145
+ If your project uses snapshot tests and Canvas Kit, chances are you'll run into issues with changing
146
+ ids and other ARIA attributes. Canvas Kit generates unique ids that are different every time the
147
+ page loads. This can be a problem with snapshot tests. To fix this, you'll need to add special code
148
+ to your test bootstrap file. For example:
149
+
150
+ ```ts
151
+ import {setUniqueSeed, resetUniqueIdCount} from '@workday/canvas-kit-react/common';
152
+
153
+ beforeEach(() => {
154
+ setUniqueSeed('a'); // set a static seed
155
+ resetUniqueIdCount(); // reset the id counter before every test
156
+ });
157
+ ```
158
+
159
+ This will ensure snapshot tests have stable ids for each snapshot. It is still possible to get ids
160
+ changing if you add an additional component that uses id generation - subsequent ids will be
161
+ different, but this will prevent snapshot tests that don't have any changes from showing diffs.
162
+
139
163
  ## Functional tests
140
164
 
141
165
  Canvas Kit uses [Cypress][https://cypress.io] for browser-based behavior testing (additional info:
@@ -8,6 +8,7 @@ import {
8
8
  usePopupModel,
9
9
  useAlwaysCloseOnOutsideClick,
10
10
  useCloseOnEscape,
11
+ useTransferOnFullscreenExit,
11
12
  } from '@workday/canvas-kit-react/popup';
12
13
 
13
14
  const ContextMenuTarget = createComponent('div')({
@@ -40,6 +41,7 @@ export default () => {
40
41
 
41
42
  useAlwaysCloseOnOutsideClick(model);
42
43
  useCloseOnEscape(model);
44
+ useTransferOnFullscreenExit(model);
43
45
 
44
46
  return (
45
47
  <Popup model={model}>
@@ -0,0 +1,8 @@
1
+ import Basic from './examples/CookieBanner';
2
+
3
+
4
+ # Canvas Kit Examples
5
+
6
+ ## CookieBanner
7
+
8
+ <ExampleCodeBlock code={Basic} />
@@ -0,0 +1,9 @@
1
+ import Basic from './examples/GlobalHeader';
2
+
3
+
4
+ # Canvas Kit Examples
5
+
6
+ ## GlobalHeader
7
+ Developers building internal Workday applications will likely not need to create this component. However, if you're building components to be used outside of Workday, this is a helpful reference for building a global navigation header that looks like our internal `GlobalHeader`.
8
+
9
+ <ExampleCodeBlock code={Basic} />
@@ -0,0 +1,8 @@
1
+ import {Basic} from "./examples/PageHeader";
2
+
3
+
4
+ # Canvas Kit Examples
5
+
6
+ ## PageHeader
7
+
8
+ <ExampleCodeBlock code={Basic} />
@@ -0,0 +1,97 @@
1
+ import * as React from 'react';
2
+
3
+ import {createComponent, styled, StyledType} from '@workday/canvas-kit-react/common';
4
+ import {colors, commonColors, depth, type, space} from '@workday/canvas-kit-react/tokens';
5
+ import {PrimaryButton, TertiaryButton} from '@workday/canvas-kit-react/button';
6
+
7
+ const CookieBannerItem = createComponent('div')({
8
+ displayName: 'CookieBanner.Item',
9
+ Component: ({isRow, ...elProps}: ItemProps, ref) => (
10
+ <BannerItem ref={ref} isRow={isRow} {...elProps} />
11
+ ),
12
+ });
13
+
14
+ const CookieBanner = createComponent('div')({
15
+ displayName: 'CookieBanner',
16
+ Component: (props: BannerProps, ref, Element) => <Banner ref={ref} as={Element} {...props} />,
17
+ subComponents: {Item: CookieBannerItem},
18
+ });
19
+
20
+ export default () => {
21
+ const DefaultNotice = `We use cookies to ensure that we give you the best experience on our website.
22
+ If you continue without changing your settings, we'll assume that you are willing to receive cookies.`;
23
+
24
+ return (
25
+ <ExampleContainer>
26
+ <CookieBanner isClosed={false}>
27
+ <CookieBanner.Item>{DefaultNotice}</CookieBanner.Item>
28
+ <CookieBanner.Item isRow>
29
+ <TertiaryButton>Settings</TertiaryButton>
30
+ <PrimaryButton>Continue</PrimaryButton>
31
+ </CookieBanner.Item>
32
+ </CookieBanner>
33
+ </ExampleContainer>
34
+ );
35
+ };
36
+
37
+ interface BannerProps {
38
+ isClosed?: boolean;
39
+ }
40
+
41
+ interface ItemProps {
42
+ isRow?: boolean;
43
+ }
44
+
45
+ const ExampleContainer = styled('div')({
46
+ minHeight: 84,
47
+ margin: space.xs,
48
+ position: 'relative',
49
+ });
50
+
51
+ const Banner = styled('div')<BannerProps & StyledType>(
52
+ type.levels.subtext.medium,
53
+ {
54
+ backgroundColor: commonColors.background,
55
+ borderTop: `1px solid ${colors.soap400}`,
56
+ display: 'flex',
57
+ ...depth[1],
58
+ padding: space.m,
59
+ alignItems: 'center',
60
+ justifyContent: 'space-between',
61
+ position: 'absolute',
62
+ bottom: 0,
63
+ left: 0,
64
+ right: 0,
65
+ zIndex: 99,
66
+ transition: 'transform 0.2s ease-out',
67
+ '@media (max-width: 450px)': {
68
+ flexDirection: 'column',
69
+ alignItems: 'stretch',
70
+ textAlign: 'center',
71
+ padding: `${space.s} 0`,
72
+ },
73
+ },
74
+ ({isClosed}) => isClosed && {transform: 'translateY(100%)'}
75
+ );
76
+
77
+ const BannerItem = styled('div')<ItemProps & StyledType>(
78
+ {
79
+ marginLeft: space.s,
80
+ marginRight: space.s,
81
+ '@media (max-width: 450px)': {
82
+ '&:not(:first-of-type)': {
83
+ marginTop: space.s,
84
+ '> *': {
85
+ flex: 1,
86
+ },
87
+ },
88
+ },
89
+ },
90
+ ({isRow}) =>
91
+ isRow && {
92
+ display: 'flex',
93
+ '> *': {
94
+ marginLeft: space.s,
95
+ },
96
+ }
97
+ );
@@ -0,0 +1,66 @@
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
+
5
+ import {
6
+ notificationsIcon,
7
+ inboxIcon,
8
+ justifyIcon,
9
+ assistantIcon,
10
+ } from '@workday/canvas-system-icons-web';
11
+
12
+ import {IconButton, Hyperlink} from '@workday/canvas-kit-react/button';
13
+ import {Avatar} from '@workday/canvas-kit-react/avatar';
14
+ import {SearchForm, HStack, HStackProps, StackSpacing} from '@workday/canvas-kit-labs-react';
15
+
16
+ interface HeaderItemProps extends Omit<HStackProps, 'spacing'> {
17
+ spacing?: StackSpacing;
18
+ }
19
+
20
+ export default () => (
21
+ <GlobalHeader>
22
+ <GlobalHeader.Item>
23
+ <IconButton aria-label="menu" icon={justifyIcon} />
24
+ <Hyperlink>
25
+ <WorkdayLogo dangerouslySetInnerHTML={{__html: dubLogoBlue}} />
26
+ </Hyperlink>
27
+ </GlobalHeader.Item>
28
+ <GlobalHeader.Item margin="auto" width="100%" maxWidth={`calc(${space.xxxl} * 6)`}>
29
+ <SearchForm onSubmit={() => 1} />
30
+ </GlobalHeader.Item>
31
+ <GlobalHeader.Item>
32
+ <IconButton aria-label="messages" icon={assistantIcon} />
33
+ <IconButton aria-label="notifications" icon={notificationsIcon} />
34
+ <IconButton aria-label="inbox" icon={inboxIcon} />
35
+ <Avatar size={Avatar.Size.m} variant={Avatar.Variant.Light} />
36
+ </GlobalHeader.Item>
37
+ </GlobalHeader>
38
+ );
39
+
40
+ const GlobalHeaderItem = createComponent('div')({
41
+ displayName: 'GlobalHeader.Item',
42
+ Component: ({spacing = 's', ...props}: HeaderItemProps, ref) => (
43
+ <HStack spacing={spacing} alignItems="center" marginX={space.xs} ref={ref} {...props} />
44
+ ),
45
+ });
46
+
47
+ const GlobalHeader = createComponent('header')({
48
+ displayName: 'GlobalHeader',
49
+ Component: (props, ref, Element) => <HeaderWrapper ref={ref} as={Element} {...props} />,
50
+ subComponents: {Item: GlobalHeaderItem},
51
+ });
52
+
53
+ const HeaderWrapper = styled('header')({
54
+ display: 'flex',
55
+ alignItems: 'center',
56
+ justifyContent: 'space-between',
57
+ boxSizing: 'border-box',
58
+ ...type.levels.subtext.large,
59
+ WebkitFontSmoothing: 'antialiased',
60
+ MozOsxFontSmoothing: 'grayscale',
61
+ backgroundColor: colors.frenchVanilla100,
62
+ ...depth[1],
63
+ padding: space.xxs,
64
+ });
65
+
66
+ const WorkdayLogo = styled('span')({lineHeight: 0});
@@ -0,0 +1,63 @@
1
+ import * as React from 'react';
2
+ import {styled, createComponent} from '@workday/canvas-kit-react/common';
3
+
4
+ import {colors, gradients, space, type} from '@workday/canvas-kit-react/tokens';
5
+
6
+ import {HStack, HStackProps, StackSpacing} from '@workday/canvas-kit-labs-react';
7
+ import {IconButton} from '@workday/canvas-kit-react/button';
8
+ import {justifyIcon, notificationsIcon} from '@workday/canvas-system-icons-web';
9
+
10
+ interface HeaderItemProps extends Omit<HStackProps, 'spacing'> {
11
+ spacing?: StackSpacing;
12
+ }
13
+
14
+ export default () => (
15
+ <PageHeader>
16
+ <PageHeader.Title>Page Header</PageHeader.Title>
17
+ <PageHeader.Item>
18
+ <IconButton aria-label="notifications" icon={notificationsIcon} variant="inverse" />
19
+ <IconButton aria-label="menu" icon={justifyIcon} variant="inverse" />
20
+ </PageHeader.Item>
21
+ </PageHeader>
22
+ );
23
+
24
+ const PageHeaderItem = createComponent('div')({
25
+ displayName: 'PageHeader.Item',
26
+ Component: ({spacing = 'xxs', ...props}: HeaderItemProps, ref, Element) => (
27
+ <HStack spacing={spacing} ref={ref} as={Element} {...props} />
28
+ ),
29
+ });
30
+
31
+ const PageHeaderTitle = createComponent('h2')({
32
+ displayName: 'PageHeader.Title',
33
+ Component: ({children, ...props}, ref, Element) => (
34
+ <Title ref={ref} as={Element} {...props}>
35
+ {children}
36
+ </Title>
37
+ ),
38
+ });
39
+
40
+ const PageHeader = createComponent('header')({
41
+ displayName: 'PageHeader',
42
+ Component: (props, ref, Element) => <Header ref={ref} as={Element} {...props} />,
43
+ subComponents: {Item: PageHeaderItem, Title: PageHeaderTitle},
44
+ });
45
+
46
+ const Header = styled('header')({
47
+ padding: `${space.xs} ${space.xl}`,
48
+ backgroundImage: gradients.blueberry,
49
+ color: colors.frenchVanilla100,
50
+ WebkitFontSmoothing: 'antialiased',
51
+ MozOsxFontSmoothing: 'grayscale',
52
+ display: 'flex',
53
+ alignItems: 'center',
54
+ justifyContent: 'space-between',
55
+ });
56
+
57
+ const Title = styled('h2')({
58
+ ...type.levels.heading.medium,
59
+ color: colors.frenchVanilla100,
60
+ padding: `${space.xs} 0`,
61
+ margin: 0,
62
+ whiteSpace: 'nowrap',
63
+ });
@@ -4,10 +4,11 @@ import {PrimaryButton} from '@workday/canvas-kit-react/button';
4
4
  import {HStack} from '@workday/canvas-kit-labs-react/layout';
5
5
  import {plusIcon} from '@workday/canvas-system-icons-web';
6
6
 
7
- export default () => (
7
+ export default props => (
8
8
  <HStack spacing="s" padding="s">
9
- <PrimaryButton>Primary</PrimaryButton>
10
- <PrimaryButton icon={plusIcon} iconPosition="right">
9
+ <PrimaryButton {...props}>Primary</PrimaryButton>
10
+ <p>{JSON.stringify(props.theme)}</p>
11
+ <PrimaryButton icon={plusIcon} iconPosition="right" {...props}>
11
12
  Primary
12
13
  </PrimaryButton>
13
14
  </HStack>
@@ -1,6 +1,7 @@
1
1
  import {LoadingAnimation} from '@workday/canvas-kit-react/loading-animation';
2
2
 
3
3
  import Basic from './examples/Basic';
4
+ import RTL from './examples/RTL';
4
5
 
5
6
 
6
7
  # Canvas Kit Loading Animation
@@ -22,6 +23,10 @@ yarn add @workday/canvas-kit-react
22
23
 
23
24
  <ExampleCodeBlock code={Basic} />
24
25
 
26
+ ### Right-to-Left (RTL)
27
+
28
+ <ExampleCodeBlock code={RTL} />
29
+
25
30
  ## Props
26
31
 
27
32
  Loading Animation does not have any documented props. Undocumented props are spread to its outermost
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import {LoadingAnimation} from '@workday/canvas-kit-react/loading-animation';
3
+ import {CanvasProvider, ContentDirection} from '@workday/canvas-kit-react/common';
4
+
5
+ export default () => {
6
+ const theme = {
7
+ canvas: {
8
+ direction: ContentDirection.RTL,
9
+ },
10
+ };
11
+ return (
12
+ <CanvasProvider theme={theme}>
13
+ <LoadingAnimation />
14
+ </CanvasProvider>
15
+ );
16
+ };
@@ -9,6 +9,7 @@ import FocusRedirect from './examples/FocusRedirect';
9
9
  import FocusTrap from './examples/FocusTrap';
10
10
  import RTL from './examples/RTL';
11
11
  import CustomTarget from './examples/CustomTarget';
12
+ import FullScreen from './examples/FullScreen';
12
13
  import {
13
14
  PopupModelConfigComponent,
14
15
  PopupStateComponent,
@@ -143,6 +144,25 @@ requires a `label` prop.
143
144
 
144
145
  <ExampleCodeBlock code={CustomTarget} />
145
146
 
147
+ ### Full Screen API
148
+
149
+ By default, popups are created as children of the `document.body` element, but the `PopupStack`
150
+ supports the [Fullscreen API](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API). When
151
+ fullscreen is entered, the `PopupStack` will automatically create a new stacking context for all
152
+ future popups. Any existing popups will disappear, but not be removed. They disappear because the
153
+ fullscreen API is only showing content within the fullscreen element. There are instances where a
154
+ popup may not close when fullscreen is exited:
155
+
156
+ - The escape key is used to exit fullscreen
157
+ - There is a button to exit fullscreen, but the popup doesn't use `useCloseOnOutsideClick`
158
+
159
+ If fullscreen is exited, popups within the fullscreen stacking context are not removed or
160
+ transferred automatically. If you do not handle this case, the popup may not render correctly. This
161
+ example shows a popup that closes when fullscreen is entered/exited and another popup that transfers
162
+ the popup's stack context when entering/exiting fullscreen.
163
+
164
+ <ExampleCodeBlock code={FullScreen} />
165
+
146
166
  ### RTL
147
167
 
148
168
  The Popup component automatically handles right-to-left rendering.
@@ -396,6 +416,15 @@ stack. This is useful for Tooltips or hierarchical menus. Adds a
396
416
  This should be used with popup elements that should close no matter their position in the stack
397
417
  (i.e. Tooltips).
398
418
 
419
+ ### useCloseOnFullscreenExit
420
+
421
+ ```ts
422
+ useCloseOnFullscreenExit(model: PopupModel): {}
423
+ ```
424
+
425
+ Closes the popup when fullscreen is exited. Entering/exiting fullscreen changes the context of the
426
+ entire screen. This should be added to popup types that are very context sensitive like Tooltips.
427
+
399
428
  ### useDisableBodyScroll
400
429
 
401
430
  ```ts
@@ -442,6 +471,10 @@ This should be used on popup elements that need to hide content (i.e. Modals).
442
471
 
443
472
  ### useInitialFocus
444
473
 
474
+ ```ts
475
+ useInitialFocus(model: PopupModel): {}
476
+ ```
477
+
445
478
  Moves focus within the popup when the popup becomes visible. This is useful for keyboard and screen
446
479
  reader users alike. This should be used with [useFocusRedirect](#usefocusredirect) or
447
480
  [useFocusTrap](#usefocustrap) for a complete focus management solution.
@@ -451,12 +484,41 @@ menus, etc.
451
484
 
452
485
  ### useReturnFocus
453
486
 
487
+ ```ts
488
+ useReturnFocus(model: PopupModel): {}
489
+ ```
490
+
454
491
  Returns focus to the target element when the popup is hidden. This works well with
455
492
  [useInitialFocus](#useinitialfocus). This should be used with [useFocusRedirect](#usefocusredirect)
456
493
  or [useFocusTrap](#usefocustrap) for a complete focus management solution.
457
494
 
458
495
  This should ble used on popup elements that use [useInitialFocus](#useinitialfocus).
459
496
 
497
+ ### useTransferOnFullscreenEnter
498
+
499
+ ```ts
500
+ useTransferOnFullscreenEnter(model: PopupModel): {}
501
+ ```
502
+
503
+ Makes the popup transfer to the fullscreen element when fullscreen is entered. Without this, the
504
+ popup would seem to disappear because the popup container element is not a child of the fullscreen
505
+ element.
506
+
507
+ Don't use this in conjunction with a hook that will close the popup when entering fullscreen. Doing
508
+ so would open the popup when the intention was to close it.
509
+
510
+ ### useTransferOnFullscreenExit
511
+
512
+ ```ts
513
+ useTransferOnFullscreenExit(model: PopupModel): {}
514
+ ```
515
+
516
+ Makes the popup transfer to fullscreen when fullscreen is exited. Without this hook, the popup would
517
+ not operate correctly with other popups on the screen.
518
+
519
+ Don't use this in conjunction with a hook that will close the popup when exiting fullscreen. Doing
520
+ so would open the popup when the intention was to close it.
521
+
460
522
  ### usePopupPopper
461
523
 
462
524
  ```ts
@@ -0,0 +1,115 @@
1
+ import * as React from 'react';
2
+
3
+ import {SecondaryButton} from '@workday/canvas-kit-react/button';
4
+ import {
5
+ Popup,
6
+ useCloseOnEscape,
7
+ useCloseOnOutsideClick,
8
+ useFocusTrap,
9
+ useInitialFocus,
10
+ useReturnFocus,
11
+ usePopupModel,
12
+ useCloseOnFullscreenExit,
13
+ useTransferOnFullscreenExit,
14
+ useTransferOnFullscreenEnter,
15
+ } from '@workday/canvas-kit-react/popup';
16
+ import {HStack, Flex} from '@workday/canvas-kit-labs-react/layout';
17
+ import {useIsFullscreen} from '@workday/canvas-kit-react/common';
18
+ import screenfull from 'screenfull';
19
+
20
+ const SelfClosePopup = () => {
21
+ const model = usePopupModel();
22
+
23
+ useCloseOnOutsideClick(model);
24
+ useCloseOnEscape(model);
25
+ useInitialFocus(model);
26
+ useReturnFocus(model);
27
+ useFocusTrap(model);
28
+ useCloseOnFullscreenExit(model);
29
+
30
+ return (
31
+ <Popup model={model}>
32
+ <Popup.Target>Open Self-close Popup</Popup.Target>
33
+ <Popup.Popper>
34
+ <Popup.Card width={400} padding="s">
35
+ <Popup.CloseIcon aria-label="Close" />
36
+ <Popup.Heading>Self-close Popup</Popup.Heading>
37
+ <Popup.Body>
38
+ <p>
39
+ When in fullscreen, the escape key will be highjacked by the browser to exit
40
+ fullscreen and <code>useCloseOnEscape</code> hook will not receive the escape key. To
41
+ close when fullscreen is exited, use the <code>useCloseOnFullscreenExit</code> hook.
42
+ </p>
43
+ </Popup.Body>
44
+ <Popup.CloseButton>Close</Popup.CloseButton>
45
+ </Popup.Card>
46
+ </Popup.Popper>
47
+ </Popup>
48
+ );
49
+ };
50
+
51
+ const TransferClosePopup = () => {
52
+ const model = usePopupModel();
53
+
54
+ useCloseOnEscape(model);
55
+ useInitialFocus(model);
56
+ useReturnFocus(model);
57
+ useFocusTrap(model);
58
+ useTransferOnFullscreenEnter(model);
59
+ useTransferOnFullscreenExit(model);
60
+
61
+ return (
62
+ <Popup model={model}>
63
+ <Popup.Target>Open Transfer Popup</Popup.Target>
64
+ <Popup.Popper>
65
+ <Popup.Card width={400} padding="s">
66
+ <Popup.CloseIcon aria-label="Close" />
67
+ <Popup.Heading>Transfer Popup</Popup.Heading>
68
+ <Popup.Body>
69
+ <p>
70
+ When in fullscreen, the escape key will be highjacked by the browser to exit
71
+ fullscreen and <code>useCloseOnEscape</code> hook will not receive the escape key. To
72
+ close when fullscreen is exited, use the <code>useTransferOnFullscreenExit</code>{' '}
73
+ hook.
74
+ </p>
75
+ </Popup.Body>
76
+ <Popup.CloseButton>Close</Popup.CloseButton>
77
+ </Popup.Card>
78
+ </Popup.Popper>
79
+ </Popup>
80
+ );
81
+ };
82
+
83
+ export default () => {
84
+ // you could make this a hook depending on which fullscreen library your application uses
85
+ const fullscreenElementRef = React.useRef<HTMLDivElement>();
86
+ const isFullscreen = useIsFullscreen();
87
+
88
+ const enterFullScreen = () => {
89
+ screenfull.request(fullscreenElementRef.current);
90
+ };
91
+
92
+ const exitFullscreen = () => {
93
+ screenfull.exit();
94
+ };
95
+
96
+ return (
97
+ <>
98
+ <SecondaryButton onClick={enterFullScreen}>Open Fullscreen</SecondaryButton>
99
+ <Flex
100
+ ref={fullscreenElementRef}
101
+ alignItems="center"
102
+ justifyContent="center"
103
+ background="white"
104
+ >
105
+ <HStack spacing="s">
106
+ <SelfClosePopup />
107
+ <TransferClosePopup />
108
+ {isFullscreen ? (
109
+ <SecondaryButton onClick={exitFullscreen}>Exit fullscreen</SecondaryButton>
110
+ ) : null}
111
+ </HStack>
112
+ </Flex>
113
+ </>
114
+ );
115
+ };
@@ -4,17 +4,28 @@ import {space} from '@workday/canvas-kit-react/tokens';
4
4
  import {Tabs, useTabsModel, TabsModel} from '@workday/canvas-kit-react/tabs';
5
5
  import {SelectionModel} from '../../lib/selection';
6
6
 
7
+ type Tab = {
8
+ tab: string;
9
+ id: string;
10
+ };
11
+
7
12
  export default () => {
8
- const [tabs, setTabs] = React.useState([
13
+ const [tabs, setTabs] = React.useState<Tab[]>([
9
14
  {tab: 'Tab 1', id: 'tab-1'},
10
15
  {tab: 'Tab 2', id: 'tab-2'},
11
16
  {tab: 'Tab 3', id: 'tab-3'},
17
+ {tab: 'Add Tab', id: 'add'},
12
18
  ]);
13
- const addedRef = React.useRef(tabs.length);
19
+ const addedRef = React.useRef(tabs.length - 1);
14
20
  const model = useTabsModel({
15
- shouldSelect: ({data}) => data.id !== 'last',
21
+ items: tabs,
22
+ shouldSelect: ({data}) => data.id !== 'add',
16
23
  });
17
24
 
25
+ // A ref of the model for the render functions to work around the caching done to lists
26
+ const modelRef = React.useRef(model);
27
+ modelRef.current = model;
28
+
18
29
  /**
19
30
  * Helper function that should be called when an item is programmatically removed. The following
20
31
  * side effects depend on state of the model:
@@ -22,8 +33,9 @@ export default () => {
22
33
  * * **Item is selected**: Selection will be moved to the next item in the list
23
34
  * @param id The id of the item that will be removed
24
35
  */
25
- const removeItem = (id: string, model: SelectionModel) => {
36
+ const removeItem = <T extends unknown>(id: string, model: SelectionModel<T>) => {
26
37
  const index = model.state.items.findIndex(item => model.getId(item) === model.state.cursorId);
38
+ console.log('index', index, id, model.state.cursorId, model.state.items);
27
39
  const nextIndex = index === model.state.items.length - 1 ? index - 1 : index + 1;
28
40
  const nextId = model.getId(model.state.items[nextIndex]);
29
41
  if (model.state.selectedIds[0] === id) {
@@ -41,48 +53,52 @@ export default () => {
41
53
  }
42
54
  };
43
55
 
44
- const onKeyDown = (e: React.KeyboardEvent<HTMLElement>, id: string) => {
45
- if (e.key === 'Delete') {
56
+ const onKeyDown = (id: string) => (e: React.KeyboardEvent<HTMLElement>) => {
57
+ if (e.key === 'Delete' && id !== 'add') {
46
58
  setTabs(tabs.filter(item => item.id !== id));
59
+ const model = modelRef.current;
47
60
  removeItem(id, model);
48
61
  }
49
62
  };
50
63
 
64
+ const onClick = (id: string) => (e: React.MouseEvent) => {
65
+ if (id === 'add') {
66
+ addedRef.current += 1;
67
+ setTabs(tabs => {
68
+ const newTabs = tabs.slice(0, tabs.length - 1);
69
+ const addTab = tabs.slice(-1);
70
+ return newTabs.concat(
71
+ {tab: `Tab ${addedRef.current}`, id: `tab-${addedRef.current}`},
72
+ addTab
73
+ );
74
+ });
75
+ model.events.goTo({id: 'add'});
76
+ }
77
+ };
78
+
51
79
  return (
52
80
  <Tabs model={model}>
53
- <Tabs.List>
54
- {tabs.map((item, index) => (
55
- <Tabs.Item
56
- key={item.id}
57
- name={item.id}
58
- index={index}
59
- onKeyDown={e => onKeyDown(e, item.id)}
60
- >
81
+ <Tabs.List overflowButton={<Tabs.OverflowButton>More</Tabs.OverflowButton>}>
82
+ {(item: Tab) => (
83
+ <Tabs.Item name={item.id} onKeyDown={onKeyDown(item.id)} onClick={onClick(item.id)}>
61
84
  {item.tab}
62
85
  </Tabs.Item>
63
- ))}
64
- <Tabs.Item
65
- key={'last'}
66
- index={tabs.length}
67
- name={'last'}
68
- onClick={() => {
69
- addedRef.current += 1;
70
- setTabs(tabs =>
71
- tabs.concat({tab: `Tab ${addedRef.current}`, id: `tab-${addedRef.current}`})
72
- );
73
- model.events.goTo({id: 'last'});
74
- }}
75
- >
76
- Add Tab
77
- </Tabs.Item>
86
+ )}
78
87
  </Tabs.List>
79
- <div style={{marginTop: space.m}}>
80
- {tabs.map((item, index) => (
81
- <Tabs.Panel key={item.id} name={item.id}>
88
+ <Tabs.Menu.Popper>
89
+ <Tabs.Menu.Card maxWidth={300} maxHeight={200}>
90
+ <Tabs.Menu.List>
91
+ {(item: Tab) => <Tabs.Menu.Item name={item.id}>{item.tab}</Tabs.Menu.Item>}
92
+ </Tabs.Menu.List>
93
+ </Tabs.Menu.Card>
94
+ </Tabs.Menu.Popper>
95
+ <Tabs.Panels>
96
+ {(item: Tab) => (
97
+ <Tabs.Panel marginTop="m" name={item.id}>
82
98
  Contents of {item.tab}
83
99
  </Tabs.Panel>
84
- ))}
85
- </div>
100
+ )}
101
+ </Tabs.Panels>
86
102
  </Tabs>
87
103
  );
88
104
  };
@@ -3,6 +3,7 @@ import {Specifications} from '@workday/canvas-kit-docs';
3
3
 
4
4
  import Default from './examples/Default';
5
5
  import CustomContent from './examples/CustomContent';
6
+ import DelayedTooltip from './examples/DelayedTooltip';
6
7
  import DescribeType from './examples/DescribeType';
7
8
  import Muted from './examples/Muted';
8
9
  import Placements from './examples/Placements';
@@ -87,6 +88,13 @@ a dialog instead.
87
88
 
88
89
  <ExampleCodeBlock code={CustomContent} />
89
90
 
91
+ ### Delayed Tooltip
92
+
93
+ The default delay for showing and hiding a tooltip are 300ms and 100ms, respectively. You can
94
+ control the length of the delay by providing custom `showDelay` and `hideDelay` in ms.
95
+
96
+ <ExampleCodeBlock code={DelayedTooltip} />
97
+
90
98
  ### Placements
91
99
 
92
100
  The tooltip allows for a `placement` configuration. The tooltip uses
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+
3
+ import {SecondaryButton} from '@workday/canvas-kit-react/button';
4
+ import {Tooltip} from '@workday/canvas-kit-react/tooltip';
5
+
6
+ export default () => {
7
+ return (
8
+ <React.Fragment>
9
+ <Tooltip type="describe" title="Tooltip Text" showDelay={2000} hideDelay={1000}>
10
+ <SecondaryButton>
11
+ Tooltip appears after 2 seconds and disappears after 1 second
12
+ </SecondaryButton>
13
+ </Tooltip>
14
+ </React.Fragment>
15
+ );
16
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workday/canvas-kit-docs",
3
- "version": "5.3.0-next.18+6a7c92ab",
3
+ "version": "5.3.0-next.32+a02de5eb",
4
4
  "description": "Documentation components of Canvas Kit components",
5
5
  "author": "Workday, Inc. (https://www.workday.com)",
6
6
  "license": "Apache-2.0",
@@ -35,7 +35,8 @@
35
35
  "build:specs": "node ./utils/build-specifications.js",
36
36
  "build:downlevel-dts": "yarn run downlevel-dts dist ts3.5/dist",
37
37
  "build": "npm-run-all --parallel build:cjs build:es6 build:mdx --sequential build:specs build:downlevel-dts",
38
- "depcheck": "node ../../utils/check-dependencies-exist.js"
38
+ "depcheck": "node ../../utils/check-dependencies-exist.js",
39
+ "typecheck:src": "tsc -p . --noEmit --incremental false"
39
40
  },
40
41
  "publishConfig": {
41
42
  "access": "public"
@@ -49,7 +50,7 @@
49
50
  ],
50
51
  "dependencies": {
51
52
  "@storybook/csf": "0.0.1",
52
- "@workday/canvas-kit-react": "^5.3.0-next.18+6a7c92ab"
53
+ "@workday/canvas-kit-react": "^5.3.0-next.32+a02de5eb"
53
54
  },
54
55
  "devDependencies": {
55
56
  "fs-extra": "^10.0.0",
@@ -57,5 +58,5 @@
57
58
  "mkdirp": "^1.0.3",
58
59
  "typescript": "^3.8.3"
59
60
  },
60
- "gitHead": "6a7c92ab26c236a362790dceab7a2f10d84fe9ae"
61
+ "gitHead": "a02de5eb284c706211aa55ef3554b11f52668b28"
61
62
  }