@ringcentral/spring-ui 1.12.0 → 1.13.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/Accordion.js +90 -34
- package/AccordionHeader.js +71 -21
- package/Alert.js +120 -28
- package/Animation.js +2 -46
- package/Announcement.js +46 -35
- package/Autocomplete.js +1011 -583
- package/Avatar.js +134 -73
- package/AvatarGroup.js +50 -7
- package/Backdrop.js +45 -7
- package/Badge.js +77 -15
- package/Block.js +34 -12
- package/BlockHeader.js +57 -15
- package/BottomNavigation.js +181 -65
- package/BottomNavigationItem.js +93 -30
- package/Button.js +160 -24
- package/CallButton.js +53 -31
- package/Checkbox.js +88 -26
- package/Chip.js +126 -26
- package/CircularProgressIndicator.js +80 -8
- package/ClickAwayListener.js +4 -1
- package/ComponentsProps.js +1 -0
- package/DatePicker.js +611 -192
- package/DateUtilsContext.js +43 -25
- package/Dialer.js +564 -253
- package/Dialog.js +99 -45
- package/Divider.js +43 -7
- package/DragHandle.js +32 -6
- package/Drawer.js +117 -19
- package/Dropdown.js +154 -77
- package/EmptyState.js +65 -15
- package/FabButton.js +51 -17
- package/FocusTrap.js +109 -12
- package/FormField.js +108 -12
- package/FormLabel.js +81 -7
- package/Icon.js +1 -8
- package/IconButton.js +142 -26
- package/IconButtonGroup.js +35 -12
- package/InlineEditable.js +98 -44
- package/LinearProgressIndicator.js +73 -24
- package/Link.js +40 -11
- package/List.js +54 -9
- package/ListItem.js +152 -23
- package/ListItemText.js +44 -17
- package/ListNavigation.js +272 -20
- package/Menu.js +139 -23
- package/MenuActions.js +37 -12
- package/MenuDivider.js +34 -8
- package/MenuFooter.js +36 -12
- package/MenuHeader.js +54 -16
- package/MenuItem.js +402 -35
- package/MenuItemText.js +51 -9
- package/MenuList.js +87 -36
- package/Modal.js +345 -16
- package/NestedMenuContext.js +183 -11
- package/Option.js +32 -36
- package/Overlap.js +54 -7
- package/PageHeader.js +62 -24
- package/PickerTextField.js +156 -35
- package/Popover.js +262 -21
- package/Popper.js +112 -62
- package/PopperPaper.js +54 -8
- package/Portal.js +6 -1
- package/Radio.js +65 -21
- package/RadioGroup.js +56 -17
- package/Rating.js +223 -87
- package/Select.js +387 -44
- package/SingleFilter.js +148 -79
- package/Skeleton.js +68 -17
- package/Slider.js +167 -69
- package/Snackbar.js +122 -59
- package/SortableList.js +32 -12
- package/SortableListDndContext.js +69 -22
- package/SortableListItem.js +92 -47
- package/SortableVirtualizedList.js +44 -20
- package/SplitButton.js +124 -89
- package/Squircle.js +4 -7
- package/StatusIndicator.js +51 -9
- package/Step.js +63 -38
- package/StepButton.js +51 -23
- package/StepConnector.js +48 -8
- package/StepContent.js +67 -24
- package/StepIcon.js +90 -9
- package/StepLabel.js +90 -14
- package/Stepper.js +65 -23
- package/SubMenu.js +203 -101
- package/SwipeableDrawer.js +439 -194
- package/Switch.js +70 -23
- package/Tab.js +120 -57
- package/TabContext.js +59 -15
- package/TabPanel.js +40 -13
- package/Table.js +60 -17
- package/TableBody.js +42 -12
- package/TableCell.js +84 -50
- package/TableContainer.js +32 -10
- package/TableFooter.js +38 -10
- package/TableHead.js +49 -15
- package/TablePagination.js +153 -79
- package/TableRow.js +60 -21
- package/Tabs.js +592 -271
- package/Tag.js +51 -16
- package/Text.js +68 -37
- package/TextField.js +209 -31
- package/Textarea.js +51 -42
- package/TimePicker.js +490 -232
- package/Tooltip.js +48 -23
- package/Tray.js +237 -13
- package/TrayMenu.js +62 -43
- package/VirtualizedList.js +205 -107
- package/VirtualizedMenu.js +359 -29
- package/VisuallyHidden.js +29 -6
- package/chunks/1250.js +98 -0
- package/chunks/1294.js +126 -0
- package/chunks/2956.js +18 -0
- package/chunks/4476.js +460 -0
- package/chunks/4491.js +10 -0
- package/chunks/4535.js +364 -0
- package/chunks/5047.js +3 -0
- package/chunks/6335.js +24 -0
- package/chunks/7279.js +10 -0
- package/chunks/8011.js +8 -0
- package/chunks/8307.js +56 -0
- package/chunks/8467.js +8 -0
- package/chunks/8809.js +8 -0
- package/chunks/{RadioGroup.context.js → 9551.js} +12 -6
- package/chunks/987.js +8 -0
- package/index.css +2436 -2261
- package/index.js +245 -195
- package/internal/utils/setRef.d.ts +2 -1
- package/package.json +4 -4
- package/src/Animation/hooks/useMotionSafeTransition/useMotionSafeTransition.d.ts +2 -0
- package/src/Autocomplete/SuggestionList/SuggestionList.d.ts +1 -1
- package/src/Button/Button.classes.d.ts +1 -1
- package/src/ComponentsProps.d.ts +111 -110
- package/src/DatePicker/DatePickerDataAttributes.d.ts +11 -0
- package/src/DatePicker/utils/DatePickerUtils.d.ts +1 -1
- package/src/MenuItem/MenuItem.type.d.ts +1 -0
- package/src/MenuList/MenuListContext.d.ts +2 -3
- package/src/Modal/Modal.type.d.ts +1 -0
- package/src/Popover/Popover.classes.d.ts +1 -1
- package/src/Popover/Popover.internal.d.ts +8 -0
- package/src/Popover/Popover.type.d.ts +1 -0
- package/src/Select/Select.type.d.ts +6 -4
- package/src/Table/Table.type.d.ts +3 -0
- package/src/Table/TableContext.d.ts +3 -0
- package/src/Table/TableRowContext.d.ts +7 -0
- package/src/Table/index.d.ts +1 -0
- package/src/TableCell/TableCell.classes.d.ts +1 -1
- package/src/TableFooter/TableFooter.classes.d.ts +1 -1
- package/src/TableFooter/TableFooter.type.d.ts +1 -0
- package/src/TableHead/TableHead.classes.d.ts +1 -1
- package/src/TableHead/TableHead.type.d.ts +1 -0
- package/src/TableRow/TableRow.type.d.ts +2 -0
- package/chunks/AccordionContext.js +0 -6
- package/chunks/Alert.js +0 -48
- package/chunks/AvatarGroup.js +0 -22
- package/chunks/Backdrop.js +0 -18
- package/chunks/Badge.js +0 -32
- package/chunks/BottomNavigationItem.js +0 -36
- package/chunks/Button.js +0 -88
- package/chunks/Chip.js +0 -73
- package/chunks/CircularProgressIndicator.js +0 -36
- package/chunks/Divider.js +0 -19
- package/chunks/DragHandle.js +0 -11
- package/chunks/Drawer.js +0 -33
- package/chunks/ExpandCollapseCaret.js +0 -24
- package/chunks/FormLabel.js +0 -27
- package/chunks/Icon.js +0 -25
- package/chunks/IconButtonLabel.js +0 -68
- package/chunks/List.js +0 -22
- package/chunks/ListItem.js +0 -83
- package/chunks/Menu.js +0 -61
- package/chunks/MenuDivider.js +0 -14
- package/chunks/MenuItemText.js +0 -18
- package/chunks/MenuList.js +0 -47
- package/chunks/Modal.js +0 -211
- package/chunks/Overlap.js +0 -25
- package/chunks/PickerTextField.js +0 -85
- package/chunks/Popover.js +0 -61
- package/chunks/PopperPaper.js +0 -15
- package/chunks/Select.js +0 -225
- package/chunks/ShowHideEye.js +0 -56
- package/chunks/SlideIn.js +0 -20
- package/chunks/SortableArrow.js +0 -73
- package/chunks/Squircle.js +0 -31
- package/chunks/StatusIndicator.js +0 -20
- package/chunks/StepConnector.js +0 -21
- package/chunks/StepIcon.js +0 -43
- package/chunks/StepLabel.js +0 -29
- package/chunks/StepperContext.js +0 -9
- package/chunks/TableContext.js +0 -6
- package/chunks/Tablelvl2Context.js +0 -6
- package/chunks/TabsContext.js +0 -6
- package/chunks/TextField.js +0 -110
- package/chunks/Tooltip.js +0 -217
- package/chunks/VirtualizedMenu.js +0 -162
- package/chunks/VisuallyHidden.js +0 -12
- package/chunks/composeEventHandlers.js +0 -10
- package/chunks/getFade.js +0 -27
- package/chunks/getGrow.js +0 -70
- package/chunks/getSlideIn.js +0 -30
- package/chunks/hasContent.js +0 -3
- package/chunks/useAnimationFrame.js +0 -15
- package/chunks/useFocusTrapStack.js +0 -38
- package/chunks/useHelperTextId.js +0 -24
- package/chunks/useListNavigation.js +0 -199
- package/chunks/useMenuItemSelect.js +0 -282
- package/chunks/useMotionSafeTransition.js +0 -9
- package/chunks/useNestedMenuItem.js +0 -27
- package/chunks/useNestedParentMenu.js +0 -113
- package/chunks/usePrefersReducedMotion.js +0 -36
- package/chunks/useTooltipForceHide.js +0 -27
- package/chunks/useTrayNavigate.js +0 -150
- package/chunks/withTooltip.js +0 -15
package/Tabs.js
CHANGED
|
@@ -1,89 +1,92 @@
|
|
|
1
|
-
import { jsx, jsxs
|
|
2
|
-
import
|
|
3
|
-
import { useTab, useTabsList
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
import './chunks/List.js';
|
|
46
|
-
import './chunks/hasContent.js';
|
|
47
|
-
import 'clsx';
|
|
48
|
-
|
|
49
|
-
const TabMenuItem = forwardRef((props, ref) => {
|
|
50
|
-
const { label, component: MenuItemComponent = MenuItem, onKeyDown } = props;
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import react, { Children, cloneElement, forwardRef, isValidElement, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { TabsListProvider, useTab, useTabsList } from "@mui/base";
|
|
4
|
+
import { combineClasses, combineProps, getTitle, logInDev, useClasses, useEventCallback, useFontLoaded, useForkRef, useOnReRender, usePrevious, useWindowContext } from "@ringcentral/spring-base";
|
|
5
|
+
import { CaretLeftMd, CaretRightMd, OverflowMd } from "@ringcentral/spring-icon";
|
|
6
|
+
import { motionTransition, useClassesWithMode, useMode, useThemeProps } from "@ringcentral/spring-theme";
|
|
7
|
+
import { animate, motion, useMotionValue } from "framer-motion";
|
|
8
|
+
import isEqual from "lodash/isEqual";
|
|
9
|
+
import throttle from "lodash/throttle";
|
|
10
|
+
import { Badge as Badge_Badge } from "./Badge.js";
|
|
11
|
+
import { IconButton as IconButton_IconButton } from "./IconButton.js";
|
|
12
|
+
import { Menu as Menu_Menu } from "./Menu.js";
|
|
13
|
+
import { MenuItem as MenuItem_MenuItem } from "./MenuItem.js";
|
|
14
|
+
import { MenuItemText as MenuItemText_MenuItemText } from "./MenuItemText.js";
|
|
15
|
+
import { MenuList as MenuList_MenuList } from "./MenuList.js";
|
|
16
|
+
import "./Tooltip.js";
|
|
17
|
+
import { TabsContext, useTabsContext } from "./chunks/8809.js";
|
|
18
|
+
import "./chunks/4476.js";
|
|
19
|
+
import { useInternalTabsContext, generateTabStableIds } from "./TabContext.js";
|
|
20
|
+
import { Tooltip } from "./chunks/4535.js";
|
|
21
|
+
import { useMotionSafeTransition, usePrefersReducedMotion } from "./chunks/1294.js";
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
const TabMenuItem = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
44
|
+
const { label, component: MenuItemComponent = MenuItem_MenuItem, onKeyDown } = props;
|
|
51
45
|
const { onChange, ...rest } = props;
|
|
52
46
|
const { getRootProps } = useTab({
|
|
53
47
|
...rest,
|
|
54
|
-
rootRef: ref
|
|
48
|
+
rootRef: ref
|
|
55
49
|
});
|
|
56
|
-
return
|
|
50
|
+
return(// @ts-ignore
|
|
51
|
+
/*#__PURE__*/ jsx(MenuItemComponent, {
|
|
52
|
+
...props,
|
|
53
|
+
...getRootProps(),
|
|
54
|
+
onKeyDown: onKeyDown,
|
|
55
|
+
children: /*#__PURE__*/ jsx(MenuItemText_MenuItemText, {
|
|
56
|
+
children: label
|
|
57
|
+
})
|
|
58
|
+
}));
|
|
57
59
|
});
|
|
58
|
-
const TabMoreMenu = forwardRef((props, ref)
|
|
60
|
+
const TabMoreMenu = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
59
61
|
const { classes, className, menuItems, TooltipProps, BadgeProps, ButtonProps, MenuProps, MenuItemComponent, ...rest } = props;
|
|
60
62
|
const [anchor, setAnchor] = useState(null);
|
|
61
63
|
const { mode } = useMode();
|
|
62
64
|
const cls = useClasses('tab-more-menu', classes, className);
|
|
63
65
|
const { pill } = useTabsContext();
|
|
64
|
-
const iconButtonProps = useMemo(()
|
|
65
|
-
? {
|
|
66
|
+
const iconButtonProps = useMemo(()=>pill ? {
|
|
66
67
|
shape: 'circular',
|
|
67
68
|
variant: 'outlined',
|
|
68
69
|
size: mode === 'compact' ? 'medium' : 'large',
|
|
69
|
-
background: true
|
|
70
|
-
}
|
|
71
|
-
: {
|
|
70
|
+
background: true
|
|
71
|
+
} : {
|
|
72
72
|
shape: 'squircle',
|
|
73
73
|
variant: 'contained',
|
|
74
74
|
size: 'small',
|
|
75
|
-
background: false
|
|
76
|
-
}
|
|
75
|
+
background: false
|
|
76
|
+
}, [
|
|
77
|
+
pill,
|
|
78
|
+
mode
|
|
79
|
+
]);
|
|
77
80
|
let iconButtonClasses = {
|
|
78
|
-
root: cls('button')
|
|
81
|
+
root: cls('button')
|
|
79
82
|
};
|
|
80
83
|
if (ButtonProps?.classes) {
|
|
81
84
|
iconButtonClasses = combineClasses(iconButtonClasses, ButtonProps.classes);
|
|
82
85
|
}
|
|
83
86
|
let badgeClasses = {
|
|
84
87
|
root: cls('badge-root', {
|
|
85
|
-
'pill-badge': pill
|
|
86
|
-
})
|
|
88
|
+
'pill-badge': pill
|
|
89
|
+
})
|
|
87
90
|
};
|
|
88
91
|
if (BadgeProps?.classes) {
|
|
89
92
|
badgeClasses = combineClasses(badgeClasses, BadgeProps.classes);
|
|
@@ -92,73 +95,173 @@ const TabMoreMenu = forwardRef((props, ref) => {
|
|
|
92
95
|
color: 'secondary',
|
|
93
96
|
variant: pill ? 'outlined' : 'contained',
|
|
94
97
|
...BadgeProps,
|
|
95
|
-
classes: badgeClasses
|
|
98
|
+
classes: badgeClasses
|
|
96
99
|
};
|
|
97
|
-
const handleClose = (event, reason)
|
|
100
|
+
const handleClose = (event, reason)=>{
|
|
98
101
|
setAnchor(null);
|
|
99
102
|
};
|
|
100
|
-
const iconButton =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
const iconButton = /*#__PURE__*/ jsx(IconButton_IconButton, {
|
|
104
|
+
color: 'secondary',
|
|
105
|
+
symbol: OverflowMd,
|
|
106
|
+
...iconButtonProps,
|
|
107
|
+
onClick: (e)=>{
|
|
108
|
+
setAnchor((prev)=>prev ? null : e.currentTarget);
|
|
109
|
+
},
|
|
110
|
+
"aria-expanded": Boolean(anchor),
|
|
111
|
+
"aria-label": getTitle(TooltipProps?.title),
|
|
112
|
+
...ButtonProps,
|
|
113
|
+
classes: iconButtonClasses
|
|
114
|
+
});
|
|
115
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
116
|
+
children: [
|
|
117
|
+
/*#__PURE__*/ jsxs("div", {
|
|
118
|
+
className: cls('root'),
|
|
119
|
+
ref: ref,
|
|
120
|
+
...rest,
|
|
121
|
+
children: [
|
|
122
|
+
TooltipProps ? /*#__PURE__*/ jsx(Tooltip, {
|
|
123
|
+
...TooltipProps,
|
|
124
|
+
children: iconButton
|
|
125
|
+
}) : iconButton,
|
|
126
|
+
Number(BadgeProps?.count) > 0 ? /*#__PURE__*/ jsx(Badge_Badge, {
|
|
127
|
+
...badgeProps
|
|
128
|
+
}) : null
|
|
129
|
+
]
|
|
130
|
+
}),
|
|
131
|
+
/*#__PURE__*/ jsx(Menu_Menu, {
|
|
132
|
+
open: Boolean(anchor),
|
|
133
|
+
anchorEl: anchor,
|
|
134
|
+
...combineProps({
|
|
135
|
+
onClose: handleClose
|
|
136
|
+
}, MenuProps),
|
|
137
|
+
children: /*#__PURE__*/ jsx(MenuList_MenuList, {
|
|
138
|
+
children: menuItems.map(({ ref, ...props }, index)=>/*#__PURE__*/ jsx(TabMenuItem, {
|
|
139
|
+
...props,
|
|
140
|
+
ref: ref,
|
|
141
|
+
component: MenuItemComponent
|
|
142
|
+
}, props.value || `TabMenuItem-${index}`))
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
]
|
|
146
|
+
});
|
|
104
147
|
});
|
|
105
148
|
TabMoreMenu.displayName = 'SuiTabMoreMenu';
|
|
106
149
|
|
|
107
|
-
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
const TabScrollButton = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
108
163
|
const { classes, className, ButtonProps, direction = 'left', onClick, ...rest } = props;
|
|
109
164
|
const { mode } = useMode();
|
|
110
165
|
const cls = useClasses('tab-scroll-button', classes, className);
|
|
111
|
-
const iconButtonClasses = useMemo(()
|
|
166
|
+
const iconButtonClasses = useMemo(()=>{
|
|
112
167
|
let combinedClasses = {
|
|
113
|
-
root: cls('button')
|
|
168
|
+
root: cls('button')
|
|
114
169
|
};
|
|
115
170
|
if (ButtonProps?.classes) {
|
|
116
171
|
combinedClasses = combineClasses(combinedClasses, ButtonProps.classes);
|
|
117
172
|
}
|
|
118
173
|
return combinedClasses;
|
|
119
|
-
}, [
|
|
174
|
+
}, [
|
|
175
|
+
ButtonProps?.classes,
|
|
176
|
+
cls
|
|
177
|
+
]);
|
|
120
178
|
const icon = direction === 'left' ? CaretLeftMd : CaretRightMd;
|
|
121
|
-
const iconButtonSize = useMemo(()
|
|
179
|
+
const iconButtonSize = useMemo(()=>{
|
|
122
180
|
return mode === 'compact' ? 'medium' : 'large';
|
|
123
|
-
}, [
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
181
|
+
}, [
|
|
182
|
+
mode
|
|
183
|
+
]);
|
|
184
|
+
return /*#__PURE__*/ jsx("div", {
|
|
185
|
+
className: cls('root', {
|
|
186
|
+
start: direction === 'left'
|
|
187
|
+
}),
|
|
188
|
+
ref: ref,
|
|
189
|
+
...rest,
|
|
190
|
+
children: /*#__PURE__*/ jsx(IconButton_IconButton, {
|
|
191
|
+
"aria-hidden": "true",
|
|
192
|
+
role: "presentation",
|
|
193
|
+
tabIndex: -1,
|
|
194
|
+
component: "div",
|
|
195
|
+
color: "secondary",
|
|
196
|
+
symbol: icon,
|
|
197
|
+
shape: "circular",
|
|
198
|
+
variant: "outlined",
|
|
199
|
+
size: iconButtonSize,
|
|
200
|
+
background: true,
|
|
201
|
+
onClick: onClick,
|
|
202
|
+
...ButtonProps,
|
|
203
|
+
classes: iconButtonClasses
|
|
204
|
+
})
|
|
205
|
+
});
|
|
127
206
|
});
|
|
128
207
|
TabScrollButton.displayName = 'SuiTabScrollButton';
|
|
129
208
|
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
/* oxlint-disable sonarjs/cognitive-complexity */
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
130
230
|
function useComputedTabs(props) {
|
|
131
|
-
const { variant, value, children, pill, pillGap, onGroupInfoUpdate, floatingUnderlineLeftMotionValue, floatingUnderlineWidthMotionValue, tabListRef, scrollerRef, moreRef
|
|
231
|
+
const { variant, value, children, pill, pillGap, onGroupInfoUpdate, floatingUnderlineLeftMotionValue, floatingUnderlineWidthMotionValue, tabListRef, scrollerRef, moreRef } = props;
|
|
132
232
|
const fontsLoaded = useFontLoaded(variant === 'moreMenu');
|
|
133
233
|
const prefersReducedMotion = usePrefersReducedMotion();
|
|
134
234
|
const { externalWindow } = useWindowContext();
|
|
135
235
|
const ResizeObserverClass = (externalWindow || window).ResizeObserver;
|
|
136
236
|
const [moreMenuItems, setMoreMenuItems] = useState([]);
|
|
237
|
+
// Render the more button initially to get the offsetWidth to compute
|
|
137
238
|
const [showMore, setShowMore] = useState(variant === 'moreMenu');
|
|
138
239
|
const moreBtnWidth = useRef(0);
|
|
139
240
|
const switchToMoreMenu = useRef(variant === 'moreMenu');
|
|
241
|
+
// Store a ref to visibleTabs and hiddenTabs for data comparison
|
|
140
242
|
const visibleTabsRef = useRef();
|
|
141
243
|
const hiddenTabsRef = useRef();
|
|
142
244
|
const shouldUpdate = useRef(false);
|
|
245
|
+
// Cache children info to reduce render times when computing more menu
|
|
143
246
|
const tabsChildrenCache = useRef();
|
|
144
247
|
const { tabsInstanceId } = useInternalTabsContext();
|
|
145
|
-
const processedChildren = useMemo(()
|
|
248
|
+
const processedChildren = useMemo(()=>{
|
|
146
249
|
let childIndex = 0;
|
|
147
250
|
const visibleTabs = new Map();
|
|
148
|
-
Children.forEach(children, (child)
|
|
251
|
+
Children.forEach(children, (child)=>{
|
|
149
252
|
if (isValidElement(child)) {
|
|
150
|
-
if (process.env.NODE_ENV !== 'production' &&
|
|
151
|
-
child.type === React.Fragment) {
|
|
253
|
+
if (process.env.NODE_ENV !== 'production' && child.type === react.Fragment) {
|
|
152
254
|
logInDev({
|
|
153
255
|
scope: 'Tabs',
|
|
154
256
|
level: 'error',
|
|
155
257
|
message: [
|
|
156
258
|
"The Tabs component doesn't accept a Fragment as a child.",
|
|
157
|
-
'Consider providing an array instead.'
|
|
158
|
-
].join('\n')
|
|
259
|
+
'Consider providing an array instead.'
|
|
260
|
+
].join('\n')
|
|
159
261
|
});
|
|
160
262
|
}
|
|
161
263
|
const childValue = child.props.value === undefined ? childIndex : child.props.value;
|
|
264
|
+
// Only clone element when necessary: no user-provided id
|
|
162
265
|
let finalChild = child;
|
|
163
266
|
if (child.props.id === undefined) {
|
|
164
267
|
if (process.env.NODE_ENV !== 'production') {
|
|
@@ -174,169 +277,214 @@ function useComputedTabs(props) {
|
|
|
174
277
|
`<Tab id="unique-tab-id" label="Your label" />`,
|
|
175
278
|
'',
|
|
176
279
|
'Unique ids are required for accessibility and correct tab-panel linking.',
|
|
177
|
-
'If no id is provided, each Tab element will be cloned to provide its auto-generated id, which may have a performance cost.'
|
|
178
|
-
].join('\n')
|
|
280
|
+
'If no id is provided, each Tab element will be cloned to provide its auto-generated id, which may have a performance cost.'
|
|
281
|
+
].join('\n')
|
|
179
282
|
});
|
|
180
283
|
}
|
|
181
284
|
const { tabId } = generateTabStableIds(tabsInstanceId, childValue);
|
|
182
285
|
finalChild = cloneElement(child, {
|
|
183
286
|
...child.props,
|
|
184
|
-
id: tabId
|
|
287
|
+
id: tabId
|
|
185
288
|
});
|
|
186
289
|
}
|
|
187
|
-
visibleTabs.set(childValue, {
|
|
290
|
+
visibleTabs.set(childValue, {
|
|
291
|
+
childIndex,
|
|
292
|
+
child: finalChild
|
|
293
|
+
});
|
|
188
294
|
childIndex += 1;
|
|
189
295
|
}
|
|
190
296
|
});
|
|
191
297
|
return visibleTabs;
|
|
192
|
-
}, [
|
|
298
|
+
}, [
|
|
299
|
+
children,
|
|
300
|
+
tabsInstanceId
|
|
301
|
+
]);
|
|
193
302
|
const [visibleTabs, setVisibleTabs] = useState(processedChildren);
|
|
194
|
-
const visibleTabsChildren = useMemo(()
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
303
|
+
const visibleTabsChildren = useMemo(()=>{
|
|
304
|
+
// Remapping Map → array makes these list children; React requires a key.
|
|
305
|
+
return Array.from(visibleTabs.entries()).map(([childValue, { child }])=>({
|
|
306
|
+
...child,
|
|
307
|
+
key: child.key ?? childValue,
|
|
308
|
+
props: {
|
|
309
|
+
...child.props,
|
|
310
|
+
value: childValue
|
|
311
|
+
}
|
|
312
|
+
}));
|
|
313
|
+
}, [
|
|
314
|
+
visibleTabs
|
|
315
|
+
]);
|
|
316
|
+
const prevChildren = usePrevious(()=>children);
|
|
317
|
+
const cacheTabsChildren = useEventCallback(()=>{
|
|
318
|
+
// If this is a full update clear the cache
|
|
319
|
+
if (shouldUpdate.current === 'full') tabsChildrenCache.current = new Map();
|
|
320
|
+
// We can't cache if the tabListRef is not available
|
|
321
|
+
if (!tabListRef.current?.children || !tabsChildrenCache.current) return;
|
|
207
322
|
const tabsChildren = tabListRef.current?.children;
|
|
208
|
-
for (const [childValue, { childIndex, child }] of visibleTabs.entries())
|
|
323
|
+
for (const [childValue, { childIndex, child }] of visibleTabs.entries()){
|
|
209
324
|
const childElement = tabsChildren[childIndex];
|
|
210
|
-
if (!child || !childElement)
|
|
211
|
-
return;
|
|
325
|
+
if (!child || !childElement) return;
|
|
212
326
|
tabsChildrenCache.current?.set(childValue, {
|
|
327
|
+
// @ts-ignore
|
|
213
328
|
child,
|
|
214
329
|
element: childElement,
|
|
215
|
-
width: childElement.offsetWidth
|
|
330
|
+
width: childElement.offsetWidth
|
|
216
331
|
});
|
|
217
332
|
}
|
|
218
333
|
});
|
|
219
|
-
const getSelectedTabElement = useEventCallback(()
|
|
334
|
+
const getSelectedTabElement = useEventCallback(()=>{
|
|
220
335
|
const children = tabListRef.current?.children;
|
|
221
|
-
if (!children || children.length === 0 || value === null)
|
|
222
|
-
return undefined;
|
|
336
|
+
if (!children || children.length === 0 || value === null) return undefined;
|
|
223
337
|
let selectedIndex = -1;
|
|
224
338
|
if (variant === 'moreMenu' && visibleTabsRef.current) {
|
|
225
|
-
|
|
339
|
+
/**
|
|
340
|
+
* When the variant is `moreMenu`, visibleTabs may have a different
|
|
341
|
+
* order than processed children as some tabs are hidden in the more menu.
|
|
342
|
+
*/ selectedIndex = Array.from(visibleTabsRef.current).findIndex(([key])=>{
|
|
226
343
|
return key === value;
|
|
227
344
|
});
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
345
|
+
} else {
|
|
230
346
|
selectedIndex = visibleTabs.get(value)?.childIndex;
|
|
231
347
|
}
|
|
232
348
|
const selectedTabElement = children[selectedIndex];
|
|
233
|
-
if (process.env.NODE_ENV !== 'production' &&
|
|
234
|
-
selectedTabElement === undefined) {
|
|
349
|
+
if (process.env.NODE_ENV !== 'production' && selectedTabElement === undefined) {
|
|
235
350
|
logInDev({
|
|
236
351
|
scope: 'Tabs',
|
|
237
352
|
message: [
|
|
238
353
|
`The \`value\` provided to the Tabs component is invalid.`,
|
|
239
|
-
`None of the Tabs' children match with "${value}"
|
|
240
|
-
].join('\n')
|
|
354
|
+
`None of the Tabs' children match with "${value}".`
|
|
355
|
+
].join('\n')
|
|
241
356
|
});
|
|
242
357
|
}
|
|
358
|
+
// provide the first tab as default selected tab when value is invalid
|
|
243
359
|
return selectedTabElement || children[0];
|
|
244
360
|
});
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const width = scrollerRef.current?.offsetWidth || 0;
|
|
249
|
-
const visibleWidth = width +
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const selectedItemWidth =
|
|
253
|
-
const spaceForMoreBtn = moreBtnWidth.current + pillGap;
|
|
361
|
+
// main logic for computing whether show more menu when variant is 'moreMenu'
|
|
362
|
+
const computeMoreMenuWithCache = useEventCallback(()=>{
|
|
363
|
+
if (variant !== 'moreMenu' || !tabsChildrenCache.current) return;
|
|
364
|
+
const width = scrollerRef.current?.offsetWidth || 0; // Includes padding
|
|
365
|
+
const visibleWidth = width + pillGap - (tabListRef.current ? getHorizontalPadding(tabListRef.current) : 0); // Excludes padding
|
|
366
|
+
// The reserved slot must include the gap, like every other tab slot below,
|
|
367
|
+
// otherwise the more button loses its left margin at the overflow threshold
|
|
368
|
+
const selectedItemWidth = value !== null ? (tabsChildrenCache.current.get(value)?.width ?? 0) + pillGap : 0;
|
|
369
|
+
const spaceForMoreBtn = moreBtnWidth.current + pillGap; // more button needs left margin
|
|
254
370
|
let consumedWidth = spaceForMoreBtn;
|
|
255
371
|
let isSelectedItemVisible = false;
|
|
256
372
|
let noWidthForUnselectedTab = false;
|
|
257
373
|
const visibleItems = new Map();
|
|
258
374
|
const hiddenItems = new Map();
|
|
259
375
|
const cache = Array.from(tabsChildrenCache.current);
|
|
260
|
-
for
|
|
376
|
+
for(let i = 0; i < cache.length; i++){
|
|
261
377
|
const [key, itemInfo] = cache[i];
|
|
262
378
|
const { child, width: itemWidth } = itemInfo;
|
|
263
379
|
const width = itemWidth + pillGap;
|
|
264
|
-
|
|
380
|
+
/**
|
|
381
|
+
* If there's not enough space to accommodate an unselected tab and
|
|
382
|
+
* the tab isn't selected we'll hide it, aka put it in the more menu.
|
|
383
|
+
*/ if (noWidthForUnselectedTab && key !== value) {
|
|
265
384
|
hiddenItems.set(key, child);
|
|
266
385
|
continue;
|
|
267
386
|
}
|
|
268
387
|
if (isSelectedItemVisible) {
|
|
388
|
+
// Check if this is the last tab in the list
|
|
269
389
|
if (cache.length === i + 1) {
|
|
390
|
+
// Check if there is enough space to accommodate the tab without the more button
|
|
270
391
|
if (visibleWidth >= consumedWidth - spaceForMoreBtn + width) {
|
|
392
|
+
// There's enough space available, show the last tab
|
|
271
393
|
visibleItems.set(key, child);
|
|
272
|
-
}
|
|
273
|
-
|
|
394
|
+
} else {
|
|
395
|
+
// There's not enough space available, put the last item inside more menu
|
|
274
396
|
hiddenItems.set(key, child);
|
|
275
397
|
}
|
|
276
|
-
}
|
|
277
|
-
|
|
398
|
+
} else {
|
|
399
|
+
// The current item is not the last tab in the list
|
|
400
|
+
// Check if there is enough space to accommodate this tab
|
|
278
401
|
if (visibleWidth >= consumedWidth + width) {
|
|
279
402
|
consumedWidth += width;
|
|
403
|
+
// There's enough space available, show this tab in the list
|
|
280
404
|
visibleItems.set(key, child);
|
|
281
|
-
}
|
|
282
|
-
|
|
405
|
+
} else {
|
|
406
|
+
// There's not enoguh space available put the tab inside the more menu (hide it)
|
|
283
407
|
hiddenItems.set(key, child);
|
|
284
408
|
noWidthForUnselectedTab = true;
|
|
285
409
|
}
|
|
286
410
|
}
|
|
287
411
|
continue;
|
|
288
412
|
}
|
|
413
|
+
// The selected tab is not visible yet. Check if the current tab is selected
|
|
289
414
|
if (key === value) {
|
|
415
|
+
// Show the selected tab, it should never be inside the more menu
|
|
290
416
|
visibleItems.set(key, child);
|
|
291
|
-
if
|
|
292
|
-
|
|
417
|
+
// Check if the current tab is not the last one in the list, if it isn't we need to account for its width
|
|
418
|
+
if (cache.length !== i + 1) consumedWidth += width;
|
|
293
419
|
isSelectedItemVisible = true;
|
|
294
420
|
continue;
|
|
295
421
|
}
|
|
422
|
+
// The current tab is not selected. Check if there is enough space for the current tab, the selected tab and more button
|
|
296
423
|
if (visibleWidth >= consumedWidth + width + selectedItemWidth) {
|
|
297
424
|
consumedWidth += width;
|
|
298
425
|
visibleItems.set(key, child);
|
|
299
426
|
continue;
|
|
300
427
|
}
|
|
428
|
+
// There's not enough space available. Check if current tab immediately precedes the last selected tab
|
|
301
429
|
const isSecondToLast = cache.length - 1 === i + 1;
|
|
302
|
-
|
|
303
|
-
|
|
430
|
+
// Check if there is enough space for the current tab and the last selected tab without the more button
|
|
431
|
+
const hasSpaceForLast2Items = visibleWidth >= consumedWidth - spaceForMoreBtn + width + selectedItemWidth;
|
|
304
432
|
if (isSecondToLast && hasSpaceForLast2Items) {
|
|
433
|
+
// If it's second to last and has enough space, show the tab
|
|
305
434
|
visibleItems.set(key, child);
|
|
306
|
-
}
|
|
307
|
-
|
|
435
|
+
} else {
|
|
436
|
+
// Otherwise hide the tab and put it in the more menu
|
|
308
437
|
hiddenItems.set(key, child);
|
|
309
438
|
noWidthForUnselectedTab = true;
|
|
310
439
|
}
|
|
311
440
|
}
|
|
312
441
|
if (hiddenItems.size) {
|
|
313
442
|
setShowMore(true);
|
|
314
|
-
setMoreMenuItems(Array.from(hiddenItems.entries()).map(([childValue, child])
|
|
315
|
-
return {
|
|
443
|
+
setMoreMenuItems(Array.from(hiddenItems.entries()).map(([childValue, child])=>{
|
|
444
|
+
return {
|
|
445
|
+
...child.props,
|
|
446
|
+
value: childValue,
|
|
447
|
+
ref: child.ref
|
|
448
|
+
};
|
|
316
449
|
}));
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
450
|
+
} else {
|
|
319
451
|
setShowMore(false);
|
|
320
452
|
}
|
|
453
|
+
// @ts-ignore
|
|
321
454
|
const newVisibleTabs = new Map();
|
|
322
455
|
let childIndex = 0;
|
|
323
|
-
for (const [childValue, child] of visibleItems.entries())
|
|
324
|
-
newVisibleTabs.set(childValue, {
|
|
456
|
+
for (const [childValue, child] of visibleItems.entries()){
|
|
457
|
+
newVisibleTabs.set(childValue, {
|
|
458
|
+
childIndex,
|
|
459
|
+
child
|
|
460
|
+
});
|
|
325
461
|
childIndex++;
|
|
326
462
|
}
|
|
327
463
|
setVisibleTabs(newVisibleTabs);
|
|
328
464
|
shouldUpdate.current = false;
|
|
329
|
-
if
|
|
330
|
-
|
|
331
|
-
|
|
465
|
+
// Check if visibleTabs or hiddenTabs has changed
|
|
466
|
+
if (!isEqual([
|
|
467
|
+
...visibleTabsRef.current?.keys() || []
|
|
468
|
+
], [
|
|
469
|
+
...visibleItems.keys()
|
|
470
|
+
]) || !isEqual([
|
|
471
|
+
...hiddenTabsRef.current?.keys() || []
|
|
472
|
+
], [
|
|
473
|
+
...hiddenItems.keys()
|
|
474
|
+
])) {
|
|
475
|
+
// If it has changed, invoke onGroupInfoUpdate with the updated group data
|
|
476
|
+
onGroupInfoUpdate?.([
|
|
477
|
+
...visibleItems.keys()
|
|
478
|
+
], [
|
|
479
|
+
...hiddenItems.keys()
|
|
480
|
+
]);
|
|
332
481
|
}
|
|
333
482
|
visibleTabsRef.current = visibleItems;
|
|
334
483
|
hiddenTabsRef.current = hiddenItems;
|
|
335
484
|
});
|
|
336
|
-
const computeFloatingUnderline = useEventCallback((withoutAnimation = false)
|
|
485
|
+
const computeFloatingUnderline = useEventCallback((withoutAnimation = false)=>{
|
|
337
486
|
const selectedTabElement = getSelectedTabElement();
|
|
338
|
-
if (!selectedTabElement)
|
|
339
|
-
return;
|
|
487
|
+
if (!selectedTabElement) return;
|
|
340
488
|
const currentWidth = floatingUnderlineWidthMotionValue.get();
|
|
341
489
|
const currentLeft = floatingUnderlineLeftMotionValue.get();
|
|
342
490
|
const targetWidth = selectedTabElement.offsetWidth;
|
|
@@ -347,25 +495,28 @@ function useComputedTabs(props) {
|
|
|
347
495
|
floatingUnderlineLeftMotionValue.set(targetLeft);
|
|
348
496
|
return;
|
|
349
497
|
}
|
|
498
|
+
// Only animate if either value has changed
|
|
350
499
|
if (currentWidth !== targetWidth || currentLeft !== targetLeft) {
|
|
351
|
-
const transition = motionTransition[
|
|
500
|
+
const transition = motionTransition["spring-01-fast"];
|
|
352
501
|
animate(floatingUnderlineWidthMotionValue, targetWidth, transition);
|
|
353
502
|
animate(floatingUnderlineLeftMotionValue, targetLeft, transition);
|
|
354
503
|
}
|
|
355
504
|
});
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
505
|
+
// variant 'moreMenu', check if cache need to be updated when visibleTabs changed
|
|
506
|
+
// also need to update the cache when fonts are loaded after the initial mount
|
|
507
|
+
useEffect(()=>{
|
|
508
|
+
if (variant !== 'moreMenu') return;
|
|
359
509
|
if (moreRef.current) {
|
|
360
510
|
moreBtnWidth.current = moreRef.current.offsetWidth;
|
|
361
511
|
}
|
|
362
512
|
if (tabsChildrenCache.current) {
|
|
363
513
|
if (!shouldUpdate.current) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
514
|
+
// Check if the rendered visibleTabs have a different width than the cached tabs
|
|
515
|
+
for (const [childValue, { childIndex: index, child }] of visibleTabs.entries()){
|
|
516
|
+
if (shouldUpdate.current || !child) continue;
|
|
367
517
|
const cachedWidth = tabsChildrenCache.current?.get(childValue)?.width;
|
|
368
518
|
const childElement = tabListRef.current?.children[index];
|
|
519
|
+
// If the cached width is different, we should perform a partial update
|
|
369
520
|
if (cachedWidth !== childElement.offsetWidth) {
|
|
370
521
|
shouldUpdate.current = 'partial';
|
|
371
522
|
}
|
|
@@ -376,82 +527,122 @@ function useComputedTabs(props) {
|
|
|
376
527
|
computeMoreMenuWithCache();
|
|
377
528
|
}
|
|
378
529
|
}
|
|
379
|
-
|
|
380
|
-
|
|
530
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
531
|
+
}, [
|
|
532
|
+
visibleTabs,
|
|
533
|
+
fontsLoaded
|
|
534
|
+
]);
|
|
535
|
+
useEffect(()=>{
|
|
381
536
|
if (prevChildren && children !== prevChildren) {
|
|
382
537
|
setVisibleTabs(processedChildren);
|
|
383
538
|
shouldUpdate.current = 'full';
|
|
384
539
|
}
|
|
385
|
-
}, [
|
|
386
|
-
|
|
540
|
+
}, [
|
|
541
|
+
children,
|
|
542
|
+
prevChildren,
|
|
543
|
+
processedChildren
|
|
544
|
+
]);
|
|
545
|
+
// initial cache for variant 'moreMenu', used for computing more menu
|
|
546
|
+
useLayoutEffect(()=>{
|
|
387
547
|
if (switchToMoreMenu.current && tabListRef.current?.children) {
|
|
388
548
|
switchToMoreMenu.current = false;
|
|
549
|
+
// initial cache of tabsChildrenCache
|
|
389
550
|
tabsChildrenCache.current = new Map();
|
|
390
551
|
cacheTabsChildren();
|
|
552
|
+
// initial cache of moreBtnWidth
|
|
391
553
|
moreBtnWidth.current = moreRef.current?.offsetWidth || 0;
|
|
392
554
|
computeMoreMenuWithCache();
|
|
393
555
|
}
|
|
394
|
-
|
|
395
|
-
|
|
556
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
557
|
+
}, [
|
|
558
|
+
showMore
|
|
559
|
+
]);
|
|
560
|
+
// handle switch variant situation
|
|
561
|
+
useOnReRender(()=>{
|
|
396
562
|
if (variant !== 'moreMenu') {
|
|
397
563
|
setShowMore(false);
|
|
398
564
|
setVisibleTabs(processedChildren);
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
565
|
+
} else {
|
|
401
566
|
switchToMoreMenu.current = true;
|
|
402
567
|
setShowMore(true);
|
|
403
568
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
569
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
570
|
+
}, [
|
|
571
|
+
variant
|
|
572
|
+
]);
|
|
573
|
+
// Check if we need to update the cache and update the more menu when any tab child is resized
|
|
574
|
+
useEffect(()=>{
|
|
575
|
+
if (variant !== 'moreMenu') return;
|
|
408
576
|
let initialObservation = true;
|
|
409
|
-
const handleResize = throttle((entries)
|
|
577
|
+
const handleResize = throttle((entries)=>{
|
|
410
578
|
if (initialObservation) {
|
|
411
|
-
|
|
579
|
+
/**
|
|
580
|
+
* We handle the computation of the more menu after the initial mount,
|
|
581
|
+
* or we re-compute when the `showMore` prop has updated. There's
|
|
582
|
+
* no need to compute here.
|
|
583
|
+
*/ initialObservation = false;
|
|
412
584
|
return;
|
|
413
585
|
}
|
|
414
586
|
if (entries) {
|
|
587
|
+
// Partially update the cache before computing the more menu
|
|
415
588
|
cacheTabsChildren();
|
|
416
|
-
if (moreRef.current)
|
|
417
|
-
moreBtnWidth.current = moreRef.current.offsetWidth;
|
|
589
|
+
if (moreRef.current) moreBtnWidth.current = moreRef.current.offsetWidth;
|
|
418
590
|
}
|
|
419
591
|
computeMoreMenuWithCache();
|
|
420
|
-
|
|
592
|
+
/**
|
|
593
|
+
* For variants other than moreMenu, we compute the floating underline using a dedicated
|
|
594
|
+
* ResizeObserver for the selected tab. When we're on the moreMenu we already have a resize
|
|
595
|
+
* observer for the tabList, so we don't need to create a dedicated one for the selected tab,
|
|
596
|
+
* then we compute the floating underline as part of the resize event we otherwise use for
|
|
597
|
+
* the more menu.
|
|
598
|
+
*/ computeFloatingUnderline(true);
|
|
421
599
|
}, 200);
|
|
422
600
|
let resizeObserver;
|
|
601
|
+
// Check if ResizeObserver is supported in this environment
|
|
423
602
|
if (typeof ResizeObserverClass !== 'undefined') {
|
|
424
603
|
resizeObserver = new ResizeObserverClass(handleResize);
|
|
425
|
-
if (scrollerRef.current)
|
|
426
|
-
resizeObserver.observe(scrollerRef.current);
|
|
604
|
+
if (scrollerRef.current) resizeObserver.observe(scrollerRef.current);
|
|
427
605
|
if (tabListRef && tabListRef.current) {
|
|
428
606
|
const tabsChildren = tabListRef.current.children;
|
|
429
|
-
Array.from(tabsChildren).forEach((child, index)
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
607
|
+
Array.from(tabsChildren).forEach((child, index)=>{
|
|
608
|
+
// The last child when the pill prop is enabled is the underline element, no need to observe it
|
|
609
|
+
if (pill && index === tabsChildren.length - 1) return;
|
|
610
|
+
// When the pill props is disabled, the last 2 children are the underline and floating underline elements, no need to observe them
|
|
611
|
+
if (!pill && index >= tabsChildren.length - 2) return;
|
|
434
612
|
resizeObserver?.observe(child);
|
|
435
613
|
});
|
|
436
614
|
}
|
|
437
615
|
}
|
|
438
|
-
return ()
|
|
616
|
+
return ()=>{
|
|
439
617
|
handleResize.cancel();
|
|
440
618
|
resizeObserver?.disconnect();
|
|
441
619
|
};
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
620
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
621
|
+
}, [
|
|
622
|
+
variant,
|
|
623
|
+
pill,
|
|
624
|
+
ResizeObserverClass
|
|
625
|
+
]);
|
|
626
|
+
useEffect(()=>{
|
|
627
|
+
// The ResizeObserver for the moreMenu automatically handles floating underline computation
|
|
628
|
+
if (variant === 'moreMenu') return;
|
|
446
629
|
let initialObservation = true;
|
|
447
|
-
const handleSelectedTabResize = throttle(()
|
|
630
|
+
const handleSelectedTabResize = throttle(()=>{
|
|
448
631
|
if (initialObservation) {
|
|
449
|
-
|
|
632
|
+
/**
|
|
633
|
+
* We handle the initial computation of the floating underline in the
|
|
634
|
+
* Tabs component after the value has changed, we don't need to compute
|
|
635
|
+
* it here again. This also allows us to distinguish between when the
|
|
636
|
+
* selected tab is resized and when the user selects a different tab,
|
|
637
|
+
* meaning we can apply an instant transition during resizing, but use
|
|
638
|
+
* a spring transition when switching tabs.
|
|
639
|
+
*/ initialObservation = false;
|
|
450
640
|
return;
|
|
451
641
|
}
|
|
452
642
|
computeFloatingUnderline(true);
|
|
453
643
|
}, 16);
|
|
454
644
|
let selectedTabResizeObserver;
|
|
645
|
+
// Check if ResizeObserver is supported in this environment
|
|
455
646
|
if (typeof ResizeObserverClass !== 'undefined') {
|
|
456
647
|
selectedTabResizeObserver = new ResizeObserverClass(handleSelectedTabResize);
|
|
457
648
|
const selectedTabElement = getSelectedTabElement();
|
|
@@ -459,24 +650,35 @@ function useComputedTabs(props) {
|
|
|
459
650
|
selectedTabResizeObserver.observe(selectedTabElement);
|
|
460
651
|
}
|
|
461
652
|
}
|
|
462
|
-
return ()
|
|
653
|
+
return ()=>{
|
|
463
654
|
handleSelectedTabResize.cancel();
|
|
464
655
|
selectedTabResizeObserver?.disconnect();
|
|
465
656
|
};
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
657
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
658
|
+
}, [
|
|
659
|
+
variant,
|
|
660
|
+
value,
|
|
661
|
+
ResizeObserverClass
|
|
662
|
+
]);
|
|
663
|
+
const prevVariant = usePrevious(()=>variant);
|
|
664
|
+
useEffect(()=>{
|
|
665
|
+
// If only the variant is changed, we need to update the floating underline without animation
|
|
469
666
|
if (prevVariant !== variant) {
|
|
470
667
|
computeFloatingUnderline(true);
|
|
471
668
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
669
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
670
|
+
}, [
|
|
671
|
+
variant
|
|
672
|
+
]);
|
|
673
|
+
useEffect(()=>{
|
|
674
|
+
if (variant === 'moreMenu' && value !== null && hiddenTabsRef.current?.has(value)) {
|
|
675
|
+
// Re-compute when a hidden tab (i.e. tab in the more menu) is selected
|
|
477
676
|
computeMoreMenuWithCache();
|
|
478
677
|
}
|
|
479
|
-
|
|
678
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
679
|
+
}, [
|
|
680
|
+
value
|
|
681
|
+
]);
|
|
480
682
|
return {
|
|
481
683
|
visibleTabsChildren,
|
|
482
684
|
showMore,
|
|
@@ -484,7 +686,7 @@ function useComputedTabs(props) {
|
|
|
484
686
|
processedChildren,
|
|
485
687
|
getSelectedTabElement,
|
|
486
688
|
computeMoreMenuWithCache,
|
|
487
|
-
computeFloatingUnderline
|
|
689
|
+
computeFloatingUnderline
|
|
488
690
|
};
|
|
489
691
|
}
|
|
490
692
|
function getHorizontalPadding(element) {
|
|
@@ -494,25 +696,36 @@ function getHorizontalPadding(element) {
|
|
|
494
696
|
return paddingLeft + paddingRight;
|
|
495
697
|
}
|
|
496
698
|
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
|
|
497
708
|
const THRESHOLD_FOR_SUB_PIXEL_CALCULATION = 0.5;
|
|
498
|
-
|
|
709
|
+
/**
|
|
710
|
+
* This `useTabsScroller` hook manages the state and behavior of scroll buttons at both ends of the tab list (start and end) when the tab list exceeds the visible container width
|
|
711
|
+
*/ const useTabsScroller = ({ enable, scrollerRef, tabListRef, scroll, ScrollButtonStartProps, ScrollButtonEndProps })=>{
|
|
499
712
|
const [displayStartScroll, setDisplayStartScroll] = useState(false);
|
|
500
713
|
const [displayEndScroll, setDisplayEndScroll] = useState(false);
|
|
501
714
|
const { externalWindow } = useWindowContext();
|
|
502
715
|
const ResizeObserverClass = (externalWindow || window).ResizeObserver;
|
|
503
|
-
useLayoutEffect(()
|
|
504
|
-
if (!enable)
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
if (!scrollerRef.current)
|
|
508
|
-
return;
|
|
716
|
+
useLayoutEffect(()=>{
|
|
717
|
+
if (!enable) return;
|
|
718
|
+
const checkScroll = throttle(()=>{
|
|
719
|
+
if (!scrollerRef.current) return;
|
|
509
720
|
const { scrollLeft, scrollWidth, clientWidth } = scrollerRef.current;
|
|
510
721
|
const maxScrollLeft = scrollWidth - clientWidth;
|
|
511
722
|
setDisplayStartScroll(scrollLeft > 0);
|
|
512
723
|
setDisplayEndScroll(maxScrollLeft - scrollLeft > THRESHOLD_FOR_SUB_PIXEL_CALCULATION);
|
|
513
724
|
}, 100);
|
|
725
|
+
// add scroll event listener
|
|
514
726
|
const currentTabsRef = scrollerRef.current;
|
|
515
727
|
currentTabsRef?.addEventListener('scroll', checkScroll);
|
|
728
|
+
// ResizeObserver for tabs list
|
|
516
729
|
let resizeObserver;
|
|
517
730
|
if (typeof ResizeObserverClass !== 'undefined') {
|
|
518
731
|
resizeObserver = new ResizeObserverClass(checkScroll);
|
|
@@ -520,26 +733,31 @@ const useTabsScroller = ({ enable, scrollerRef, tabListRef, scroll, ScrollButton
|
|
|
520
733
|
resizeObserver.observe(currentTabsRef);
|
|
521
734
|
}
|
|
522
735
|
}
|
|
523
|
-
checkScroll();
|
|
524
|
-
return ()
|
|
736
|
+
checkScroll(); // Initial check
|
|
737
|
+
return ()=>{
|
|
525
738
|
currentTabsRef?.removeEventListener('scroll', checkScroll);
|
|
526
739
|
checkScroll.cancel();
|
|
740
|
+
// Disconnect the ResizeObserver on cleanup
|
|
527
741
|
resizeObserver?.disconnect();
|
|
528
742
|
};
|
|
529
|
-
}, [
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
743
|
+
}, [
|
|
744
|
+
enable,
|
|
745
|
+
scrollerRef,
|
|
746
|
+
ResizeObserverClass
|
|
747
|
+
]);
|
|
748
|
+
const moveTabsScroll = useCallback((delta)=>{
|
|
749
|
+
const scrollValue = scrollerRef.current ? scrollerRef.current.scrollLeft + delta * 1 : 0;
|
|
534
750
|
scroll(scrollValue, 'neutral-01-slow');
|
|
535
|
-
}, [
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
751
|
+
}, [
|
|
752
|
+
scroll,
|
|
753
|
+
scrollerRef
|
|
754
|
+
]);
|
|
755
|
+
const getScrollSize = useCallback(()=>{
|
|
756
|
+
if (!scrollerRef.current || !tabListRef.current) return 0;
|
|
539
757
|
const containerSize = scrollerRef.current.clientWidth;
|
|
540
758
|
let totalSize = 0;
|
|
541
759
|
const children = Array.from(tabListRef.current.children);
|
|
542
|
-
for
|
|
760
|
+
for(let i = 0; i < children.length; i += 1){
|
|
543
761
|
const tab = children[i];
|
|
544
762
|
if (totalSize + tab.clientWidth > containerSize) {
|
|
545
763
|
if (i === 0) {
|
|
@@ -550,32 +768,64 @@ const useTabsScroller = ({ enable, scrollerRef, tabListRef, scroll, ScrollButton
|
|
|
550
768
|
totalSize += tab.clientWidth;
|
|
551
769
|
}
|
|
552
770
|
return totalSize;
|
|
553
|
-
}, [
|
|
554
|
-
|
|
771
|
+
}, [
|
|
772
|
+
scrollerRef,
|
|
773
|
+
tabListRef
|
|
774
|
+
]);
|
|
775
|
+
const handleStartScrollClick = useCallback(()=>{
|
|
555
776
|
moveTabsScroll(-getScrollSize());
|
|
556
|
-
}, [
|
|
557
|
-
|
|
777
|
+
}, [
|
|
778
|
+
moveTabsScroll,
|
|
779
|
+
getScrollSize
|
|
780
|
+
]);
|
|
781
|
+
const handleEndScrollClick = useCallback(()=>{
|
|
558
782
|
moveTabsScroll(getScrollSize());
|
|
559
|
-
}, [
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
783
|
+
}, [
|
|
784
|
+
moveTabsScroll,
|
|
785
|
+
getScrollSize
|
|
786
|
+
]);
|
|
787
|
+
return useMemo(()=>({
|
|
788
|
+
scrollButtonStart: enable && displayStartScroll ? /*#__PURE__*/ jsx(TabScrollButton, {
|
|
789
|
+
direction: 'left',
|
|
790
|
+
onClick: handleStartScrollClick,
|
|
791
|
+
ButtonProps: ScrollButtonStartProps
|
|
792
|
+
}) : null,
|
|
793
|
+
scrollButtonEnd: enable && displayEndScroll ? /*#__PURE__*/ jsx(TabScrollButton, {
|
|
794
|
+
direction: 'right',
|
|
795
|
+
onClick: handleEndScrollClick,
|
|
796
|
+
ButtonProps: ScrollButtonEndProps
|
|
797
|
+
}) : null
|
|
798
|
+
}), [
|
|
564
799
|
enable,
|
|
565
800
|
displayStartScroll,
|
|
566
801
|
handleStartScrollClick,
|
|
567
802
|
ScrollButtonStartProps,
|
|
568
803
|
displayEndScroll,
|
|
569
804
|
handleEndScrollClick,
|
|
570
|
-
ScrollButtonEndProps
|
|
805
|
+
ScrollButtonEndProps
|
|
571
806
|
]);
|
|
572
807
|
};
|
|
573
808
|
|
|
574
|
-
|
|
809
|
+
// extracted by css-extract-rspack-plugin
|
|
810
|
+
/* export default */ var Tabs = ({"sui-tabs-root":"sui-tabs-root","sui-tabs-large":"sui-tabs-large","sui-tabs-scroller":"sui-tabs-scroller","sui-tabs-scrollable":"sui-tabs-scrollable","sui-tab":"sui-tab","sui-tabs-tab-list":"sui-tabs-tab-list","sui-tabs-underline":"sui-tabs-underline","sui-tabs-indicator":"sui-tabs-indicator","sui-tabs-compact":"sui-tabs-compact","sui-tabs-pill":"sui-tabs-pill","sui-tab-more-menu-root":"sui-tab-more-menu-root","sui-tab-more-menu-button":"sui-tab-more-menu-button","sui-tabs-scroll-left":"sui-tabs-scroll-left","sui-tabs-scroll-right":"sui-tabs-scroll-right","sui-tabs-scroll-both":"sui-tabs-scroll-both","sui-tabs-floating-underline":"sui-tabs-floating-underline","sui-tab-more-menu-badge-root":"sui-tab-more-menu-badge-root","sui-badge-dot":"sui-badge-dot","sui-badge-content":"sui-badge-content","sui-tab-more-menu-pill-badge":"sui-tab-more-menu-pill-badge","sui-tab-scroll-button-root":"sui-tab-scroll-button-root","sui-tab-scroll-button-start":"sui-tab-scroll-button-start","sui-compact":"sui-compact"});
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
|
|
575
822
|
|
|
576
823
|
const DEFAULT_PILL_GAP = 4;
|
|
577
|
-
const
|
|
578
|
-
const props = useThemeProps({
|
|
824
|
+
const Tabs_Tabs = /*#__PURE__*/ forwardRef((inProps, ref)=>{
|
|
825
|
+
const props = useThemeProps({
|
|
826
|
+
props: inProps,
|
|
827
|
+
name: 'Tabs'
|
|
828
|
+
});
|
|
579
829
|
const { classes, className, children, variant = 'standard', onGroupInfoUpdate, MoreMenuProps, pill, pillGap: pillGapProp = DEFAULT_PILL_GAP, color = 'default', size = 'medium', ScrollButtonStartProps, ScrollButtonEndProps, enableScrollButtons = true, ...rest } = props;
|
|
580
830
|
const pillGap = pill ? pillGapProp : 0;
|
|
581
831
|
const tabsRef = useRef(null);
|
|
@@ -587,15 +837,15 @@ const Tabs = forwardRef((inProps, ref) => {
|
|
|
587
837
|
const floatingUnderlineWidthMotionValue = useMotionValue(0);
|
|
588
838
|
const floatingUnderlineLeftMotionValue = useMotionValue(0);
|
|
589
839
|
const floatingUnderlineOpacityMotionValue = useMotionValue(1);
|
|
590
|
-
const floatingUnderlineTransition = useMotionSafeTransition(motionTransition[
|
|
840
|
+
const floatingUnderlineTransition = useMotionSafeTransition(motionTransition["spring-01-fast"]);
|
|
591
841
|
const prefersReducedMotion = usePrefersReducedMotion();
|
|
592
842
|
const cls = useClassesWithMode('tabs', classes, className);
|
|
593
843
|
const { onGroupInfoUpdate: groupInfoUpdateHandler, ...restProps } = props;
|
|
594
|
-
const { getRootProps, contextValue, selectedValue: value
|
|
844
|
+
const { getRootProps, contextValue, selectedValue: value } = useTabsList({
|
|
595
845
|
...restProps,
|
|
596
|
-
rootRef
|
|
846
|
+
rootRef
|
|
597
847
|
});
|
|
598
|
-
const { processedChildren, visibleTabsChildren, showMore, moreMenuItems, getSelectedTabElement, computeFloatingUnderline
|
|
848
|
+
const { processedChildren, visibleTabsChildren, showMore, moreMenuItems, getSelectedTabElement, computeFloatingUnderline } = useComputedTabs({
|
|
599
849
|
variant,
|
|
600
850
|
value,
|
|
601
851
|
children,
|
|
@@ -605,13 +855,14 @@ const Tabs = forwardRef((inProps, ref) => {
|
|
|
605
855
|
floatingUnderlineWidthMotionValue,
|
|
606
856
|
tabListRef,
|
|
607
857
|
scrollerRef,
|
|
608
|
-
moreRef
|
|
858
|
+
moreRef
|
|
609
859
|
});
|
|
610
|
-
const getTabsMeta = useEventCallback(()
|
|
860
|
+
const getTabsMeta = useEventCallback(()=>{
|
|
611
861
|
const tabsNode = scrollerRef.current;
|
|
612
862
|
let tabsMeta;
|
|
613
863
|
if (tabsNode) {
|
|
614
864
|
const rect = tabsNode.getBoundingClientRect();
|
|
865
|
+
// create a new object with ClientRect class props + scrollLeft
|
|
615
866
|
tabsMeta = {
|
|
616
867
|
clientWidth: tabsNode.clientWidth,
|
|
617
868
|
scrollLeft: tabsNode.scrollLeft,
|
|
@@ -620,7 +871,7 @@ const Tabs = forwardRef((inProps, ref) => {
|
|
|
620
871
|
top: rect.top,
|
|
621
872
|
bottom: rect.bottom,
|
|
622
873
|
left: rect.left,
|
|
623
|
-
right: rect.right
|
|
874
|
+
right: rect.right
|
|
624
875
|
};
|
|
625
876
|
}
|
|
626
877
|
let tabMeta;
|
|
@@ -629,6 +880,7 @@ const Tabs = forwardRef((inProps, ref) => {
|
|
|
629
880
|
if (tab) {
|
|
630
881
|
const rect = tab.getBoundingClientRect();
|
|
631
882
|
tabMeta = {
|
|
883
|
+
// Use offsetWidth/offsetHeight to negate any transforms that might be applied
|
|
632
884
|
height: tab.offsetHeight,
|
|
633
885
|
width: tab.offsetWidth,
|
|
634
886
|
x: rect.x,
|
|
@@ -636,47 +888,50 @@ const Tabs = forwardRef((inProps, ref) => {
|
|
|
636
888
|
top: rect.top,
|
|
637
889
|
right: rect.right,
|
|
638
890
|
bottom: rect.bottom,
|
|
639
|
-
left: rect.left
|
|
891
|
+
left: rect.left
|
|
640
892
|
};
|
|
641
893
|
}
|
|
642
894
|
}
|
|
643
|
-
return {
|
|
895
|
+
return {
|
|
896
|
+
tabsMeta,
|
|
897
|
+
tabMeta
|
|
898
|
+
};
|
|
644
899
|
});
|
|
645
900
|
const scrollStart = 'scrollLeft';
|
|
646
901
|
const start = 'left';
|
|
647
902
|
const end = 'right';
|
|
648
|
-
const scroll = (scrollValue, transitionType)
|
|
649
|
-
if (!scrollerRef.current)
|
|
650
|
-
return;
|
|
903
|
+
const scroll = (scrollValue, transitionType)=>{
|
|
904
|
+
if (!scrollerRef.current) return;
|
|
651
905
|
const from = scrollerRef.current[scrollStart];
|
|
652
906
|
const to = Number(Number.parseFloat(scrollValue.toString()).toFixed(2));
|
|
653
907
|
if (from.toFixed(2) === to.toFixed(2)) {
|
|
654
908
|
return;
|
|
655
909
|
}
|
|
656
910
|
if (transitionType === null || prefersReducedMotion) {
|
|
911
|
+
// if transitionType is null or prefersReducedMotion, set the scroll value without animation
|
|
657
912
|
scrollerRef.current[scrollStart] = to;
|
|
658
913
|
return;
|
|
659
914
|
}
|
|
660
915
|
const transition = motionTransition[transitionType ? transitionType : 'haste-01-fast'];
|
|
661
916
|
animate(from, to, {
|
|
662
917
|
...transition,
|
|
663
|
-
onUpdate: (value)
|
|
664
|
-
if (!scrollerRef.current)
|
|
665
|
-
return;
|
|
918
|
+
onUpdate: (value)=>{
|
|
919
|
+
if (!scrollerRef.current) return;
|
|
666
920
|
scrollerRef.current[scrollStart] = value;
|
|
667
|
-
}
|
|
921
|
+
}
|
|
668
922
|
});
|
|
669
923
|
};
|
|
670
|
-
const scrollSelectedIntoView = useEventCallback((noAnimation)
|
|
924
|
+
const scrollSelectedIntoView = useEventCallback((noAnimation)=>{
|
|
671
925
|
const { tabsMeta, tabMeta } = getTabsMeta();
|
|
672
926
|
if (!tabMeta || !tabsMeta) {
|
|
673
927
|
return;
|
|
674
928
|
}
|
|
929
|
+
// Check if the left side of the tab is out of view
|
|
675
930
|
if (tabMeta[start] < tabsMeta[start]) {
|
|
676
931
|
const nextScrollStart = tabsMeta[scrollStart] + (tabMeta[start] - tabsMeta[start]);
|
|
677
932
|
scroll(nextScrollStart, noAnimation ? null : undefined);
|
|
678
|
-
|
|
679
|
-
else if (tabMeta[end] > tabsMeta[end]) {
|
|
933
|
+
// Check if the right side of the tab is out of view
|
|
934
|
+
} else if (tabMeta[end] > tabsMeta[end]) {
|
|
680
935
|
const nextScrollStart = tabsMeta[scrollStart] + Math.abs(tabMeta[end] - tabsMeta[end]);
|
|
681
936
|
scroll(nextScrollStart, noAnimation ? null : undefined);
|
|
682
937
|
}
|
|
@@ -687,48 +942,114 @@ const Tabs = forwardRef((inProps, ref) => {
|
|
|
687
942
|
tabListRef,
|
|
688
943
|
scroll,
|
|
689
944
|
ScrollButtonStartProps,
|
|
690
|
-
ScrollButtonEndProps
|
|
945
|
+
ScrollButtonEndProps
|
|
691
946
|
});
|
|
692
947
|
const prevValueAfterChildrenUpdate = useRef(value);
|
|
693
948
|
const prevVisibleTabsChildren = useRef(visibleTabsChildren);
|
|
694
|
-
useEffect(()
|
|
949
|
+
useEffect(()=>{
|
|
695
950
|
const noAnimationWhenFirstRender = floatingUnderlineWidthMotionValue.get() === 0;
|
|
696
951
|
if (variant !== 'moreMenu') {
|
|
697
|
-
|
|
952
|
+
/**
|
|
953
|
+
* Invoke scrollSelectedIntoView when visibleTabsChildren has been updated or the selected
|
|
954
|
+
* tab has changed. It will be updated when the variant is switched from `moreMenu`
|
|
955
|
+
* to another value. If the variant is `moreMenu`, `visibleTabsChildren` will also be
|
|
956
|
+
* updated when resized.
|
|
957
|
+
*/ scrollSelectedIntoView(noAnimationWhenFirstRender);
|
|
698
958
|
}
|
|
699
|
-
|
|
700
|
-
|
|
959
|
+
// If the visibleTabsChildren has been updated but selected tab has not changed,
|
|
960
|
+
// we don't need to animate the floating underline
|
|
961
|
+
const noAnimationWhenNoValueChange = prevVisibleTabsChildren.current !== visibleTabsChildren && prevValueAfterChildrenUpdate.current === value;
|
|
701
962
|
if (prevVisibleTabsChildren.current !== visibleTabsChildren) {
|
|
702
963
|
prevVisibleTabsChildren.current = visibleTabsChildren;
|
|
703
964
|
prevValueAfterChildrenUpdate.current = value;
|
|
704
965
|
}
|
|
705
966
|
computeFloatingUnderline(noAnimationWhenFirstRender || noAnimationWhenNoValueChange);
|
|
706
|
-
|
|
707
|
-
|
|
967
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
968
|
+
}, [
|
|
969
|
+
visibleTabsChildren,
|
|
970
|
+
value
|
|
971
|
+
]);
|
|
972
|
+
const tabsContextValue = useMemo(()=>({
|
|
973
|
+
pill,
|
|
974
|
+
pillGap,
|
|
975
|
+
color,
|
|
976
|
+
size,
|
|
977
|
+
setFloatingUnderlineVisibility: (isVisible)=>{
|
|
978
|
+
floatingUnderlineOpacityMotionValue.set(isVisible ? 1 : 0);
|
|
979
|
+
}
|
|
980
|
+
}), [
|
|
981
|
+
floatingUnderlineOpacityMotionValue,
|
|
708
982
|
pill,
|
|
709
983
|
pillGap,
|
|
710
984
|
color,
|
|
711
|
-
size
|
|
712
|
-
|
|
713
|
-
floatingUnderlineOpacityMotionValue.set(isVisible ? 1 : 0);
|
|
714
|
-
},
|
|
715
|
-
}), [floatingUnderlineOpacityMotionValue, pill, pillGap, color, size]);
|
|
985
|
+
size
|
|
986
|
+
]);
|
|
716
987
|
if (!processedChildren.size) {
|
|
717
988
|
return null;
|
|
718
989
|
}
|
|
719
|
-
return
|
|
990
|
+
return /*#__PURE__*/ jsx(TabsListProvider, {
|
|
991
|
+
value: contextValue,
|
|
992
|
+
children: /*#__PURE__*/ jsx(TabsContext.Provider, {
|
|
993
|
+
value: tabsContextValue,
|
|
994
|
+
children: /*#__PURE__*/ jsxs("div", {
|
|
995
|
+
className: cls('root', {
|
|
720
996
|
pill,
|
|
721
997
|
scrollable: variant === 'scrollable',
|
|
722
998
|
compact: variant === 'moreMenu',
|
|
723
999
|
'scroll-left': !!scrollButtonStart,
|
|
724
1000
|
'scroll-right': !!scrollButtonEnd,
|
|
725
|
-
'scroll-both': !!(scrollButtonStart && scrollButtonEnd)
|
|
726
|
-
}, size),
|
|
1001
|
+
'scroll-both': !!(scrollButtonStart && scrollButtonEnd)
|
|
1002
|
+
}, size),
|
|
1003
|
+
...getRootProps(rest),
|
|
1004
|
+
children: [
|
|
1005
|
+
scrollButtonStart,
|
|
1006
|
+
/*#__PURE__*/ jsx("div", {
|
|
1007
|
+
className: cls('scroller'),
|
|
1008
|
+
ref: scrollerRef,
|
|
1009
|
+
children: /*#__PURE__*/ jsxs("div", {
|
|
1010
|
+
className: cls('tab-list'),
|
|
1011
|
+
ref: tabListRef,
|
|
1012
|
+
children: [
|
|
1013
|
+
visibleTabsChildren,
|
|
1014
|
+
showMore && /*#__PURE__*/ jsx(TabMoreMenu, {
|
|
1015
|
+
menuItems: moreMenuItems,
|
|
1016
|
+
...MoreMenuProps,
|
|
1017
|
+
ref: tabMoreMenuRef
|
|
1018
|
+
}),
|
|
1019
|
+
/*#__PURE__*/ jsx("div", {
|
|
1020
|
+
className: cls('underline')
|
|
1021
|
+
}),
|
|
1022
|
+
!pill && /*#__PURE__*/ jsx(motion.div, {
|
|
1023
|
+
style: {
|
|
727
1024
|
left: floatingUnderlineLeftMotionValue,
|
|
728
1025
|
width: floatingUnderlineWidthMotionValue,
|
|
729
|
-
opacity: floatingUnderlineOpacityMotionValue
|
|
730
|
-
},
|
|
1026
|
+
opacity: floatingUnderlineOpacityMotionValue
|
|
1027
|
+
},
|
|
1028
|
+
transition: floatingUnderlineTransition,
|
|
1029
|
+
className: cls('floating-underline')
|
|
1030
|
+
})
|
|
1031
|
+
]
|
|
1032
|
+
})
|
|
1033
|
+
}),
|
|
1034
|
+
scrollButtonEnd
|
|
1035
|
+
]
|
|
1036
|
+
})
|
|
1037
|
+
})
|
|
1038
|
+
});
|
|
731
1039
|
});
|
|
732
|
-
|
|
1040
|
+
Tabs_Tabs.displayName = 'SuiTabs';
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
|
|
733
1053
|
|
|
734
|
-
export { TabMoreMenu, TabScrollButton, Tabs
|
|
1054
|
+
export { TabMoreMenu, TabScrollButton, Tabs as tabsStyle, Tabs_Tabs as Tabs, useComputedTabs, useTabsScroller };
|
|
1055
|
+
export { TabsContext, useTabsContext } from "./chunks/8809.js";
|