@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.
Files changed (213) hide show
  1. package/Accordion.js +90 -34
  2. package/AccordionHeader.js +71 -21
  3. package/Alert.js +120 -28
  4. package/Animation.js +2 -46
  5. package/Announcement.js +46 -35
  6. package/Autocomplete.js +1011 -583
  7. package/Avatar.js +134 -73
  8. package/AvatarGroup.js +50 -7
  9. package/Backdrop.js +45 -7
  10. package/Badge.js +77 -15
  11. package/Block.js +34 -12
  12. package/BlockHeader.js +57 -15
  13. package/BottomNavigation.js +181 -65
  14. package/BottomNavigationItem.js +93 -30
  15. package/Button.js +160 -24
  16. package/CallButton.js +53 -31
  17. package/Checkbox.js +88 -26
  18. package/Chip.js +126 -26
  19. package/CircularProgressIndicator.js +80 -8
  20. package/ClickAwayListener.js +4 -1
  21. package/ComponentsProps.js +1 -0
  22. package/DatePicker.js +611 -192
  23. package/DateUtilsContext.js +43 -25
  24. package/Dialer.js +564 -253
  25. package/Dialog.js +99 -45
  26. package/Divider.js +43 -7
  27. package/DragHandle.js +32 -6
  28. package/Drawer.js +117 -19
  29. package/Dropdown.js +154 -77
  30. package/EmptyState.js +65 -15
  31. package/FabButton.js +51 -17
  32. package/FocusTrap.js +109 -12
  33. package/FormField.js +108 -12
  34. package/FormLabel.js +81 -7
  35. package/Icon.js +1 -8
  36. package/IconButton.js +142 -26
  37. package/IconButtonGroup.js +35 -12
  38. package/InlineEditable.js +98 -44
  39. package/LinearProgressIndicator.js +73 -24
  40. package/Link.js +40 -11
  41. package/List.js +54 -9
  42. package/ListItem.js +152 -23
  43. package/ListItemText.js +44 -17
  44. package/ListNavigation.js +272 -20
  45. package/Menu.js +139 -23
  46. package/MenuActions.js +37 -12
  47. package/MenuDivider.js +34 -8
  48. package/MenuFooter.js +36 -12
  49. package/MenuHeader.js +54 -16
  50. package/MenuItem.js +402 -35
  51. package/MenuItemText.js +51 -9
  52. package/MenuList.js +87 -36
  53. package/Modal.js +345 -16
  54. package/NestedMenuContext.js +183 -11
  55. package/Option.js +32 -36
  56. package/Overlap.js +54 -7
  57. package/PageHeader.js +62 -24
  58. package/PickerTextField.js +156 -35
  59. package/Popover.js +262 -21
  60. package/Popper.js +112 -62
  61. package/PopperPaper.js +54 -8
  62. package/Portal.js +6 -1
  63. package/Radio.js +65 -21
  64. package/RadioGroup.js +56 -17
  65. package/Rating.js +223 -87
  66. package/Select.js +387 -44
  67. package/SingleFilter.js +148 -79
  68. package/Skeleton.js +68 -17
  69. package/Slider.js +167 -69
  70. package/Snackbar.js +122 -59
  71. package/SortableList.js +32 -12
  72. package/SortableListDndContext.js +69 -22
  73. package/SortableListItem.js +92 -47
  74. package/SortableVirtualizedList.js +44 -20
  75. package/SplitButton.js +124 -89
  76. package/Squircle.js +4 -7
  77. package/StatusIndicator.js +51 -9
  78. package/Step.js +63 -38
  79. package/StepButton.js +51 -23
  80. package/StepConnector.js +48 -8
  81. package/StepContent.js +67 -24
  82. package/StepIcon.js +90 -9
  83. package/StepLabel.js +90 -14
  84. package/Stepper.js +65 -23
  85. package/SubMenu.js +203 -101
  86. package/SwipeableDrawer.js +439 -194
  87. package/Switch.js +70 -23
  88. package/Tab.js +120 -57
  89. package/TabContext.js +59 -15
  90. package/TabPanel.js +40 -13
  91. package/Table.js +60 -17
  92. package/TableBody.js +42 -12
  93. package/TableCell.js +84 -50
  94. package/TableContainer.js +32 -10
  95. package/TableFooter.js +38 -10
  96. package/TableHead.js +49 -15
  97. package/TablePagination.js +153 -79
  98. package/TableRow.js +60 -21
  99. package/Tabs.js +592 -271
  100. package/Tag.js +51 -16
  101. package/Text.js +68 -37
  102. package/TextField.js +209 -31
  103. package/Textarea.js +51 -42
  104. package/TimePicker.js +490 -232
  105. package/Tooltip.js +48 -23
  106. package/Tray.js +237 -13
  107. package/TrayMenu.js +62 -43
  108. package/VirtualizedList.js +205 -107
  109. package/VirtualizedMenu.js +359 -29
  110. package/VisuallyHidden.js +29 -6
  111. package/chunks/1250.js +98 -0
  112. package/chunks/1294.js +126 -0
  113. package/chunks/2956.js +18 -0
  114. package/chunks/4476.js +460 -0
  115. package/chunks/4491.js +10 -0
  116. package/chunks/4535.js +364 -0
  117. package/chunks/5047.js +3 -0
  118. package/chunks/6335.js +24 -0
  119. package/chunks/7279.js +10 -0
  120. package/chunks/8011.js +8 -0
  121. package/chunks/8307.js +56 -0
  122. package/chunks/8467.js +8 -0
  123. package/chunks/8809.js +8 -0
  124. package/chunks/{RadioGroup.context.js → 9551.js} +12 -6
  125. package/chunks/987.js +8 -0
  126. package/index.css +2436 -2261
  127. package/index.js +245 -195
  128. package/internal/utils/setRef.d.ts +2 -1
  129. package/package.json +4 -4
  130. package/src/Animation/hooks/useMotionSafeTransition/useMotionSafeTransition.d.ts +2 -0
  131. package/src/Autocomplete/SuggestionList/SuggestionList.d.ts +1 -1
  132. package/src/Button/Button.classes.d.ts +1 -1
  133. package/src/ComponentsProps.d.ts +111 -110
  134. package/src/DatePicker/DatePickerDataAttributes.d.ts +11 -0
  135. package/src/DatePicker/utils/DatePickerUtils.d.ts +1 -1
  136. package/src/MenuItem/MenuItem.type.d.ts +1 -0
  137. package/src/MenuList/MenuListContext.d.ts +2 -3
  138. package/src/Modal/Modal.type.d.ts +1 -0
  139. package/src/Popover/Popover.classes.d.ts +1 -1
  140. package/src/Popover/Popover.internal.d.ts +8 -0
  141. package/src/Popover/Popover.type.d.ts +1 -0
  142. package/src/Select/Select.type.d.ts +6 -4
  143. package/src/Table/Table.type.d.ts +3 -0
  144. package/src/Table/TableContext.d.ts +3 -0
  145. package/src/Table/TableRowContext.d.ts +7 -0
  146. package/src/Table/index.d.ts +1 -0
  147. package/src/TableCell/TableCell.classes.d.ts +1 -1
  148. package/src/TableFooter/TableFooter.classes.d.ts +1 -1
  149. package/src/TableFooter/TableFooter.type.d.ts +1 -0
  150. package/src/TableHead/TableHead.classes.d.ts +1 -1
  151. package/src/TableHead/TableHead.type.d.ts +1 -0
  152. package/src/TableRow/TableRow.type.d.ts +2 -0
  153. package/chunks/AccordionContext.js +0 -6
  154. package/chunks/Alert.js +0 -48
  155. package/chunks/AvatarGroup.js +0 -22
  156. package/chunks/Backdrop.js +0 -18
  157. package/chunks/Badge.js +0 -32
  158. package/chunks/BottomNavigationItem.js +0 -36
  159. package/chunks/Button.js +0 -88
  160. package/chunks/Chip.js +0 -73
  161. package/chunks/CircularProgressIndicator.js +0 -36
  162. package/chunks/Divider.js +0 -19
  163. package/chunks/DragHandle.js +0 -11
  164. package/chunks/Drawer.js +0 -33
  165. package/chunks/ExpandCollapseCaret.js +0 -24
  166. package/chunks/FormLabel.js +0 -27
  167. package/chunks/Icon.js +0 -25
  168. package/chunks/IconButtonLabel.js +0 -68
  169. package/chunks/List.js +0 -22
  170. package/chunks/ListItem.js +0 -83
  171. package/chunks/Menu.js +0 -61
  172. package/chunks/MenuDivider.js +0 -14
  173. package/chunks/MenuItemText.js +0 -18
  174. package/chunks/MenuList.js +0 -47
  175. package/chunks/Modal.js +0 -211
  176. package/chunks/Overlap.js +0 -25
  177. package/chunks/PickerTextField.js +0 -85
  178. package/chunks/Popover.js +0 -61
  179. package/chunks/PopperPaper.js +0 -15
  180. package/chunks/Select.js +0 -225
  181. package/chunks/ShowHideEye.js +0 -56
  182. package/chunks/SlideIn.js +0 -20
  183. package/chunks/SortableArrow.js +0 -73
  184. package/chunks/Squircle.js +0 -31
  185. package/chunks/StatusIndicator.js +0 -20
  186. package/chunks/StepConnector.js +0 -21
  187. package/chunks/StepIcon.js +0 -43
  188. package/chunks/StepLabel.js +0 -29
  189. package/chunks/StepperContext.js +0 -9
  190. package/chunks/TableContext.js +0 -6
  191. package/chunks/Tablelvl2Context.js +0 -6
  192. package/chunks/TabsContext.js +0 -6
  193. package/chunks/TextField.js +0 -110
  194. package/chunks/Tooltip.js +0 -217
  195. package/chunks/VirtualizedMenu.js +0 -162
  196. package/chunks/VisuallyHidden.js +0 -12
  197. package/chunks/composeEventHandlers.js +0 -10
  198. package/chunks/getFade.js +0 -27
  199. package/chunks/getGrow.js +0 -70
  200. package/chunks/getSlideIn.js +0 -30
  201. package/chunks/hasContent.js +0 -3
  202. package/chunks/useAnimationFrame.js +0 -15
  203. package/chunks/useFocusTrapStack.js +0 -38
  204. package/chunks/useHelperTextId.js +0 -24
  205. package/chunks/useListNavigation.js +0 -199
  206. package/chunks/useMenuItemSelect.js +0 -282
  207. package/chunks/useMotionSafeTransition.js +0 -9
  208. package/chunks/useNestedMenuItem.js +0 -27
  209. package/chunks/useNestedParentMenu.js +0 -113
  210. package/chunks/usePrefersReducedMotion.js +0 -36
  211. package/chunks/useTooltipForceHide.js +0 -27
  212. package/chunks/useTrayNavigate.js +0 -150
  213. package/chunks/withTooltip.js +0 -15
