@workday/canvas-kit-preview-react 16.0.0-alpha.0474-next.0 → 16.0.0-alpha.0476-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/commonjs/index.d.ts +1 -0
  2. package/dist/commonjs/index.d.ts.map +1 -1
  3. package/dist/commonjs/index.js +1 -0
  4. package/dist/commonjs/tabs/index.d.ts +7 -0
  5. package/dist/commonjs/tabs/index.d.ts.map +1 -0
  6. package/dist/commonjs/tabs/index.js +22 -0
  7. package/dist/commonjs/tabs/lib/Tabs.d.ts +9555 -0
  8. package/dist/commonjs/tabs/lib/Tabs.d.ts.map +1 -0
  9. package/dist/commonjs/tabs/lib/Tabs.js +127 -0
  10. package/dist/commonjs/tabs/lib/TabsItem.d.ts +719 -0
  11. package/dist/commonjs/tabs/lib/TabsItem.d.ts.map +1 -0
  12. package/dist/commonjs/tabs/lib/TabsItem.js +50 -0
  13. package/dist/commonjs/tabs/lib/TabsList.d.ts +998 -0
  14. package/dist/commonjs/tabs/lib/TabsList.d.ts.map +1 -0
  15. package/dist/commonjs/tabs/lib/TabsList.js +121 -0
  16. package/dist/commonjs/tabs/lib/TabsOverflowButton.d.ts +650 -0
  17. package/dist/commonjs/tabs/lib/TabsOverflowButton.d.ts.map +1 -0
  18. package/dist/commonjs/tabs/lib/TabsOverflowButton.js +28 -0
  19. package/dist/commonjs/tabs/lib/TabsPanel.d.ts +654 -0
  20. package/dist/commonjs/tabs/lib/TabsPanel.d.ts.map +1 -0
  21. package/dist/commonjs/tabs/lib/TabsPanel.js +42 -0
  22. package/dist/commonjs/tabs/lib/TabsPanels.d.ts +320 -0
  23. package/dist/commonjs/tabs/lib/TabsPanels.d.ts.map +1 -0
  24. package/dist/commonjs/tabs/lib/TabsPanels.js +13 -0
  25. package/dist/commonjs/tabs/lib/useTabsModel.d.ts +5924 -0
  26. package/dist/commonjs/tabs/lib/useTabsModel.d.ts.map +1 -0
  27. package/dist/commonjs/tabs/lib/useTabsModel.js +128 -0
  28. package/dist/es6/index.d.ts +1 -0
  29. package/dist/es6/index.d.ts.map +1 -1
  30. package/dist/es6/index.js +1 -0
  31. package/dist/es6/tabs/index.d.ts +7 -0
  32. package/dist/es6/tabs/index.d.ts.map +1 -0
  33. package/dist/es6/tabs/index.js +6 -0
  34. package/dist/es6/tabs/lib/Tabs.d.ts +9555 -0
  35. package/dist/es6/tabs/lib/Tabs.d.ts.map +1 -0
  36. package/dist/es6/tabs/lib/Tabs.js +124 -0
  37. package/dist/es6/tabs/lib/TabsItem.d.ts +719 -0
  38. package/dist/es6/tabs/lib/TabsItem.d.ts.map +1 -0
  39. package/dist/es6/tabs/lib/TabsItem.js +47 -0
  40. package/dist/es6/tabs/lib/TabsList.d.ts +998 -0
  41. package/dist/es6/tabs/lib/TabsList.d.ts.map +1 -0
  42. package/dist/es6/tabs/lib/TabsList.js +95 -0
  43. package/dist/es6/tabs/lib/TabsOverflowButton.d.ts +650 -0
  44. package/dist/es6/tabs/lib/TabsOverflowButton.d.ts.map +1 -0
  45. package/dist/es6/tabs/lib/TabsOverflowButton.js +25 -0
  46. package/dist/es6/tabs/lib/TabsPanel.d.ts +654 -0
  47. package/dist/es6/tabs/lib/TabsPanel.d.ts.map +1 -0
  48. package/dist/es6/tabs/lib/TabsPanel.js +36 -0
  49. package/dist/es6/tabs/lib/TabsPanels.d.ts +320 -0
  50. package/dist/es6/tabs/lib/TabsPanels.d.ts.map +1 -0
  51. package/dist/es6/tabs/lib/TabsPanels.js +10 -0
  52. package/dist/es6/tabs/lib/useTabsModel.d.ts +5924 -0
  53. package/dist/es6/tabs/lib/useTabsModel.d.ts.map +1 -0
  54. package/dist/es6/tabs/lib/useTabsModel.js +122 -0
  55. package/index.ts +1 -0
  56. package/package.json +4 -4
  57. package/tabs/index.ts +7 -0
  58. package/tabs/lib/Tabs.tsx +135 -0
  59. package/tabs/lib/TabsItem.tsx +274 -0
  60. package/tabs/lib/TabsList.tsx +181 -0
  61. package/tabs/lib/TabsOverflowButton.tsx +61 -0
  62. package/tabs/lib/TabsPanel.tsx +77 -0
  63. package/tabs/lib/TabsPanels.tsx +20 -0
  64. package/tabs/lib/useTabsModel.tsx +141 -0
  65. package/tabs/package.json +6 -0
