@itwin/itwinui-react 3.0.0-dev.11 → 3.0.0-dev.13

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 (50) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/cjs/core/ColorPicker/ColorBuilder.js +2 -0
  3. package/cjs/core/ColorPicker/ColorInputPanel.js +24 -4
  4. package/cjs/core/ColorPicker/ColorPalette.js +2 -80
  5. package/cjs/core/ColorPicker/ColorSwatch.d.ts +1 -1
  6. package/cjs/core/ColorPicker/ColorSwatch.js +25 -15
  7. package/cjs/core/DropdownMenu/DropdownMenu.d.ts +2 -2
  8. package/cjs/core/LabeledSelect/LabeledSelect.d.ts +1 -1
  9. package/cjs/core/LabeledSelect/LabeledSelect.js +3 -3
  10. package/cjs/core/Popover/Popover.d.ts +26 -6
  11. package/cjs/core/Popover/Popover.js +38 -24
  12. package/cjs/core/Select/Select.d.ts +1 -1
  13. package/cjs/core/Select/Select.js +6 -4
  14. package/cjs/core/Surface/Surface.d.ts +8 -0
  15. package/cjs/core/Surface/Surface.js +19 -1
  16. package/cjs/core/Table/columns/expanderColumn.js +1 -0
  17. package/cjs/core/Tabs/Tabs.d.ts +222 -52
  18. package/cjs/core/Tabs/Tabs.js +436 -376
  19. package/cjs/core/ThemeProvider/ThemeProvider.js +3 -1
  20. package/cjs/index.d.ts +1 -2
  21. package/cjs/index.js +1 -2
  22. package/cjs/styles.js +4 -2
  23. package/esm/core/ColorPicker/ColorBuilder.js +2 -0
  24. package/esm/core/ColorPicker/ColorInputPanel.js +25 -5
  25. package/esm/core/ColorPicker/ColorPalette.js +3 -83
  26. package/esm/core/ColorPicker/ColorSwatch.d.ts +1 -1
  27. package/esm/core/ColorPicker/ColorSwatch.js +18 -12
  28. package/esm/core/DropdownMenu/DropdownMenu.d.ts +2 -2
  29. package/esm/core/DropdownMenu/DropdownMenu.js +1 -1
  30. package/esm/core/LabeledSelect/LabeledSelect.d.ts +1 -1
  31. package/esm/core/LabeledSelect/LabeledSelect.js +3 -2
  32. package/esm/core/Popover/Popover.d.ts +26 -6
  33. package/esm/core/Popover/Popover.js +40 -24
  34. package/esm/core/Select/Select.d.ts +1 -1
  35. package/esm/core/Select/Select.js +3 -3
  36. package/esm/core/Surface/Surface.d.ts +8 -0
  37. package/esm/core/Surface/Surface.js +19 -1
  38. package/esm/core/Table/columns/expanderColumn.js +1 -0
  39. package/esm/core/Tabs/Tabs.d.ts +222 -52
  40. package/esm/core/Tabs/Tabs.js +429 -369
  41. package/esm/core/ThemeProvider/ThemeProvider.js +3 -1
  42. package/esm/index.d.ts +1 -2
  43. package/esm/index.js +1 -2
  44. package/esm/styles.js +4 -2
  45. package/package.json +2 -2
  46. package/styles.css +13 -13
  47. package/cjs/core/Tabs/Tab.d.ts +0 -40
  48. package/cjs/core/Tabs/Tab.js +0 -65
  49. package/esm/core/Tabs/Tab.d.ts +0 -40
  50. package/esm/core/Tabs/Tab.js +0 -57
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  Object.defineProperty(exports, '__esModule', { value: true });
3
- exports.Tabs = void 0;
3
+ exports.Tabs = exports.Tab = void 0;
4
4
  const tslib_1 = require('tslib');