package/Tabs.js CHANGED
@@ -1,89 +1,92 @@
1
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import React, { forwardRef, useState, useMemo, useRef, Children, isValidElement, cloneElement, useEffect, useLayoutEffect, useCallback } from 'react';
3
- import { useTab, useTabsList, TabsListProvider } from '@mui/base';
4
- import { useClasses, combineClasses, getTitle, combineProps, useFontLoaded, useWindowContext, logInDev, usePrevious, useEventCallback, useOnReRender, useForkRef } from '@ringcentral/spring-base';
5
- import { OverflowMd, CaretLeftMd, CaretRightMd } from '@ringcentral/spring-icon';
6
- import { useMode, motionTransition, useThemeProps, useClassesWithMode } from '@ringcentral/spring-theme';
7
- import { B as Badge } from './chunks/Badge.js';
8
- import { I as IconButton } from './chunks/IconButtonLabel.js';
9
- import { M as Menu } from './chunks/Menu.js';
10
- import { M as MenuItem } from './chunks/useMenuItemSelect.js';
11
- import '@mui/utils';
12
- import 'tabbable';
13
- import './chunks/useListNavigation.js';
14
- import './VirtualizedList.js';
15
- import { M as MenuItemText } from './chunks/MenuItemText.js';
16
- import { M as MenuList } from './chunks/MenuList.js';
17
- import { T as Tooltip } from './chunks/Tooltip.js';
18
- import { u as useTabsContext, T as TabsContext } from './chunks/TabsContext.js';
19
- import { animate, useMotionValue, motion } from 'framer-motion';
20
- import './chunks/Icon.js';
21
- import { u as usePrefersReducedMotion } from './chunks/usePrefersReducedMotion.js';
22
- import { u as useMotionSafeTransition } from './chunks/useMotionSafeTransition.js';
23
- import isEqual from 'lodash/isEqual';
24
- import throttle from 'lodash/throttle';
25
- import { useInternalTabsContext, generateTabStableIds } from './TabContext.js';
26
- import 'lodash/isNumber';
27
- import 'lodash/kebabCase';
28
- import './chunks/Squircle.js';
29
- import './chunks/withTooltip.js';
30
- import './chunks/useNestedParentMenu.js';
31
- import './chunks/Popover.js';
32
- import '@floating-ui/react-dom';
33
- import './chunks/getGrow.js';
34
- import './chunks/Modal.js';
35
- import './chunks/getFade.js';
36
- import './chunks/useFocusTrapStack.js';
37
- import './chunks/Backdrop.js';
38
- import './Popper.js';
39
- import './chunks/PopperPaper.js';
40
- import './chunks/useAnimationFrame.js';
41
- import './chunks/useNestedMenuItem.js';
42
- import './chunks/composeEventHandlers.js';
43
- import './ListNavigation.js';
44
- import 'react-virtuoso';
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 (jsx(MenuItemComponent, { ...props, ...getRootProps(), onKeyDown: onKeyDown, children: jsx(MenuItemText, { children: label }) }));
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(() => (pill
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
- }), [pill, mode]);
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 = (jsx(IconButton, { color: 'secondary', symbol: OverflowMd, ...iconButtonProps, onClick: (e) => {
101
- setAnchor((prev) => (prev ? null : e.currentTarget));
102
- }, "aria-expanded": Boolean(anchor), "aria-label": getTitle(TooltipProps?.title), ...ButtonProps, classes: iconButtonClasses }));
103
- return (jsxs(Fragment, { children: [jsxs("div", { className: cls('root'), ref: ref, ...rest, children: [TooltipProps ? (jsx(Tooltip, { ...TooltipProps, children: iconButton })) : (iconButton), Number(BadgeProps?.count) > 0 ? jsx(Badge, { ...badgeProps }) : null] }), jsx(Menu, { open: Boolean(anchor), anchorEl: anchor, ...combineProps({ onClose: handleClose }, MenuProps), children: jsx(MenuList, { children: menuItems.map(({ ref, ...props }, index) => (jsx(TabMenuItem, { ...props, ref: ref, component: MenuItemComponent }, props.value || `TabMenuItem-${index}`))) }) })] }));
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
- const TabScrollButton = forwardRef((props, ref) => {
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
- }, [ButtonProps?.classes, cls]);
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
- }, [mode]);
124
- return (jsx("div", { className: cls('root', {
125
- start: direction === 'left',
126
- }), ref: ref, ...rest, children: jsx(IconButton, { "aria-hidden": "true", role: "presentation", tabIndex: -1, component: "div", color: "secondary", symbol: icon, shape: "circular", variant: "outlined", size: iconButtonSize, background: true, onClick: onClick, ...ButtonProps, classes: iconButtonClasses }) }));
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, } = props;
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, { childIndex, child: finalChild });
290
+ visibleTabs.set(childValue, {
291
+ childIndex,
292
+ child: finalChild
293
+ });
188
294
  childIndex += 1;
