@workday/canvas-kit-preview-react 16.0.0-alpha.0474-next.0 → 16.0.0-alpha.0475-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.
- package/dist/commonjs/index.d.ts +1 -0
- package/dist/commonjs/index.d.ts.map +1 -1
- package/dist/commonjs/index.js +1 -0
- package/dist/commonjs/tabs/index.d.ts +7 -0
- package/dist/commonjs/tabs/index.d.ts.map +1 -0
- package/dist/commonjs/tabs/index.js +22 -0
- package/dist/commonjs/tabs/lib/Tabs.d.ts +9555 -0
- package/dist/commonjs/tabs/lib/Tabs.d.ts.map +1 -0
- package/dist/commonjs/tabs/lib/Tabs.js +127 -0
- package/dist/commonjs/tabs/lib/TabsItem.d.ts +719 -0
- package/dist/commonjs/tabs/lib/TabsItem.d.ts.map +1 -0
- package/dist/commonjs/tabs/lib/TabsItem.js +50 -0
- package/dist/commonjs/tabs/lib/TabsList.d.ts +998 -0
- package/dist/commonjs/tabs/lib/TabsList.d.ts.map +1 -0
- package/dist/commonjs/tabs/lib/TabsList.js +121 -0
- package/dist/commonjs/tabs/lib/TabsOverflowButton.d.ts +650 -0
- package/dist/commonjs/tabs/lib/TabsOverflowButton.d.ts.map +1 -0
- package/dist/commonjs/tabs/lib/TabsOverflowButton.js +28 -0
- package/dist/commonjs/tabs/lib/TabsPanel.d.ts +654 -0
- package/dist/commonjs/tabs/lib/TabsPanel.d.ts.map +1 -0
- package/dist/commonjs/tabs/lib/TabsPanel.js +42 -0
- package/dist/commonjs/tabs/lib/TabsPanels.d.ts +320 -0
- package/dist/commonjs/tabs/lib/TabsPanels.d.ts.map +1 -0
- package/dist/commonjs/tabs/lib/TabsPanels.js +13 -0
- package/dist/commonjs/tabs/lib/useTabsModel.d.ts +5924 -0
- package/dist/commonjs/tabs/lib/useTabsModel.d.ts.map +1 -0
- package/dist/commonjs/tabs/lib/useTabsModel.js +128 -0
- package/dist/es6/index.d.ts +1 -0
- package/dist/es6/index.d.ts.map +1 -1
- package/dist/es6/index.js +1 -0
- package/dist/es6/tabs/index.d.ts +7 -0
- package/dist/es6/tabs/index.d.ts.map +1 -0
- package/dist/es6/tabs/index.js +6 -0
- package/dist/es6/tabs/lib/Tabs.d.ts +9555 -0
- package/dist/es6/tabs/lib/Tabs.d.ts.map +1 -0
- package/dist/es6/tabs/lib/Tabs.js +124 -0
- package/dist/es6/tabs/lib/TabsItem.d.ts +719 -0
- package/dist/es6/tabs/lib/TabsItem.d.ts.map +1 -0
- package/dist/es6/tabs/lib/TabsItem.js +47 -0
- package/dist/es6/tabs/lib/TabsList.d.ts +998 -0
- package/dist/es6/tabs/lib/TabsList.d.ts.map +1 -0
- package/dist/es6/tabs/lib/TabsList.js +95 -0
- package/dist/es6/tabs/lib/TabsOverflowButton.d.ts +650 -0
- package/dist/es6/tabs/lib/TabsOverflowButton.d.ts.map +1 -0
- package/dist/es6/tabs/lib/TabsOverflowButton.js +25 -0
- package/dist/es6/tabs/lib/TabsPanel.d.ts +654 -0
- package/dist/es6/tabs/lib/TabsPanel.d.ts.map +1 -0
- package/dist/es6/tabs/lib/TabsPanel.js +36 -0
- package/dist/es6/tabs/lib/TabsPanels.d.ts +320 -0
- package/dist/es6/tabs/lib/TabsPanels.d.ts.map +1 -0
- package/dist/es6/tabs/lib/TabsPanels.js +10 -0
- package/dist/es6/tabs/lib/useTabsModel.d.ts +5924 -0
- package/dist/es6/tabs/lib/useTabsModel.d.ts.map +1 -0
- package/dist/es6/tabs/lib/useTabsModel.js +122 -0
- package/index.ts +1 -0
- package/package.json +4 -4
- package/tabs/index.ts +7 -0
- package/tabs/lib/Tabs.tsx +135 -0
- package/tabs/lib/TabsItem.tsx +274 -0
- package/tabs/lib/TabsList.tsx +181 -0
- package/tabs/lib/TabsOverflowButton.tsx +61 -0
- package/tabs/lib/TabsPanel.tsx +77 -0
- package/tabs/lib/TabsPanels.tsx +20 -0
- package/tabs/lib/useTabsModel.tsx +141 -0
- 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
|
+
});
|