@@ -0,0 +1,181 @@
1
+ import * as React from 'react';
2
+
3
+ import {
4
+ useListRenderItems,
5
+ useListResetCursorOnBlur,
6
+ useOverflowListMeasure,
7
+ } from '@workday/canvas-kit-react/collection';
8
+ import {
9
+ ExtractProps,
10
+ composeHooks,
11
+ createElemPropsHook,
12
+ createSubcomponent,
13
+ useLocalRef,
14
+ useModalityType,
15
+ } from '@workday/canvas-kit-react/common';
16
+ import {Flex, mergeStyles} from '@workday/canvas-kit-react/layout';
17
+ import {createStencil} from '@workday/canvas-kit-styling';
18
+ import {system} from '@workday/canvas-tokens-web';
19
+
20
+ import {useTabsModel} from './useTabsModel';
21
+
22
+ export interface TabListProps<T = any> extends Omit<ExtractProps<typeof Flex, never>, 'children'> {
23
+ /**
24
+ * If items are passed to a `TabsModel`, the child of `Tabs.List` should be a render prop. The
25
+ * List will determine how and when the item will be rendered.
26
+ *
27
+ * @example
28
+ * <Tabs.List>
29
+ * {(item) => <Tabs.Item>{item.text}</Tabs.Item>}
30
+ * </Tabs.List>
31
+ */
32
+ children: ((item: T) => React.ReactNode) | React.ReactNode;
33
+ overflowButton?: React.ReactNode;
34
+ }
35
+
36
+ function getScrollPercentage(elem: HTMLDivElement, direction: 'vertical' | 'horizontal'): number {
37
+ if (direction === 'vertical') {
38
+ if (elem.scrollHeight > elem.clientHeight) {
39
+ const percentage = elem.scrollTop / (elem.scrollHeight - elem.clientHeight);
40
+ return percentage;
41
+ } else {
42
+ return -1;
43
+ }
44
+ }
45
+ if (elem.scrollWidth > elem.clientWidth) {
46
+ const percentage = elem.scrollLeft / (elem.scrollWidth - elem.clientWidth);
47
+ return percentage;
48
+ } else {
49
+ return -1;
50
+ }
51
+ }
52
+
53
+ function getScrollPosition(
54
+ elem: HTMLDivElement,
55
+ direction: 'vertical' | 'horizontal'
56
+ ): 'start' | 'middle' | 'end' | undefined {
57
+ const percentage = getScrollPercentage(elem, direction);
58
+ return percentage <= -1
59
+ ? undefined
60
+ : percentage > 0.99
61
+ ? 'end'
62
+ : percentage > 0.01
63
+ ? 'middle'
64
+ : 'start';
65
+ }
66
+
67
+ function setScrollPosition(elem: HTMLDivElement, scrollPosition?: 'start' | 'middle' | 'end') {
68
+ if (scrollPosition) {
69
+ elem.setAttribute('data-scroll-position', scrollPosition);
70
+ } else {
71
+ elem.removeAttribute('data-scroll-position');
72
+ }
73
+ }
74
+
75
+ export const useTabOverflowScroll = createElemPropsHook(useTabsModel)((
76
+ _model,
77
+ ref,
78
+ elemProps: {
79
+ 'aria-orientation'?: 'vertical' | 'horizontal';
80
+ } = {}
81
+ ) => {
82
+ const direction = elemProps['aria-orientation'] || 'vertical';
83
+
84
+ const {localRef, elementRef} = useLocalRef<HTMLDivElement>(ref as React.Ref<HTMLDivElement>);
85
+
86
+ React.useLayoutEffect(() => {
87
+ if (!localRef.current) {
88
+ return;
89
+ }
90
+
91
+ setScrollPosition(localRef.current, getScrollPosition(localRef.current, direction));
92
+ }, [localRef, direction]);
93
+
94
+ return {
95
+ ref: elementRef,
96
+ onScroll: (event: React.UIEvent<HTMLDivElement>) => {
97
+ const elem = event.currentTarget;
98
+ setScrollPosition(elem, getScrollPosition(elem, direction));
99
+ },
100
+ };
101
+ });
102
+
103
+ export const useTabsList = composeHooks(
104
+ useTabOverflowScroll,
105
+ createElemPropsHook(useTabsModel)(model => {
106
+ return {
107
+ role: 'tablist',
108
+ 'aria-orientation': model.state.orientation,
109
+ } as const;
110
+ }),
111
+ useOverflowListMeasure,
112
+ useListResetCursorOnBlur
113
+ );
114
+
115
+ export const tabsListStencil = createStencil({
116
+ base: {
117
+ display: 'flex',
118
+ position: 'relative',
119
+ gap: system.legacy.gap.sm,
120
+
121
+ '@media screen and (max-width: 768px)': {
122
+ paddingInline: system.legacy.padding.md,
123
+ },
124
+ },
125
+ modifiers: {
126
+ modality: {
127
+ touch: {
128
+ overflowX: 'auto',
129
+ paddingInline: 0,
130
+ // data attributes are needed until scroll-driven animations are supported. Once they are,
131
+ // we can use a CSS-only solution:
132
+ // https://www.bram.us/2023/09/16/solved-by-css-scroll-driven-animations-detect-if-an-element-can-scroll-or-not/
133
+ '&[data-scroll-position="start"]': {
134
+ maskImage: 'linear-gradient(to right, white 80%, transparent)',
135
+ },
136
+ '&[data-scroll-position="middle"]': {
137
+ maskImage:
138
+ 'linear-gradient(to left, white 80%, transparent), linear-gradient(to right, white 80%, transparent)',
139
+ maskComposite: 'intersect',
140
+ },
141
+ '&[data-scroll-position="end"]': {
142
+ maskImage: 'linear-gradient(to left, white 80%, transparent)',
143
+ },
144
+ },
145
+ mouse: {},
146
+ pen: {},
147
+ },
148
+ isDragging: {
149
+ true: {},
150
+ false: {},
151
+ },
152
+ direction: {
153
+ left: {},
154
+ right: {},
155
+ },
156
+ },
157
+ });
158
+
159
+ export const TabsList = createSubcomponent('div')({
160
+ displayName: 'Tabs.List',
161
+ modelHook: useTabsModel,
162
+ elemPropsHook: useTabsList,
163
+ })<TabListProps & {maskImage?: string}>(
164
+ ({children, overflowButton, ...elemProps}, Element, model) => {
165
+ const modality = useModalityType();
166
+
167
+ return (
168
+ <Element
169
+ {...mergeStyles(
170
+ elemProps,
171
+ tabsListStencil({
172
+ modality: modality,
173
+ })
174
+ )}
175
+ >
176
+ {useListRenderItems(model, children)}
177
+ {overflowButton}
178
+ </Element>
179
+ );
180
+ }
181
+ );
@@ -0,0 +1,61 @@
1
+ import * as React from 'react';
2
+
3
+ import {useOverflowListTarget} from '@workday/canvas-kit-react/collection';
4
+ import {
5
+ composeHooks,
6
+ createElemPropsHook,
7
+ createSubModelElemPropsHook,
8
+ createSubcomponent,
9
+ } from '@workday/canvas-kit-react/common';
10
+ import {SystemIcon} from '@workday/canvas-kit-react/icon';
11
+ import {mergeStyles} from '@workday/canvas-kit-react/layout';
12
+ import {useMenuTarget} from '@workday/canvas-kit-react/menu';
13
+ import {createStencil} from '@workday/canvas-kit-styling';
14
+ import {chevronDownSmallIcon} from '@workday/canvas-system-icons-web';
15
+
16
+ import {StyledTabItem} from './TabsItem';
17
+ import {useTabsModel} from './useTabsModel';
18
+
19
+ export interface OverflowButtonProps {
20
+ /**
21
+ * The label text of the Tab.
22
+ */
23
+ children: React.ReactNode;
24
+ }
25
+
26
+ const tabsOverflowButtonStencil = createStencil({
27
+ base: {
28
+ '&:has([data-part="tabs-overflow-button-icon"])': {
29
+ display: 'flex',
30
+ gap: 0,
31
+ },
32
+ },
33
+ });
34
+
35
+ export const useTabsOverflowButton = composeHooks(
36
+ createElemPropsHook(useTabsModel)(() => {
37
+ return {
38
+ 'aria-haspopup': true,
39
+ } as const;
40
+ }),
41
+ useOverflowListTarget,
42
+ createSubModelElemPropsHook(useTabsModel)(m => m.menu, useMenuTarget)
43
+ );
44
+
45
+ export const TabsOverflowButton = createSubcomponent('button')({
46
+ displayName: 'Tabs.OverflowButton',
47
+ modelHook: useTabsModel,
48
+ elemPropsHook: useTabsOverflowButton,
49
+ })<OverflowButtonProps>(({children, ...elemProps}, Element, model) => {
50
+ return (
51
+ <StyledTabItem
52
+ type="button"
53
+ as={Element}
54
+ variant={model.state.variant}
55
+ {...mergeStyles(elemProps, tabsOverflowButtonStencil())}
56
+ >
57
+ <span>{children}</span>
58
+ <SystemIcon data-part="tabs-overflow-button-icon" icon={chevronDownSmallIcon} />
59
+ </StyledTabItem>
60
+ );
61
+ });
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+
3
+ import {ListRenderItemContext} from '@workday/canvas-kit-react/collection';
4
+ import {
5
+ ExtractProps,
6
+ createElemPropsHook,
7
+ createSubcomponent,
8
+ slugify,
9
+ useMountLayout,
10
+ } from '@workday/canvas-kit-react/common';
11
+ import {Box} from '@workday/canvas-kit-react/layout';
12
+
13
+ import {useTabsModel} from './useTabsModel';
14
+
15
+ export interface TabPanelProps extends ExtractProps<typeof Box, never> {
16
+ /**
17
+ * The contents of the TabPanel.
18
+ */
19
+ children: React.ReactNode;
20
+ /**
21
+ * The identifier of the tab. This identifier will be used in change events and for `initialTab`.
22
+ * Must match the `data-id` of the associated tab item. If this property is not provided, it will
23
+ * default to a string representation of the zero-based index of the Tab when it was
24
+ * initialized.
25
+ */
26
+ 'data-id'?: string;
27
+ /**
28
+ * Part of the ARIA specification for tabs. By default, all `tabpanel` elements have a `tabIndex`
29
+ * of `0` which makes the whole content area receive focus. If you have a focusable item near the
30
+ * top of the tab panel content area, you may set `tabIndex` to `undefined` to prevent the tab
31
+ * panel element from receiving focus. Only do this if a child of the tab panel can receive focus.
32
+ */
33
+ tabIndex?: number;
34
+ }
35
+
36
+ export const useTabsPanel = createElemPropsHook(useTabsModel)((
37
+ {state, events},
38
+ _,
39
+ elemProps: {'data-id'?: string} = {}
40
+ ) => {
41
+ const {item} = React.useContext(ListRenderItemContext);
42
+ const [localId, setLocalId] = React.useState(elemProps['data-id'] || item?.id || '');
43
+
44
+ useMountLayout(() => {
45
+ if (item) {
46
+ return;
47
+ }
48
+ const defaultId = state.panelIndexRef.current;
49
+ const itemId = localId || String(defaultId);
50
+ events.registerPanel({id: itemId, textValue: ''});
51
+ setLocalId(itemId);
52
+
53
+ return () => {
54
+ events.unregisterPanel({id: itemId});
55
+ };
56
+ });
57
+
58
+ return {
59
+ role: 'tabpanel' as const,
60
+ 'aria-labelledby': slugify(`${state.id}-${localId}`),
61
+ hidden: !!localId && localId !== state.selectedIds[0],
62
+ id: slugify(`tabpanel-${state.id}-${localId}`),
63
+ tabIndex: 0 as const,
64
+ };
65
+ });
66
+
67
+ export const TabsPanel = createSubcomponent('div')({
68
+ displayName: 'Tabs.Panel',
69
+ modelHook: useTabsModel,
70
+ elemPropsHook: useTabsPanel,
71
+ })<TabPanelProps>(({children, ...elemProps}, Element) => {
72
+ return (
73
+ <Box as={Element} {...elemProps}>
74
+ {children}
75
+ </Box>
76
+ );
77
+ });
@@ -0,0 +1,20 @@
1
+ import * as React from 'react';
2
+
3
+ import {useListRenderItems} from '@workday/canvas-kit-react/collection';
4
+ import {createSubcomponent} from '@workday/canvas-kit-react/common';
5
+
6
+ import {useTabsModel} from './useTabsModel';
7
+
8
+ export interface TabsPanelsProps<T = any> {
9
+ /**
10
+ *
11
+ */
12
+ children: ((item: T) => React.ReactNode) | React.ReactNode;
13
+ }
14
+
15
+ export const TabsPanels = createSubcomponent()({
16
+ displayName: 'Tabs.Panels',
17
+ modelHook: useTabsModel,
18
+ })<TabsPanelsProps>(({children}, _, model) => {
19
+ return <>{useListRenderItems(model, children)}</>;
20
+ });
@@ -0,0 +1,141 @@
1
+ import React from 'react';
2
+
3
+ import {
4
+ defaultGetId,
5
+ useListModel,
6
+ useOverflowListModel,
7
+ } from '@workday/canvas-kit-react/collection';
8
+ import {createModelHook, useModalityType} from '@workday/canvas-kit-react/common';
9
+ import {useMenuModel} from '@workday/canvas-kit-react/menu';
10
+
11
+ /**
12
+ * The TabsModel extends the [Collection
13
+ * System](/get-started/for-developers/guides/collection-api/). Tabs have tab items and
14
+ * panels. Tabs can be overflowed if there isn't enough room to render and will overflow to a
15
+ * {@link MenuModel} sub-model.
16
+ *
17
+ * ```tsx
18
+ * const model = useTabsModel({
19
+ * onSelect(data) {
20
+ * console.log('Selected Tab', data)
21
+ * }
22
+ * })
23
+ *
24
+ * <Tabs model={model}>{Tabs child components}</Tabs>
25
+ * ```
26
+ */
27
+ export const useTabsModel = createModelHook({
28
+ defaultConfig: {
29
+ ...useOverflowListModel.defaultConfig,
30
+ /**
31
+ * Optional id for the whole `Tabs` group. The `aria-controls` of the `Tab.Item` and `id` of the
32
+ * `Tab.Panel` will automatically be derived from this id. If not provided, a unique id will be
33
+ * created.
34
+ * @default useUniqueId()
35
+ */
36
+ id: '',
37
+ /**
38
+ * An initially selected tab. This value must match the `name` of the `Tab.Item` component. If
39
+ * not provided, the first tab will be selected.
40
+ */
41
+ initialTab: '',
42
+ /**
43
+ * The default Tabs sub-components only handle rendering of tabs in a horizontal orientation, but
44
+ * the sub-components could be replaced to handle vertical orientations.
45
+ * @default 'horizontal'
46
+ */
47
+ orientation: 'horizontal' as typeof useOverflowListModel.defaultConfig.orientation,
48
+ /**
49
+ * The variant of the Tabs.
50
+ * @default 'filled'
51
+ */
52
+ variant: 'filled' as 'filled' | 'outlined',
53
+ menuConfig: {} as typeof useMenuModel.TConfig,
54
+ },
55
+ requiredConfig: useOverflowListModel.requiredConfig,
56
+ contextOverride: useOverflowListModel.Context,
57
+ })(config => {
58
+ const initialSelectedRef = React.useRef(config.initialTab);
59
+ const getId = config.getId || defaultGetId;
60
+ const modality = useModalityType();
61
+
62
+ const model = useOverflowListModel(
63
+ useOverflowListModel.mergeConfig(config, {
64
+ shouldCalculateOverflow: modality !== 'touch',
65
+ orientation: config.orientation || 'horizontal',
66
+ onRegisterItem(data) {
67
+ if (!initialSelectedRef.current) {
68
+ initialSelectedRef.current = data.id;
69
+ events.select({id: initialSelectedRef.current});
70
+ }
71
+ },
72
+ initialSelectedIds: config.initialTab
73
+ ? [config.initialTab]
74
+ : config.items?.length
75
+ ? [getId(config.items![0])]
76
+ : [],
77
+ shouldVirtualize: false,
78
+ })
79
+ );
80
+
81
+ const panels = useListModel();
82
+
83
+ const state = {
84
+ ...model.state,
85
+ getId,
86
+ orientation: config.orientation || 'horizontal',
87
+ /**
88
+ * A list of panels. Uses `ListModel`
89
+ */
90
+ panels: panels.state.items,
91
+ /**
92
+ * A React.Ref of the current item index. A ref is used to allow for updating outside the normal
93
+ * React state cycle to ensure accurate index tracking as items are registered within the same
94
+ * state setting phase.
95
+ */
96
+ panelIndexRef: panels.state.indexRef,
97
+ variant: config.variant || 'filled',
98
+ };
99
+
100
+ const overflowItems = React.useMemo(
101
+ () =>
102
+ config.items ? config.items.filter(item => state.hiddenIds.includes(getId(item))) : undefined,
103
+ // eslint-disable-next-line react-hooks/exhaustive-deps
104
+ [state.hiddenIds, config.items]
105
+ );
106
+
107
+ const events = {
108
+ ...model.events,
109
+ /**
110
+ * This event registers panels with state.panels. Called when a panel is mounted.
111
+ */
112
+ registerPanel: panels.events.registerItem,
113
+ /**
114
+ * This event unregisters panels with state.panels. Called when a panel is unmounted.
115
+ */
116
+ unregisterPanel: panels.events.unregisterItem,
117
+ };
118
+
119
+ const menu = useMenuModel(
120
+ useMenuModel.mergeConfig(config.menuConfig, {
121
+ id: `menu-${model.state.id}`,
122
+ items: overflowItems,
123
+ nonInteractiveIds: state.nonInteractiveIds.filter(key => !state.hiddenIds.includes(key)),
124
+ onSelect(data) {
125
+ menu.events.hide();
126
+ events.select(data);
127
+ },
128
+ onShow() {
129
+ // Always select the first item when the menu is opened
130
+ menu.events.goToFirst();
131
+ },
132
+ })
133
+ );
134
+
135
+ return {
136
+ ...model,
137
+ state,
138
+ events,
139
+ menu,
140
+ };
141
+ });
@@ -0,0 +1,6 @@
1
+ {
2
+ "main": "../dist/commonjs/tabs",
3
+ "module": "../dist/es6/tabs",
4
+ "sideEffects": false,
5
+ "types": "../dist/es6/tabs"
6
+ }