5
5
  /*---------------------------------------------------------------------------------------------
6
6
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
@@ -9,450 +9,510 @@ const tslib_1 = require('tslib');
9
9
  const classnames_1 = tslib_1.__importDefault(require('classnames'));
10
10
  const React = tslib_1.__importStar(require('react'));
11
11
  const index_js_1 = require('../utils/index.js');
12
- const Tab_js_1 = require('./Tab.js');
13
- const styles_js_1 = tslib_1.__importDefault(require('../../styles.js'));
14
- /**
15
- * Tabs organize and allow navigation between groups of content that are related and at the same level of hierarchy.
16
- * @example
17
- * const tabs = [
18
- * <Tab label='Label 1' />,
19
- * <Tab label='Label 2' />,
20
- * <Tab label='Label 3' />,
21
- * ];
22
- * <Tabs labels={tabs} />
23
- *
24
- * @example
25
- * <Tabs orientation='vertical' labels={tabs} />
26
- *
27
- * @example
28
- * const tabsWithSublabels = [
29
- * <Tab label='Label 1' sublabel='First tab' />,
30
- * <Tab label='Label 2' sublabel='Active tab' />,
31
- * ];
32
- * <Tabs labels={tabsWithSublabels} activeIndex={1} />
33
- *
34
- * @example
35
- * const tabsWithIcons = [
36
- * <Tab label='Label 1' icon={<SvgPlaceholder />} />,
37
- * <Tab label='Label 2' icon={<SvgPlaceholder />} />,
38
- * ];
39
- * <Tabs labels={tabsWithIcons} type='pill' />
40
- */
41
- const Tabs = (props) => {
42
- // Separate actions from props to avoid adding it to the DOM (using {...rest})
43
- let actions;
44
- if (props.type !== 'pill' && props.actions) {
45
- actions = props.actions;
46
- props = { ...props };
47
- delete props.actions;
48
- }
49
- // Separate overflowOptions from props to avoid adding it to the DOM (using {...rest})
50
- let overflowOptions;
51
- if (
52
- props.type !== 'borderless' &&
53
- props.type !== 'pill' &&
54
- props.overflowOptions
55
- ) {
56
- overflowOptions = props.overflowOptions;
57
- props = { ...props };
58
- delete props.overflowOptions;
59
- }
12
+ const Icon_js_1 = require('../Icon/Icon.js');
13
+ // Checking user motion preference for scroll into view animation
14
+ const isMotionOk = () =>
15
+ (0, index_js_1.getWindow)()?.matchMedia?.(
16
+ '(prefers-reduced-motion: no-preference)',
17
+ )?.matches;
18
+ const TabsWrapper = React.forwardRef((props, ref) => {
60
19
  const {
61
- labels,
62
- activeIndex,
63
- onTabSelected,
64
- focusActivationMode = 'auto',
20
+ className,
21
+ children,
22
+ orientation = 'horizontal',
65
23
  type = 'default',
24
+ focusActivationMode = 'auto',
66
25
  color = 'blue',
67
- orientation = 'horizontal',
68
- tabsClassName,
69
- contentClassName,
70
- wrapperClassName,
71
- children,
26
+ defaultValue,
27
+ value: activeValueProp,
28
+ onValueChange,
72
29
  ...rest
73
30
  } = props;
31
+ const [activeValue, setActiveValue] = (0, index_js_1.useControlledState)(
32
+ defaultValue,
33
+ activeValueProp,
34
+ onValueChange,
35
+ );
36
+ const [stripeProperties, setStripeProperties] = React.useState({});
37
+ const [hasSublabel, setHasSublabel] = React.useState(false); // used for setting size
38
+ const idPrefix = (0, index_js_1.useId)();
39
+ return React.createElement(
40
+ index_js_1.Box,
41
+ {
42
+ className: (0, classnames_1.default)(
43
+ 'iui-tabs-wrapper',
44
+ `iui-${orientation}`,
45
+ className,
46
+ ),
47
+ ...rest,
48
+ style: { ...stripeProperties, ...props?.style },
49
+ ref: ref,
50
+ },
51
+ React.createElement(
52
+ TabsContext.Provider,
53
+ {
54
+ value: {
55
+ orientation,
56
+ type,
57
+ activeValue,
58
+ setActiveValue,
59
+ setStripeProperties,
60
+ idPrefix,
61
+ focusActivationMode,
62
+ hasSublabel,
63
+ setHasSublabel,
64
+ color,
65
+ },
66
+ },
67
+ children,
68
+ ),
69
+ );
70
+ });
71
+ TabsWrapper.displayName = 'Tabs.Wrapper';
72
+ const TabList = React.forwardRef((props, ref) => {
73
+ const { className, children, ...rest } = props;
74
+ const { type, hasSublabel, color } = (0, index_js_1.useSafeContext)(
75
+ TabsContext,
76
+ );
74
77
  const isClient = (0, index_js_1.useIsClient)();
75
78
  const tablistRef = React.useRef(null);
76
79
  const [tablistSizeRef, tabsWidth] = (0, index_js_1.useContainerWidth)(
77
80
  type !== 'default',
78
81
  );
79
- const refs = (0, index_js_1.useMergedRefs)(tablistRef, tablistSizeRef);
80
- const [currentActiveIndex, setCurrentActiveIndex] = React.useState(() =>
81
- activeIndex != null
82
- ? (0, index_js_1.getBoundedValue)(activeIndex, 0, labels.length - 1)
83
- : 0,
82
+ const refs = (0, index_js_1.useMergedRefs)(ref, tablistRef, tablistSizeRef);
83
+ return React.createElement(
84
+ index_js_1.Box,
85
+ {
86
+ className: (0, classnames_1.default)(
87
+ 'iui-tabs',
88
+ `iui-${type}`,
89
+ {
90
+ 'iui-green': color === 'green',
91
+ 'iui-animated': type !== 'default' && isClient,
92
+ 'iui-not-animated': type !== 'default' && !isClient,
93
+ 'iui-large': hasSublabel,
94
+ },
95
+ className,
96
+ ),
97
+ role: 'tablist',
98
+ ref: refs,
99
+ ...rest,
100
+ },
101
+ React.createElement(
102
+ TabListContext.Provider,
103
+ {
104
+ value: {
105
+ tabsWidth,
106
+ },
107
+ },
108
+ children,
109
+ ),
84
110
  );
111
+ });
112
+ TabList.displayName = 'Tabs.TabList';
113
+ const Tab = React.forwardRef((props, forwardedRef) => {
114
+ const { className, children, value, label, ...rest } = props;
115
+ const {
116
+ orientation,
117
+ activeValue,
118
+ setActiveValue,
119
+ type,
120
+ setStripeProperties,
121
+ idPrefix,
122
+ focusActivationMode,
123
+ } = (0, index_js_1.useSafeContext)(TabsContext);
124
+ const { tabsWidth } = (0, index_js_1.useSafeContext)(TabListContext);
125
+ const tabRef = React.useRef();
126
+ const isActive = activeValue === value;
85
127
  (0, index_js_1.useIsomorphicLayoutEffect)(() => {
86
- if (activeIndex != null && currentActiveIndex !== activeIndex) {
87
- setCurrentActiveIndex(
88
- (0, index_js_1.getBoundedValue)(activeIndex, 0, labels.length - 1),
89
- );
128
+ if (isActive) {
129
+ if (orientation === 'horizontal') {
130
+ tabRef.current?.scrollIntoView({
131
+ inline: 'center',
132
+ behavior: isMotionOk() ? 'smooth' : 'auto',
133
+ });
134
+ } else {
135
+ tabRef.current?.scrollIntoView({
136
+ block: 'center',
137
+ behavior: isMotionOk() ? 'smooth' : 'auto',
138
+ });
139
+ }
90
140
  }
91
- }, [activeIndex, currentActiveIndex, labels.length]);
141
+ }, [isActive]);
142
+ const updateStripe = () => {
143
+ const currentTabRect = tabRef.current?.getBoundingClientRect();
144
+ setStripeProperties({
145
+ '--iui-tabs-stripe-size':
146
+ orientation === 'horizontal'
147
+ ? `${currentTabRect?.width}px`
148
+ : `${currentTabRect?.height}px`,
149
+ '--iui-tabs-stripe-position':
150
+ orientation === 'horizontal'
151
+ ? `${tabRef.current?.offsetLeft}px`
152
+ : `${tabRef.current?.offsetTop}px`,
153
+ });
154
+ };
92
155
  // CSS custom properties to place the active stripe
93
- const [stripeProperties, setStripeProperties] = React.useState({});
94
156
  (0, index_js_1.useIsomorphicLayoutEffect)(() => {
95
- if (type !== 'default' && tablistRef.current != undefined) {
96
- const activeTab = tablistRef.current.children[currentActiveIndex];
97
- const activeTabRect = activeTab.getBoundingClientRect();
98
- setStripeProperties({
99
- ...(orientation === 'horizontal' && {
100
- '--stripe-width': `${activeTabRect.width}px`,
101
- '--stripe-left': `${activeTab.offsetLeft}px`,
102
- }),
103
- ...(orientation === 'vertical' && {
104
- '--stripe-height': `${activeTabRect.height}px`,
105
- '--stripe-top': `${activeTab.offsetTop}px`,
106
- }),
107
- });
157
+ if (type !== 'default' && isActive) {
158
+ updateStripe();
108
159
  }
109
- }, [currentActiveIndex, type, orientation, tabsWidth]);
110
- const [focusedIndex, setFocusedIndex] = React.useState();
111
- React.useEffect(() => {
112
- if (tablistRef.current && focusedIndex !== undefined) {
113
- const tab = tablistRef.current.querySelectorAll(
114
- `.${styles_js_1.default['iui-tab']}`,
115
- )[focusedIndex];
116
- tab?.focus();
117
- }
118
- }, [focusedIndex]);
119
- const [hasSublabel, setHasSublabel] = React.useState(false); // used for setting size
120
- (0, index_js_1.useIsomorphicLayoutEffect)(() => {
121
- setHasSublabel(
122
- type !== 'pill' && // pill tabs should never have sublabels
123
- !!tablistRef.current?.querySelector(
124
- `.${styles_js_1.default['iui-tab-description']}`,
125
- ),
126
- );
127
- }, [type]);
128
- const enableHorizontalScroll = React.useCallback((e) => {
129
- const ownerDoc = tablistRef.current;
130
- if (ownerDoc === null) {
131
- return;
132
- }
133
- let scrollLeft = ownerDoc?.scrollLeft ?? 0;
134
- if (e.deltaY > 0 || e.deltaX > 0) {
135
- scrollLeft += 25;
136
- } else if (e.deltaY < 0 || e.deltaX < 0) {
137
- scrollLeft -= 25;
138
- }
139
- ownerDoc.scrollLeft = scrollLeft;
140
- }, []);
141
- // allow normal mouse wheels to scroll horizontally for horizontal overflow
142
- React.useEffect(() => {
143
- const ownerDoc = tablistRef.current;
144
- if (ownerDoc === null) {
145
- return;
146
- }
147
- if (!overflowOptions?.useOverflow || orientation === 'vertical') {
148
- ownerDoc.removeEventListener('wheel', enableHorizontalScroll);
149
- return;
150
- }
151
- ownerDoc.addEventListener('wheel', enableHorizontalScroll);
152
- }, [overflowOptions?.useOverflow, orientation, enableHorizontalScroll]);
153
- const isTabHidden = (activeTab, isVertical) => {
154
- const ownerDoc = tablistRef.current;
155
- if (ownerDoc === null) {
156
- return;
157
- }
158
- const fadeBuffer = isVertical
159
- ? ownerDoc.offsetHeight * 0.05
160
- : ownerDoc.offsetWidth * 0.05;
161
- const visibleStart = isVertical ? ownerDoc.scrollTop : ownerDoc.scrollLeft;
162
- const visibleEnd = isVertical
163
- ? ownerDoc.scrollTop + ownerDoc.offsetHeight
164
- : ownerDoc.scrollLeft + ownerDoc.offsetWidth;
165
- const tabStart = isVertical ? activeTab.offsetTop : activeTab.offsetLeft;
166
- const tabEnd = isVertical
167
- ? activeTab.offsetTop + activeTab.offsetHeight
168
- : activeTab.offsetLeft + activeTab.offsetWidth;
169
- if (
170
- tabStart > visibleStart + fadeBuffer &&
171
- tabEnd < visibleEnd - fadeBuffer
172
- ) {
173
- return 0; // tab is visible
174
- } else if (tabStart < visibleStart + fadeBuffer) {
175
- return -1; // tab is before visible section
176
- } else {
177
- return 1; // tab is after visible section
178
- }
179
- };
180
- const easeInOutQuad = (time, beginning, change, duration) => {
181
- if ((time /= duration / 2) < 1) {
182
- return (change / 2) * time * time + beginning;
183
- }
184
- return (-change / 2) * (--time * (time - 2) - 1) + beginning;
185
- };
186
- const scrollToTab = React.useCallback(
187
- (list, activeTab, duration, isVertical, tabPlacement) => {
188
- const start = isVertical ? list.scrollTop : list.scrollLeft;
189
- let change = 0;
190
- let currentTime = 0;
191
- const increment = 20;
192
- const fadeBuffer = isVertical
193
- ? list.offsetHeight * 0.05
194
- : list.offsetWidth * 0.05;
195
- if (tabPlacement < 0) {
196
- // if tab is before visible section
197
- change = isVertical
198
- ? activeTab.offsetTop - list.scrollTop
199
- : activeTab.offsetLeft - list.scrollLeft;
200
- change -= fadeBuffer; // give some space so the active tab isn't covered by the fade
201
- } else {
202
- // tab is after visible section
203
- change = isVertical
204
- ? activeTab.offsetTop -
205
- (list.scrollTop + list.offsetHeight) +
206
- activeTab.offsetHeight
207
- : activeTab.offsetLeft -
208
- (list.scrollLeft + list.offsetWidth) +
209
- activeTab.offsetWidth;
210
- change += fadeBuffer; // give some space so the active tab isn't covered by the fade
211
- }
212
- const animateScroll = () => {
213
- currentTime += increment;
214
- const val = easeInOutQuad(currentTime, start, change, duration);
215
- if (isVertical) {
216
- list.scrollTop = val;
217
- } else {
218
- list.scrollLeft = val;
219
- }
220
- if (currentTime < duration) {
221
- setTimeout(animateScroll, increment);
222
- }
223
- };
224
- animateScroll();
225
- },
226
- [],
227
- );
228
- // scroll to active tab if it is not visible with overflow
229
- (0, index_js_1.useIsomorphicLayoutEffect)(() => {
230
- setTimeout(() => {
231
- const ownerDoc = tablistRef.current;
232
- if (
233
- ownerDoc !== null &&
234
- overflowOptions?.useOverflow &&
235
- currentActiveIndex !== undefined
236
- ) {
237
- const activeTab = ownerDoc.querySelectorAll(
238
- `.${styles_js_1.default['iui-tab']}`,
239
- )[currentActiveIndex];
240
- const isVertical = orientation === 'vertical';
241
- const tabPlacement = isTabHidden(activeTab, isVertical);
242
- if (tabPlacement) {
243
- scrollToTab(ownerDoc, activeTab, 100, isVertical, tabPlacement);
244
- }
245
- }
246
- }, 50);
247
160
  }, [
248
- overflowOptions?.useOverflow,
249
- currentActiveIndex,
250
- focusedIndex,
161
+ type,
251
162
  orientation,
252
- scrollToTab,
163
+ isActive,
164
+ tabsWidth, // to fix visual artifact on initial render
253
165
  ]);
254
- const [scrollingPlacement, setScrollingPlacement] = React.useState(undefined);
255
- const determineScrollingPlacement = React.useCallback(() => {
256
- const ownerDoc = tablistRef.current;
257
- if (ownerDoc === null) {
258
- return;
259
- }
260
- const isVertical = orientation === 'vertical';
261
- const visibleStart = isVertical ? ownerDoc.scrollTop : ownerDoc.scrollLeft;
262
- const visibleEnd = isVertical
263
- ? ownerDoc.scrollTop + ownerDoc.offsetHeight
264
- : ownerDoc.scrollLeft + ownerDoc.offsetWidth;
265
- const totalTabsSpace = isVertical
266
- ? ownerDoc.scrollHeight
267
- : ownerDoc.scrollWidth;
268
- if (
269
- Math.abs(visibleStart - 0) < 1 &&
270
- Math.abs(visibleEnd - totalTabsSpace) < 1
271
- ) {
272
- setScrollingPlacement(undefined);
273
- } else if (Math.abs(visibleStart - 0) < 1) {
274
- setScrollingPlacement('start');
275
- } else if (Math.abs(visibleEnd - totalTabsSpace) < 1) {
276
- setScrollingPlacement('end');
277
- } else {
278
- setScrollingPlacement('center');
279
- }
280
- }, [orientation, setScrollingPlacement]);
281
- // apply correct mask when tabs list is resized
282
- const [resizeRef] = (0, index_js_1.useResizeObserver)(
283
- determineScrollingPlacement,
284
- );
285
- resizeRef(tablistRef?.current);
286
- // check if overflow tabs are scrolled to far edges
287
- React.useEffect(() => {
288
- const ownerDoc = tablistRef.current;
289
- if (ownerDoc === null) {
290
- return;
291
- }
292
- if (!overflowOptions?.useOverflow) {
293
- ownerDoc.removeEventListener('scroll', determineScrollingPlacement);
294
- return;
295
- }
296
- ownerDoc.addEventListener('scroll', determineScrollingPlacement);
297
- }, [overflowOptions?.useOverflow, determineScrollingPlacement]);
298
- const onTabClick = React.useCallback(
299
- (index) => {
300
- if (onTabSelected) {
301
- onTabSelected(index);
302
- }
303
- setCurrentActiveIndex(index);
304
- },
305
- [onTabSelected],
306
- );
307
166
  const onKeyDown = (event) => {
308
- // alt + arrow keys are used by browser / assistive technologies
309
167
  if (event.altKey) {
310
168
  return;
311
169
  }
312
- const isTabDisabled = (index) => {
313
- const tab = labels[index];
314
- return React.isValidElement(tab) && tab.props.disabled;
315
- };
316
- let newIndex = focusedIndex ?? currentActiveIndex;
317
- /** focus next tab if delta is +1, previous tab if -1 */
318
- const focusTab = (delta = +1) => {
319
- do {
320
- newIndex = (newIndex + delta + labels.length) % labels.length;
321
- } while (isTabDisabled(newIndex) && newIndex !== focusedIndex);
322
- setFocusedIndex(newIndex);
323
- focusActivationMode === 'auto' && onTabClick(newIndex);
324
- };
170
+ const allTabs = Array.from(
171
+ event.currentTarget.parentElement?.children ?? [],
172
+ );
173
+ const nextTab = tabRef.current?.nextElementSibling ?? allTabs.at(0);
174
+ const previousTab =
175
+ tabRef.current?.previousElementSibling ?? allTabs.at(-1);
325
176
  switch (event.key) {
326
177
  case 'ArrowDown': {
327
178
  if (orientation === 'vertical') {
328
- focusTab(+1);
179
+ nextTab?.focus();
329
180
  event.preventDefault();
330
181
  }
331
182
  break;
332
183
  }
333
184
  case 'ArrowRight': {
334
185
  if (orientation === 'horizontal') {
335
- focusTab(+1);
186
+ nextTab?.focus();
336
187
  event.preventDefault();
337
188
  }
338
189
  break;
339
190
  }
340
191
  case 'ArrowUp': {
341
192
  if (orientation === 'vertical') {
342
- focusTab(-1);
193
+ previousTab?.focus();
343
194
  event.preventDefault();
344
195
  }
345
196
  break;
346
197
  }
347
198
  case 'ArrowLeft': {
348
199
  if (orientation === 'horizontal') {
349
- focusTab(-1);
200
+ previousTab?.focus();
350
201
  event.preventDefault();
351
202
  }
352
203
  break;
353
204
  }
354
- case 'Enter':
355
- case ' ':
356
- case 'Spacebar': {
357
- event.preventDefault();
358
- if (focusActivationMode === 'manual' && focusedIndex !== undefined) {
359
- onTabClick(focusedIndex);
360
- }
361
- break;
362
- }
363
205
  default:
364
206
  break;
365
207
  }
366
208
  };
367
- const createTab = React.useCallback(
368
- (label, index) => {
369
- const onClick = () => {
370
- setFocusedIndex(index);
371
- onTabClick(index);
372
- };
373
- return React.createElement(
374
- 'li',
375
- { key: index },
376
- !React.isValidElement(label)
377
- ? React.createElement(Tab_js_1.Tab, {
378
- label: label,
379
- className: (0, classnames_1.default)({
380
- 'iui-active': index === currentActiveIndex,
381
- }),
382
- tabIndex: index === currentActiveIndex ? 0 : -1,
383
- onClick: onClick,
384
- 'aria-selected': index === currentActiveIndex,
385
- })
386
- : React.cloneElement(label, {
387
- active: index === currentActiveIndex,
388
- 'aria-selected': index === currentActiveIndex,
389
- tabIndex: index === currentActiveIndex ? 0 : -1,
390
- onClick: (args) => {
391
- onClick();
392
- label.props.onClick?.(args);
393
- },
394
- }),
395
- );
209
+ // use first tab as active if no `value` passed.
210
+ const setInitialActiveRef = React.useCallback(
211
+ (element) => {
212
+ if (activeValue !== undefined) {
213
+ return;
214
+ }
215
+ if (element?.matches(':first-of-type')) {
216
+ setActiveValue(value);
217
+ }
396
218
  },
397
- [currentActiveIndex, onTabClick],
219
+ [activeValue, setActiveValue, value],
398
220
  );
221
+ return React.createElement(
222
+ index_js_1.ButtonBase,
223
+ {
224
+ className: (0, classnames_1.default)('iui-tab', className),
225
+ role: 'tab',
226
+ tabIndex: isActive ? 0 : -1,
227
+ 'aria-selected': isActive,
228
+ 'aria-controls': `${idPrefix}-panel-${value}`,
229
+ ref: (0, index_js_1.useMergedRefs)(
230
+ tabRef,
231
+ forwardedRef,
232
+ setInitialActiveRef,
233
+ ),
234
+ ...rest,
235
+ id: `${idPrefix}-tab-${value}`,
236
+ onClick: (0, index_js_1.mergeEventHandlers)(props.onClick, () =>
237
+ setActiveValue(value),
238
+ ),
239
+ onKeyDown: (0, index_js_1.mergeEventHandlers)(props.onKeyDown, onKeyDown),
240
+ onFocus: (0, index_js_1.mergeEventHandlers)(props.onFocus, () => {
241
+ if (focusActivationMode === 'auto' && !props.disabled) {
242
+ setActiveValue(value);
243
+ }
244
+ }),
245
+ },
246
+ label ? React.createElement(exports.Tabs.TabLabel, null, label) : children,
247
+ );
248
+ });
249
+ Tab.displayName = 'Tabs.Tab';
250
+ // ----------------------------------------------------------------------------
251
+ // Tabs.TabIcon component
252
+ const TabIcon = React.forwardRef((props, ref) => {
253
+ return React.createElement(Icon_js_1.Icon, {
254
+ ...props,
255
+ className: (0, classnames_1.default)('iui-tab-icon', props?.className),
256
+ ref: ref,
257
+ });
258
+ });
259
+ TabIcon.displayName = 'Tabs.TabIcon';
260
+ // ----------------------------------------------------------------------------
261
+ // Tabs.TabLabel component
262
+ const TabLabel = index_js_1.polymorphic.span('iui-tab-label');
263
+ TabLabel.displayName = 'Tabs.TabLabel';
264
+ // ----------------------------------------------------------------------------
265
+ // Tabs.TabDescription component
266
+ const TabDescription = React.forwardRef((props, ref) => {
267
+ const { className, children, ...rest } = props;
268
+ const { hasSublabel, setHasSublabel } = (0, index_js_1.useSafeContext)(
269
+ TabsContext,
270
+ );
271
+ (0, index_js_1.useIsomorphicLayoutEffect)(() => {
272
+ if (!hasSublabel) {
273
+ setHasSublabel(true);
274
+ }
275
+ }, [hasSublabel, setHasSublabel]);
399
276
  return React.createElement(
400
277
  index_js_1.Box,
401
278
  {
279
+ as: 'span',
280
+ className: (0, classnames_1.default)('iui-tab-description', className),
281
+ ref: ref,
282
+ ...rest,
283
+ },
284
+ children,
285
+ );
286
+ });
287
+ TabDescription.displayName = 'Tabs.TabDescription';
288
+ const TabsActions = React.forwardRef((props, ref) => {
289
+ const { wrapperProps, className, children, ...rest } = props;
290
+ return React.createElement(
291
+ index_js_1.Box,
292
+ {
293
+ ...wrapperProps,
402
294
  className: (0, classnames_1.default)(
403
- 'iui-tabs-wrapper',
404
- `iui-${orientation}`,
405
- wrapperClassName,
295
+ 'iui-tabs-actions-wrapper',
296
+ wrapperProps?.className,
406
297
  ),
407
- style: stripeProperties,
408
298
  },
409
299
  React.createElement(
410
300
  index_js_1.Box,
411
301
  {
412
- as: 'ul',
413
- className: (0, classnames_1.default)(
414
- 'iui-tabs',
415
- `iui-${type}`,
416
- {
417
- 'iui-green': color === 'green',
418
- 'iui-animated': type !== 'default' && isClient,
419
- 'iui-not-animated': type !== 'default' && !isClient,
420
- 'iui-large': hasSublabel,
421
- },
422
- tabsClassName,
423
- ),
424
- 'data-iui-overflow': overflowOptions?.useOverflow,
425
- 'data-iui-scroll-placement': scrollingPlacement,
426
- role: 'tablist',
427
- ref: refs,
428
- onKeyDown: onKeyDown,
302
+ className: (0, classnames_1.default)('iui-tabs-actions', className),
303
+ ref: ref,
429
304
  ...rest,
430
305
  },
431
- labels.map((label, index) => createTab(label, index)),
306
+ children,
432
307
  ),
433
- actions &&
434
- React.createElement(
435
- index_js_1.Box,
436
- { className: 'iui-tabs-actions-wrapper' },
437
- React.createElement(
438
- index_js_1.Box,
439
- { className: 'iui-tabs-actions' },
440
- actions,
441
- ),
442
- ),
308
+ );
309
+ });
310
+ TabsActions.displayName = 'Tabs.Actions';
311
+ const TabsPanel = React.forwardRef((props, ref) => {
312
+ const { value, className, children, ...rest } = props;
313
+ const { activeValue, idPrefix } = (0, index_js_1.useSafeContext)(TabsContext);
314
+ return React.createElement(
315
+ index_js_1.Box,
316
+ {
317
+ className: (0, classnames_1.default)('iui-tabs-content', className),
318
+ 'aria-labelledby': `${idPrefix}-tab-${value}`,
319
+ role: 'tabpanel',
320
+ hidden: activeValue !== value ? true : undefined,
321
+ ref: ref,
322
+ ...rest,
323
+ id: `${idPrefix}-panel-${value}`,
324
+ },
325
+ children,
326
+ );
327
+ });
328
+ TabsPanel.displayName = 'Tabs.Panel';
329
+ const LegacyTabsComponent = React.forwardRef((props, forwardedRef) => {
330
+ let actions;
331
+ if (props.type !== 'pill' && props.actions) {
332
+ actions = props.actions;
333
+ props = { ...props };
334
+ delete props.actions;
335
+ }
336
+ const {
337
+ labels,
338
+ onTabSelected,
339
+ focusActivationMode,
340
+ color,
341
+ activeIndex: activeIndexProp,
342
+ tabsClassName,
343
+ contentClassName,
344
+ wrapperClassName,
345
+ children,
346
+ ...rest
347
+ } = props;
348
+ const [activeIndex, setActiveIndex] = (0, index_js_1.useControlledState)(
349
+ 0,
350
+ activeIndexProp,
351
+ onTabSelected,
352
+ );
353
+ return React.createElement(
354
+ TabsWrapper,
355
+ {
356
+ className: wrapperClassName,
357
+ focusActivationMode: focusActivationMode,
358
+ color: color,
359
+ value: `${activeIndex}`,
360
+ onValueChange: (value) => setActiveIndex(Number(value)),
361
+ ...rest,
362
+ },
363
+ React.createElement(
364
+ TabList,
365
+ { className: tabsClassName, ref: forwardedRef },
366
+ labels.map((label, index) => {
367
+ const tabValue = `${index}`;
368
+ return React.isValidElement(label)
369
+ ? React.cloneElement(label, {
370
+ value: tabValue,
371
+ })
372
+ : React.createElement(LegacyTab, {
373
+ key: index,
374
+ value: tabValue,
375
+ label: label,
376
+ });
377
+ }),
378
+ ),
379
+ actions && React.createElement(TabsActions, null, actions),
443
380
  children &&
444
381
  React.createElement(
445
- index_js_1.Box,
446
- {
447
- className: (0, classnames_1.default)(
448
- 'iui-tabs-content',
449
- contentClassName,
450
- ),
451
- role: 'tabpanel',
452
- },
382
+ TabsPanel,
383
+ { value: `${activeIndex}`, className: contentClassName },
453
384
  children,
454
385
  ),
455
386
  );
456
- };
457
- exports.Tabs = Tabs;
387
+ });
388
+ LegacyTabsComponent.displayName = 'Tabs';
389
+ /**
390
+ * Legacy Tab component.
391
+ * For full functionality use composition API.
392
+ *
393
+ * Individual tab component to be used in the `labels` prop of `Tabs`.
394
+ * @example
395
+ * const tabs = [
396
+ * <Tab label='Label 1' sublabel='Description 1' />,
397
+ * <Tab label='Label 2' startIcon={<SvgPlaceholder />} />,
398
+ * ];
399
+ */
400
+ const LegacyTab = React.forwardRef((props, forwardedRef) => {
401
+ const { label, sublabel, startIcon, children, value, ...rest } = props;
402
+ return React.createElement(
403
+ React.Fragment,
404
+ null,
405
+ React.createElement(
406
+ Tab,
407
+ { ...rest, value: value, ref: forwardedRef },
408
+ startIcon && React.createElement(TabIcon, null, startIcon),
409
+ React.createElement(TabLabel, null, label),
410
+ sublabel && React.createElement(TabDescription, null, sublabel),
411
+ children,
412
+ ),
413
+ );
414
+ });
415
+ exports.Tab = LegacyTab;
416
+ /**
417
+ * Tabs organize and allow navigation between groups of content that are related and at the same level of hierarchy.
418
+ * `Tabs.Tab` and `Tabs.Panel` can be associated with each other by passing them the same `value`.
419
+ * @example
420
+ * <Tabs.Wrapper>
421
+ * <Tabs.TabList>
422
+ * <Tabs.Tab value='tab1' label='Label 1' />
423
+ * <Tabs.Tab value='tab2' label='Label 2' />
424
+ * <Tabs.Tab value='tab3' label='Label 3' />
425
+ * </Tabs.TabList>
426
+ * <Tabs.ActionsWrapper>
427
+ * <Tabs.Actions>
428
+ * <Button>Sample Button</Button>
429
+ * </Tabs.Actions>
430
+ * </Tabs.ActionsWrapper>
431
+ * <Tabs.Panel value='tab1'>Content 1</Tabs.Panel>
432
+ * <Tabs.Panel value='tab2'>Content 2</Tabs.Panel>
433
+ * <Tabs.Panel value='tab3'>Content 3</Tabs.Panel>
434
+ * </Tabs.Wrapper>
435
+ *
436
+ * @example
437
+ * <Tabs orientation='vertical'/>
438
+ *
439
+ * @example
440
+ * <Tabs.Wrapper focusActivationMode='manual'>
441
+ * <Tabs.Tab value='sample'>
442
+ * <Tabs.TabIcon>
443
+ * <SvgPlaceholder />
444
+ * </Tabs.TabIcon>
445
+ * <Tabs.TabLabel>Sample Label</Tabs.TabLabel>
446
+ * <Tabs.TabDescription>Sample Description</Tabs.TabDescription>
447
+ * </Tabs.Tab>
448
+ * </Tabs.Wrapper>
449
+ */
450
+ exports.Tabs = Object.assign(LegacyTabsComponent, {
451
+ /**
452
+ * A wrapper component for Tabs
453
+ */
454
+ Wrapper: TabsWrapper,
455
+ /**
456
+ * Tablist subcomponent which contains all of the tab subcomponents.
457
+ * @example
458
+ * <Tabs.TabList>
459
+ * <Tabs.Tab value='tab1' label='Label 1' />
460
+ * <Tabs.Tab value='tab2' label='Label 2' />
461
+ * <Tabs.Tab value='tab3' label='Label 3' />
462
+ * </Tabs.TabList>
463
+ *
464
+ * @example
465
+ * <Tabs.TabList>
466
+ * <Tabs.Tab value='tab1' label='Green Tab' />
467
+ * </Tabs.TabList>
468
+ *
469
+ * @example
470
+ * <Tabs.TabList focusActivationMode='manual'>
471
+ * <Tabs.Tab value='tab1' label='Manual Focus Tab' />
472
+ * </Tabs.TabList>
473
+ */
474
+ TabList: TabList,
475
+ /**
476
+ * Tab subcomponent which is used for each of the tabs.
477
+ * @example
478
+ * <Tabs.Tab value='tab1' label='Label 1' />
479
+ *
480
+ * @example
481
+ * <Tabs.Tab value='sample'>
482
+ * <Tabs.TabIcon>
483
+ * <SvgPlaceholder />
484
+ * </Tabs.TabIcon>
485
+ * <Tabs.TabLabel>Sample Label</Tabs.TabLabel>
486
+ * <Tabs.TabDescription>Sample Description</Tabs.TabDescription>
487
+ * </Tabs.Tab>
488
+ *
489
+ */
490
+ Tab: Tab,
491
+ /**
492
+ * Tab icon subcomponent which places an icon on the left side of the tab.
493
+ */
494
+ TabIcon: TabIcon,
495
+ /**
496
+ * Tab label subcomponent which holds the tab's label.
497
+ */
498
+ TabLabel: TabLabel,
499
+ /**
500
+ * Tab description subcomponent which places a description under the tab label.
501
+ */
502
+ TabDescription: TabDescription,
503
+ /**
504
+ * Tab actions subcomponent which contains action buttons that are placed at the end of the tabs.
505
+ */
506
+ Actions: TabsActions,
507
+ /**
508
+ * Tab panel subcomponent which contains the tab's content.
509
+ * @example
510
+ * <Tabs.Panel value='tab1'>
511
+ * Sample Panel
512
+ * </Tabs.Panel>
513
+ */
514
+ Panel: TabsPanel,
515
+ });
516
+ const TabsContext = React.createContext(undefined);
517
+ const TabListContext = React.createContext(undefined);
458
518
  exports.default = exports.Tabs;