189
295
  }
190
296
  });
191
297
  return visibleTabs;
192
- }, [children, tabsInstanceId]);
298
+ }, [
299
+ children,
300
+ tabsInstanceId
301
+ ]);
193
302
  const [visibleTabs, setVisibleTabs] = useState(processedChildren);
194
- const visibleTabsChildren = useMemo(() => {
195
- return Array.from(visibleTabs.entries()).map(([childValue, { child }]) => ({
196
- ...child,
197
- key: child.key ?? childValue,
198
- props: { ...child.props, value: childValue },
199
- }));
200
- }, [visibleTabs]);
201
- const prevChildren = usePrevious(() => children);
202
- const cacheTabsChildren = useEventCallback(() => {
203
- if (shouldUpdate.current === 'full')
204
- tabsChildrenCache.current = new Map();
205
- if (!tabListRef.current?.children || !tabsChildrenCache.current)
206
- return;
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
- selectedIndex = Array.from(visibleTabsRef.current).findIndex(([key]) => {
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
- const computeMoreMenuWithCache = useEventCallback(() => {
246
- if (variant !== 'moreMenu' || !tabsChildrenCache.current)
247
- return;
248
- const width = scrollerRef.current?.offsetWidth || 0;
249
- const visibleWidth = width +
250
- pillGap -
251
- (tabListRef.current ? getHorizontalPadding(tabListRef.current) : 0);
252
- const selectedItemWidth = (value !== null && tabsChildrenCache.current.get(value)?.width) || 0;
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 (let i = 0; i < cache.length; i++) {
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
- if (noWidthForUnselectedTab && key !== value) {
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
- else {
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
- else {
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
- else {
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 (cache.length !== i + 1)
292
- consumedWidth += width;
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
- const hasSpaceForLast2Items = visibleWidth >=
303
- consumedWidth - spaceForMoreBtn + width + selectedItemWidth;
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
- else {
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 { ...child.props, value: childValue, ref: child.ref };
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, { childIndex, child });
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 (!isEqual([...(visibleTabsRef.current?.keys() || [])], [...visibleItems.keys()]) ||
330
- !isEqual([...(hiddenTabsRef.current?.keys() || [])], [...hiddenItems.keys()])) {
331
- onGroupInfoUpdate?.([...visibleItems.keys()], [...hiddenItems.keys()]);
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['spring-01-fast'];
500
+ const transition = motionTransition["spring-01-fast"];
352
501
  animate(floatingUnderlineWidthMotionValue, targetWidth, transition);
353
502
  animate(floatingUnderlineLeftMotionValue, targetLeft, transition);
354
503
  }
355
504
  });
356
- useEffect(() => {
357
- if (variant !== 'moreMenu')
358
- return;
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
- for (const [childValue, { childIndex: index, child },] of visibleTabs.entries()) {
365
- if (shouldUpdate.current || !child)
366
- continue;
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
- }, [visibleTabs, fontsLoaded]);
380
- useEffect(() => {
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
- }, [children, prevChildren, processedChildren]);
386
- useLayoutEffect(() => {
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
- }, [showMore]);
395
- useOnReRender(() => {
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
- }, [variant]);
405
- useEffect(() => {
406
- if (variant !== 'moreMenu')
407
- return;
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
- initialObservation = false;
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
- computeFloatingUnderline(true);
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
- if (pill && index === tabsChildren.length - 1)
431
- return;
432
- if (!pill && index >= tabsChildren.length - 2)
433
- return;
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
- }, [variant, pill, ResizeObserverClass]);
443
- useEffect(() => {
444
- if (variant === 'moreMenu')
445
- return;
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
- initialObservation = false;
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
- }, [variant, value, ResizeObserverClass]);
467
- const prevVariant = usePrevious(() => variant);
468
- useEffect(() => {
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
- }, [variant]);
473
- useEffect(() => {
474
- if (variant === 'moreMenu' &&
475
- value !== null &&
476
- hiddenTabsRef.current?.has(value)) {
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
- }, [value]);
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
- const useTabsScroller = ({ enable, scrollerRef, tabListRef, scroll, ScrollButtonStartProps, ScrollButtonEndProps, }) => {
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
- return;
506
- const checkScroll = throttle(() => {
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
- }, [enable, scrollerRef, ResizeObserverClass]);
530
- const moveTabsScroll = useCallback((delta) => {
531
- const scrollValue = scrollerRef.current
532
- ? scrollerRef.current.scrollLeft + delta * 1
533
- : 0;
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
- }, [scroll, scrollerRef]);
536
- const getScrollSize = useCallback(() => {
537
- if (!scrollerRef.current || !tabListRef.current)
538
- return 0;
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 (let i = 0; i < children.length; i += 1) {
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
- }, [scrollerRef, tabListRef]);
554
- const handleStartScrollClick = useCallback(() => {
771
+ }, [
772
+ scrollerRef,
773
+ tabListRef
774
+ ]);
775
+ const handleStartScrollClick = useCallback(()=>{
555
776
  moveTabsScroll(-getScrollSize());
556
- }, [moveTabsScroll, getScrollSize]);
557
- const handleEndScrollClick = useCallback(() => {
777
+ }, [
778
+ moveTabsScroll,
779
+ getScrollSize
780
+ ]);
781
+ const handleEndScrollClick = useCallback(()=>{
558
782
  moveTabsScroll(getScrollSize());
559
- }, [moveTabsScroll, getScrollSize]);
560
- return useMemo(() => ({
561
- scrollButtonStart: enable && displayStartScroll ? (jsx(TabScrollButton, { direction: 'left', onClick: handleStartScrollClick, ButtonProps: ScrollButtonStartProps })) : null,
562
- scrollButtonEnd: enable && displayEndScroll ? (jsx(TabScrollButton, { direction: 'right', onClick: handleEndScrollClick, ButtonProps: ScrollButtonEndProps })) : null,
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
- var Tabs$1 = {"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"};
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 Tabs = forwardRef((inProps, ref) => {
578
- const props = useThemeProps({ props: inProps, name: 'Tabs' });
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['spring-01-fast']);
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, } = useTabsList({
844
+ const { getRootProps, contextValue, selectedValue: value } = useTabsList({
595
845
  ...restProps,
596
- rootRef,
846
+ rootRef
597
847
  });
598
- const { processedChildren, visibleTabsChildren, showMore, moreMenuItems, getSelectedTabElement, computeFloatingUnderline, } = useComputedTabs({
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 { tabsMeta, tabMeta };
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
- scrollSelectedIntoView(noAnimationWhenFirstRender);
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
- const noAnimationWhenNoValueChange = prevVisibleTabsChildren.current !== visibleTabsChildren &&
700
- prevValueAfterChildrenUpdate.current === value;
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
- }, [visibleTabsChildren, value]);
707
- const tabsContextValue = useMemo(() => ({
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
- setFloatingUnderlineVisibility: (isVisible) => {
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 (jsx(TabsListProvider, { value: contextValue, children: jsx(TabsContext.Provider, { value: tabsContextValue, children: jsxs("div", { className: cls('root', {
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), ...getRootProps(rest), children: [scrollButtonStart, jsx("div", { className: cls('scroller'), ref: scrollerRef, children: jsxs("div", { className: cls('tab-list'), ref: tabListRef, children: [visibleTabsChildren, showMore && (jsx(TabMoreMenu, { menuItems: moreMenuItems, ...MoreMenuProps, ref: tabMoreMenuRef })), jsx("div", { className: cls('underline') }), !pill && (jsx(motion.div, { style: {
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
- }, transition: floatingUnderlineTransition, className: cls('floating-underline') }))] }) }), scrollButtonEnd] }) }) }));
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
- Tabs.displayName = 'SuiTabs';
1040
+ Tabs_Tabs.displayName = 'SuiTabs';
1041
+
1042
+
1043
+
1044
+
1045
+
1046
+
1047
+
1048
+
1049
+
1050
+
1051
+
1052
+
733
1053
 
734
- export { TabMoreMenu, TabScrollButton, Tabs, TabsContext, Tabs$1 as tabsStyle, useComputedTabs, useTabsContext, useTabsScroller };
1054
+ export { TabMoreMenu, TabScrollButton, Tabs as tabsStyle, Tabs_Tabs as Tabs, useComputedTabs, useTabsScroller };
1055
+ export { TabsContext, useTabsContext } from "./chunks/8809.js";