@react-spectrum/s2 3.0.0-nightly-101d0772b-250113 → 3.0.0-nightly-1b425caa2-250114

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 (53) hide show
  1. package/dist/ActionBar.cjs +11 -8
  2. package/dist/ActionBar.cjs.map +1 -1
  3. package/dist/ActionBar.css +15 -32
  4. package/dist/ActionBar.css.map +1 -1
  5. package/dist/ActionBar.mjs +12 -9
  6. package/dist/ActionBar.mjs.map +1 -1
  7. package/dist/Modal.cjs +15 -21
  8. package/dist/Modal.cjs.map +1 -1
  9. package/dist/Modal.css +22 -46
  10. package/dist/Modal.css.map +1 -1
  11. package/dist/Modal.mjs +15 -21
  12. package/dist/Modal.mjs.map +1 -1
  13. package/dist/Popover.cjs +19 -37
  14. package/dist/Popover.cjs.map +1 -1
  15. package/dist/Popover.css +28 -99
  16. package/dist/Popover.css.map +1 -1
  17. package/dist/Popover.mjs +19 -37
  18. package/dist/Popover.mjs.map +1 -1
  19. package/dist/Tabs.cjs +177 -414
  20. package/dist/Tabs.cjs.map +1 -1
  21. package/dist/Tabs.css +120 -208
  22. package/dist/Tabs.css.map +1 -1
  23. package/dist/Tabs.mjs +179 -416
  24. package/dist/Tabs.mjs.map +1 -1
  25. package/dist/Tooltip.cjs +19 -16
  26. package/dist/Tooltip.cjs.map +1 -1
  27. package/dist/Tooltip.css +25 -40
  28. package/dist/Tooltip.css.map +1 -1
  29. package/dist/Tooltip.mjs +19 -16
  30. package/dist/Tooltip.mjs.map +1 -1
  31. package/dist/en-US.cjs +0 -1
  32. package/dist/en-US.cjs.map +1 -1
  33. package/dist/en-US.mjs +0 -1
  34. package/dist/en-US.mjs.map +1 -1
  35. package/dist/he-IL.cjs +0 -1
  36. package/dist/he-IL.cjs.map +1 -1
  37. package/dist/he-IL.mjs +0 -1
  38. package/dist/he-IL.mjs.map +1 -1
  39. package/dist/types.d.ts +7 -11
  40. package/dist/types.d.ts.map +1 -1
  41. package/package.json +19 -19
  42. package/src/ActionBar.tsx +15 -18
  43. package/src/Modal.tsx +17 -44
  44. package/src/Popover.tsx +21 -89
  45. package/src/Tabs.tsx +156 -427
  46. package/src/Tooltip.tsx +25 -42
  47. package/dist/TabsPicker.cjs +0 -388
  48. package/dist/TabsPicker.cjs.map +0 -1
  49. package/dist/TabsPicker.css +0 -440
  50. package/dist/TabsPicker.css.map +0 -1
  51. package/dist/TabsPicker.mjs +0 -382
  52. package/dist/TabsPicker.mjs.map +0 -1
  53. package/src/TabsPicker.tsx +0 -341
package/src/Tabs.tsx CHANGED
@@ -11,35 +11,29 @@
11
11
  */
12
12
 
13
13
  import {
14
- TabListProps as AriaTabListProps,
15
- TabPanel as AriaTabPanel,
16
- TabPanelProps as AriaTabPanelProps,
17
- TabProps as AriaTabProps,
18
- TabsProps as AriaTabsProps,
19
- CollectionRenderer,
20
- ContextValue,
21
- Provider,
22
- Tab as RACTab,
23
- TabList as RACTabList,
24
- Tabs as RACTabs,
25
- TabListStateContext,
26
- UNSTABLE_CollectionRendererContext,
27
- UNSTABLE_DefaultCollectionRenderer
28
- } from 'react-aria-components';
14
+ TabListProps as AriaTabListProps,
15
+ TabPanel as AriaTabPanel,
16
+ TabPanelProps as AriaTabPanelProps,
17
+ TabProps as AriaTabProps,
18
+ TabsProps as AriaTabsProps,
19
+ ContextValue,
20
+ Provider,
21
+ Tab as RACTab,
22
+ TabList as RACTabList,
23
+ Tabs as RACTabs,
24
+ TabListStateContext,
25
+ useSlottedContext
26
+ } from 'react-aria-components';
29
27
  import {centerBaseline} from './CenterBaseline';
30
- import {Collection, DOMRef, DOMRefValue, FocusableRef, FocusableRefValue, Key, Node, Orientation, RefObject} from '@react-types/shared';
31
- import {createContext, forwardRef, Fragment, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
32
- import {focusRing, size, style} from '../style' with {type: 'macro'};
28
+ import {Collection, DOMRef, DOMRefValue, Key, Node, Orientation} from '@react-types/shared';
29
+ import {createContext, forwardRef, ReactNode, useCallback, useContext, useEffect, useRef, useState} from 'react';
30
+ import {focusRing, style} from '../style' with {type: 'macro'};
33
31
  import {getAllowedOverrides, StyleProps, StylesPropWithHeight, UnsafeStyles} from './style-utils' with {type: 'macro'};
34
32
  import {IconContext} from './Icon';
35
- // @ts-ignore
36
- import intlMessages from '../intl/*.json';
37
- import {Picker, PickerItem} from './TabsPicker';
38
33
  import {Text, TextContext} from './Content';
39
- import {useControlledState} from '@react-stately/utils';
40
34
  import {useDOMRef} from '@react-spectrum/utils';
41
- import {useEffectEvent, useLayoutEffect, useResizeObserver} from '@react-aria/utils';
42
- import {useLocale, useLocalizedStringFormatter} from '@react-aria/i18n';
35
+ import {useLayoutEffect} from '@react-aria/utils';
36
+ import {useLocale} from '@react-aria/i18n';
43
37
  import {useSpectrumContextProps} from './useSpectrumContextProps';
44
38
 
45
39
  export interface TabsProps extends Omit<AriaTabsProps, 'className' | 'style' | 'children'>, UnsafeStyles {
@@ -51,21 +45,18 @@ export interface TabsProps extends Omit<AriaTabsProps, 'className' | 'style' | '
51
45
  * The amount of space between the tabs.
52
46
  * @default 'regular'
53
47
  */
54
- density?: 'compact' | 'regular',
55
- /**
56
- * Defines if the text within the tabs should be hidden and only the icon should be shown.
57
- * The text is always visible when the item is collapsed into a picker.
58
- * @default 'show'
59
- */
60
- labelBehavior?: 'show' | 'hide'
48
+ density?: 'compact' | 'regular'
61
49
  }
62
50
 
63
51
  export interface TabProps extends Omit<AriaTabProps, 'children' | 'style' | 'className'>, StyleProps {
64
52
  /** The content to display in the tab. */
65
- children: ReactNode
53
+ children?: ReactNode
66
54
  }
67
55
 
68
- export interface TabListProps<T> extends Omit<AriaTabListProps<T>, 'style' | 'className'>, StyleProps {}
56
+ export interface TabListProps<T> extends Omit<AriaTabListProps<T>, 'children' | 'style' | 'className'>, StyleProps {
57
+ /** The content to display in the tablist. */
58
+ children?: ReactNode
59
+ }
69
60
 
70
61
  export interface TabPanelProps extends Omit<AriaTabPanelProps, 'children' | 'style' | 'className'>, UnsafeStyles {
71
62
  /** Spectrum-defined styles, returned by the `style()` macro. */
@@ -75,64 +66,82 @@ export interface TabPanelProps extends Omit<AriaTabPanelProps, 'children' | 'sty
75
66
  }
76
67
 
77
68
  export const TabsContext = createContext<ContextValue<TabsProps, DOMRefValue<HTMLDivElement>>>(null);
78
- const InternalTabsContext = createContext<TabsProps & {onFocus:() => void, pickerRef?: FocusableRef<HTMLButtonElement>}>({onFocus: () => {}});
79
69
 
80
- const tabs = style({
70
+ const tabPanel = style({
71
+ marginTop: 4,
72
+ color: 'gray-800',
73
+ flexGrow: 1,
74
+ flexBasis: '[0%]',
75
+ minHeight: 0,
76
+ minWidth: 0
77
+ }, getAllowedOverrides({height: true}));
78
+
79
+ export function TabPanel(props: TabPanelProps) {
80
+ return (
81
+ <AriaTabPanel
82
+ {...props}
83
+ style={props.UNSAFE_style}
84
+ className={(props.UNSAFE_className || '') + tabPanel(null, props.styles)} />
85
+ );
86
+ }
87
+
88
+ const tab = style({
89
+ ...focusRing(),
81
90
  display: 'flex',
82
- flexShrink: 0,
83
- font: 'ui',
84
- flexDirection: {
85
- orientation: {
86
- horizontal: 'column'
91
+ color: {
92
+ default: 'neutral-subdued',
93
+ isSelected: 'neutral',
94
+ isHovered: 'neutral-subdued',
95
+ isDisabled: 'disabled',
96
+ forcedColors: {
97
+ isSelected: 'Highlight',
98
+ isDisabled: 'GrayText'
87
99
  }
100
+ },
101
+ borderRadius: 'sm',
102
+ gap: 'text-to-visual',
103
+ height: {
104
+ density: {
105
+ compact: 32,
106
+ regular: 48
107
+ }
108
+ },
109
+ alignItems: 'center',
110
+ position: 'relative',
111
+ cursor: 'default',
112
+ flexShrink: 0,
113
+ transition: 'default'
114
+ }, getAllowedOverrides());
115
+
116
+ const icon = style({
117
+ flexShrink: 0,
118
+ '--iconPrimary': {
119
+ type: 'fill',
120
+ value: 'currentColor'
88
121
  }
89
- }, getAllowedOverrides({height: true}));
122
+ });
90
123
 
91
- /**
92
- * Tabs organize content into multiple sections and allow users to navigate between them. The content under the set of tabs should be related and form a coherent unit.
93
- */
94
- export const Tabs = forwardRef(function Tabs(props: TabsProps, ref: DOMRef<HTMLDivElement>) {
95
- [props, ref] = useSpectrumContextProps(props, ref, TabsContext);
96
- let {
97
- density = 'regular',
98
- isDisabled,
99
- disabledKeys,
100
- orientation = 'horizontal',
101
- labelBehavior = 'show'
102
- } = props;
103
- let domRef = useDOMRef(ref);
104
- let [value, setValue] = useControlledState(props.selectedKey, props.defaultSelectedKey ?? null!, props.onSelectionChange);
105
- let pickerRef = useRef<FocusableRefValue<HTMLButtonElement>>(null);
124
+ export function Tab(props: TabProps) {
125
+ let {density} = useSlottedContext(TabsContext) ?? {};
106
126
 
107
127
  return (
108
- <Provider
109
- values={[
110
- [InternalTabsContext, {
111
- density,
112
- isDisabled,
113
- orientation,
114
- disabledKeys,
115
- selectedKey: value,
116
- onSelectionChange: setValue,
117
- labelBehavior,
118
- onFocus: () => pickerRef.current?.focus(),
119
- pickerRef
120
- }]
121
- ]}>
122
- <CollapsingCollection containerRef={domRef}>
123
- <RACTabs
124
- {...props}
125
- ref={domRef}
126
- selectedKey={value}
127
- onSelectionChange={setValue}
128
- style={props.UNSAFE_style}
129
- className={renderProps => (props.UNSAFE_className || '') + tabs({...renderProps}, props.styles)}>
130
- {props.children}
131
- </RACTabs>
132
- </CollapsingCollection>
133
- </Provider>
128
+ <RACTab
129
+ {...props}
130
+ style={props.UNSAFE_style}
131
+ className={renderProps => (props.UNSAFE_className || '') + tab({...renderProps, density}, props.styles)}>
132
+ <Provider
133
+ values={[
134
+ [TextContext, {styles: style({order: 1})}],
135
+ [IconContext, {
136
+ render: centerBaseline({slot: 'icon', styles: style({order: 0})}),
137
+ styles: icon
138
+ }]
139
+ ]}>
140
+ {typeof props.children === 'string' ? <Text>{props.children}</Text> : props.children}
141
+ </Provider>
142
+ </RACTab>
134
143
  );
135
- });
144
+ }
136
145
 
137
146
  const tablist = style({
138
147
  display: 'flex',
@@ -142,14 +151,6 @@ const tablist = style({
142
151
  density: {
143
152
  compact: 24,
144
153
  regular: 32
145
- },
146
- labelBehavior: {
147
- hide: {
148
- density: {
149
- compact: 16,
150
- regular: 24
151
- }
152
- }
153
154
  }
154
155
  }
155
156
  }
@@ -174,58 +175,63 @@ const tablist = style({
174
175
  });
175
176
 
176
177
  export function TabList<T extends object>(props: TabListProps<T>) {
177
- let {density, isDisabled, disabledKeys, orientation, labelBehavior, onFocus} = useContext(InternalTabsContext) ?? {};
178
- let {showItems} = useContext(CollapseContext) ?? {};
178
+ let {density, isDisabled, disabledKeys, orientation} = useSlottedContext(TabsContext) ?? {};
179
179
  let state = useContext(TabListStateContext);
180
180
  let [selectedTab, setSelectedTab] = useState<HTMLElement | undefined>(undefined);
181
181
  let tablistRef = useRef<HTMLDivElement>(null);
182
182
 
183
183
  useLayoutEffect(() => {
184
- if (tablistRef?.current && showItems) {
184
+ if (tablistRef?.current) {
185
185
  let tab: HTMLElement | null = tablistRef.current.querySelector('[role=tab][data-selected=true]');
186
186
 
187
187
  if (tab != null) {
188
188
  setSelectedTab(tab);
189
189
  }
190
- } else if (tablistRef?.current) {
191
- let picker: HTMLElement | null = tablistRef.current.querySelector('button');
192
- if (picker != null) {
193
- setSelectedTab(picker);
194
- }
195
190
  }
196
- }, [tablistRef, state?.selectedItem?.key, showItems]);
197
-
198
- let prevFocused = useRef<boolean | undefined>(false);
199
- useLayoutEffect(() => {
200
- if (!showItems && !prevFocused.current && state?.selectionManager.isFocused) {
201
- onFocus();
202
- }
203
- prevFocused.current = state?.selectionManager.isFocused;
204
- }, [state?.selectionManager.isFocused, state?.selectionManager.focusedKey, showItems]);
191
+ }, [tablistRef, state?.selectedItem?.key]);
205
192
 
206
193
  return (
207
194
  <div
208
195
  style={props.UNSAFE_style}
209
196
  className={(props.UNSAFE_className || '') + style({position: 'relative'}, getAllowedOverrides())(null, props.styles)}>
210
- {showItems && orientation === 'vertical' &&
197
+ {orientation === 'vertical' &&
211
198
  <TabLine disabledKeys={disabledKeys} isDisabled={isDisabled} selectedTab={selectedTab} orientation={orientation} density={density} />}
212
199
  <RACTabList
213
200
  {...props}
214
201
  ref={tablistRef}
215
- className={renderProps => tablist({...renderProps, labelBehavior, density})} />
202
+ className={renderProps => tablist({...renderProps, density})} />
216
203
  {orientation === 'horizontal' &&
217
- <TabLine showItems={showItems} disabledKeys={disabledKeys} isDisabled={isDisabled} selectedTab={selectedTab} orientation={orientation} density={density} />}
204
+ <TabLine disabledKeys={disabledKeys} isDisabled={isDisabled} selectedTab={selectedTab} orientation={orientation} density={density} />}
218
205
  </div>
219
206
  );
220
207
  }
221
208
 
209
+ function isAllTabsDisabled<T>(collection: Collection<Node<T>> | null, disabledKeys: Set<Key>) {
210
+ let testKey: Key | null = null;
211
+ if (collection && collection.size > 0) {
212
+ testKey = collection.getFirstKey();
213
+
214
+ let index = 0;
215
+ while (testKey && index < collection.size) {
216
+ // We have to check if the item in the collection has a key in disabledKeys or has the isDisabled prop set directly on it
217
+ if (!disabledKeys.has(testKey) && !collection.getItem(testKey)?.props?.isDisabled) {
218
+ return false;
219
+ }
220
+
221
+ testKey = collection.getKeyAfter(testKey);
222
+ index++;
223
+ }
224
+ return true;
225
+ }
226
+ return false;
227
+ }
228
+
222
229
  interface TabLineProps {
223
230
  disabledKeys: Iterable<Key> | undefined,
224
231
  isDisabled: boolean | undefined,
225
232
  selectedTab: HTMLElement | undefined,
226
233
  orientation?: Orientation,
227
- density?: 'compact' | 'regular',
228
- showItems?: boolean
234
+ density?: 'compact' | 'regular'
229
235
  }
230
236
 
231
237
  const selectedIndicator = style({
@@ -270,9 +276,12 @@ function TabLine(props: TabLineProps) {
270
276
  let {direction} = useLocale();
271
277
  let state = useContext(TabListStateContext);
272
278
 
273
- let isDisabled = useMemo(() => {
274
- return isTabsDisabled || isAllTabsDisabled(state?.collection, disabledKeys ? new Set(disabledKeys) : new Set());
275
- }, [state?.collection, disabledKeys, isTabsDisabled]);
279
+ // We want to add disabled styling to the selection indicator only if all the Tabs are disabled
280
+ let [isDisabled, setIsDisabled] = useState<boolean>(false);
281
+ useEffect(() => {
282
+ let isDisabled = isTabsDisabled || isAllTabsDisabled(state?.collection || null, disabledKeys ? new Set(disabledKeys) : new Set(null));
283
+ setIsDisabled(isDisabled);
284
+ }, [state?.collection, disabledKeys, isTabsDisabled, setIsDisabled]);
276
285
 
277
286
  let [style, setStyle] = useState<{transform: string | undefined, width: string | undefined, height: string | undefined}>({
278
287
  transform: undefined,
@@ -305,330 +314,50 @@ function TabLine(props: TabLineProps) {
305
314
 
306
315
  useLayoutEffect(() => {
307
316
  onResize();
308
- }, [onResize, state?.selectedItem?.key, density]);
309
-
310
- let ref = useRef<HTMLElement | undefined>(selectedTab);
311
- // assign ref before the useResizeObserver useEffect runs
312
- useLayoutEffect(() => {
313
- ref.current = selectedTab;
314
- });
315
- useResizeObserver({ref, onResize});
317
+ }, [onResize, state?.selectedItem?.key, direction, orientation, density]);
316
318
 
317
319
  return (
318
320
  <div style={{...style}} className={selectedIndicator({isDisabled, orientation})} />
319
321
  );
320
322
  }
321
323
 
322
- const tab = style({
323
- ...focusRing(),
324
+ const tabs = style({
324
325
  display: 'flex',
325
- color: {
326
- default: 'neutral-subdued',
327
- isSelected: 'neutral',
328
- isHovered: 'neutral-subdued',
329
- isDisabled: 'disabled',
330
- forcedColors: {
331
- isSelected: 'Highlight',
332
- isDisabled: 'GrayText'
333
- }
334
- },
335
- borderRadius: 'sm',
336
- gap: 'text-to-visual',
337
- height: {
338
- density: {
339
- compact: 32,
340
- regular: 48
341
- }
342
- },
343
- alignItems: 'center',
344
- position: 'relative',
345
- cursor: 'default',
346
326
  flexShrink: 0,
347
- transition: 'default',
348
- paddingX: {
349
- labelBehavior: {
350
- hide: size(6)
327
+ fontFamily: 'sans',
328
+ fontWeight: 'normal',
329
+ flexDirection: {
330
+ orientation: {
331
+ horizontal: 'column'
351
332
  }
352
333
  }
353
- }, getAllowedOverrides());
354
-
355
- const icon = style({
356
- display: 'block',
357
- flexShrink: 0,
358
- '--iconPrimary': {
359
- type: 'fill',
360
- value: 'currentColor'
361
- }
362
- });
363
-
364
- export function Tab(props: TabProps) {
365
- let {density, labelBehavior} = useContext(InternalTabsContext) ?? {};
366
-
367
- return (
368
- <RACTab
369
- {...props}
370
- // @ts-ignore
371
- originalProps={props}
372
- style={props.UNSAFE_style}
373
- className={renderProps => (props.UNSAFE_className || '') + tab({...renderProps, density, labelBehavior}, props.styles)}>
374
- {({
375
- // @ts-ignore
376
- isMenu
377
- }) => {
378
- if (isMenu) {
379
- return props.children;
380
- } else {
381
- return (
382
- <Provider
383
- values={[
384
- [TextContext, {
385
- styles:
386
- style({
387
- order: 1,
388
- display: {
389
- labelBehavior: {
390
- hide: 'none'
391
- }
392
- }
393
- })({labelBehavior})
394
- }],
395
- [IconContext, {
396
- render: centerBaseline({slot: 'icon', styles: style({order: 0})}),
397
- styles: icon
398
- }]
399
- ]}>
400
- {typeof props.children === 'string' ? <Text>{props.children}</Text> : props.children}
401
- </Provider>
402
- );
403
- }
404
- }}
405
- </RACTab>
406
- );
407
- }
408
-
409
- const tabPanel = style({
410
- marginTop: 4,
411
- color: 'gray-800',
412
- flexGrow: 1,
413
- flexBasis: '[0%]',
414
- minHeight: 0,
415
- minWidth: 0
416
334
  }, getAllowedOverrides({height: true}));
417
335
 
418
- export function TabPanel(props: TabPanelProps) {
336
+ /**
337
+ * Tabs organize content into multiple sections and allow users to navigate between them. The content under the set of tabs should be related and form a coherent unit.
338
+ */
339
+ export const Tabs = forwardRef(function Tabs(props: TabsProps, ref: DOMRef<HTMLDivElement>) {
340
+ [props, ref] = useSpectrumContextProps(props, ref, TabsContext);
341
+ let {
342
+ density = 'regular',
343
+ isDisabled,
344
+ disabledKeys,
345
+ orientation = 'horizontal'
346
+ } = props;
347
+ let domRef = useDOMRef(ref);
348
+
419
349
  return (
420
- <AriaTabPanel
350
+ <RACTabs
421
351
  {...props}
352
+ ref={domRef}
422
353
  style={props.UNSAFE_style}
423
- className={(props.UNSAFE_className || '') + tabPanel(null, props.styles)} />
424
- );
425
- }
426
-
427
- function isAllTabsDisabled<T>(collection: Collection<Node<T>> | undefined, disabledKeys: Set<Key>) {
428
- let testKey: Key | null = null;
429
- if (collection && collection.size > 0) {
430
- testKey = collection.getFirstKey();
431
-
432
- let index = 0;
433
- while (testKey && index < collection.size) {
434
- // We have to check if the item in the collection has a key in disabledKeys or has the isDisabled prop set directly on it
435
- if (!disabledKeys.has(testKey) && !collection.getItem(testKey)?.props?.isDisabled) {
436
- return false;
437
- }
438
-
439
- testKey = collection.getKeyAfter(testKey);
440
- index++;
441
- }
442
- return true;
443
- }
444
- return false;
445
- }
446
-
447
- let HiddenTabs = function (props: {
448
- listRef: RefObject<HTMLDivElement | null>,
449
- items: Array<Node<any>>,
450
- size?: string,
451
- density?: 'compact' | 'regular'
452
- }) {
453
- let {listRef, items, size, density} = props;
454
-
455
- return (
456
- <div
457
- // @ts-ignore
458
- inert="true"
459
- ref={listRef}
460
- className={style({
461
- display: '[inherit]',
462
- flexDirection: '[inherit]',
463
- gap: '[inherit]',
464
- flexWrap: '[inherit]',
465
- position: 'absolute',
466
- inset: 0,
467
- visibility: 'hidden',
468
- overflow: 'hidden',
469
- opacity: 0
470
- })}>
471
- {items.map((item) => {
472
- // pull off individual props as an allow list, don't want refs or other props getting through
473
- return (
474
- <div
475
- data-hidden-tab
476
- style={item.props.UNSAFE_style}
477
- key={item.key}
478
- className={item.props.className({size, density})}>
479
- {item.props.children({size, density})}
480
- </div>
481
- );
482
- })}
483
- </div>
354
+ className={renderProps => (props.UNSAFE_className || '') + tabs({...renderProps}, props.styles)}>
355
+ <Provider
356
+ values={[
357
+ [TabsContext, {density, isDisabled, disabledKeys, orientation}]
358
+ ]}>
359
+ {props.children}
360
+ </Provider>
361
+ </RACTabs>
484
362
  );
485
- };
486
-
487
- let TabsMenu = (props: {items: Array<Node<any>>, onSelectionChange: TabsProps['onSelectionChange']}) => {
488
- let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');
489
- let {items} = props;
490
- let {density, onSelectionChange, selectedKey, isDisabled, disabledKeys, pickerRef, labelBehavior} = useContext(InternalTabsContext);
491
- let state = useContext(TabListStateContext);
492
- let allKeysDisabled = useMemo(() => {
493
- return isAllTabsDisabled(state?.collection, disabledKeys ? new Set(disabledKeys) : new Set());
494
- }, [state?.collection, disabledKeys]);
495
-
496
- return (
497
- <UNSTABLE_CollectionRendererContext.Provider value={UNSTABLE_DefaultCollectionRenderer}>
498
- <div
499
- className={style({
500
- display: 'flex',
501
- alignItems: 'center',
502
- height: {
503
- density: {
504
- compact: 32,
505
- regular: 48
506
- }
507
- }})({density})}>
508
- <Picker
509
- ref={pickerRef ? pickerRef : undefined}
510
- isDisabled={isDisabled || allKeysDisabled}
511
- density={density!}
512
- labelBehavior={labelBehavior}
513
- items={items}
514
- disabledKeys={disabledKeys}
515
- selectedKey={selectedKey}
516
- onSelectionChange={onSelectionChange}
517
- aria-label={stringFormatter.format('tabs.selectorLabel')}>
518
- {(item: Node<any>) => {
519
- // need to determine the best way to handle icon only -> icon and text
520
- // good enough to aria-label the picker item?
521
- return (
522
- <PickerItem
523
- {...item.props.originalProps}
524
- isDisabled={isDisabled || allKeysDisabled}
525
- key={item.key}>
526
- {item.props.children({density, isMenu: true})}
527
- </PickerItem>
528
- );
529
- }}
530
- </Picker>
531
- </div>
532
- </UNSTABLE_CollectionRendererContext.Provider>
533
- );
534
- };
535
-
536
- // Context for passing the count for the custom renderer
537
- let CollapseContext = createContext<{
538
- containerRef: RefObject<HTMLDivElement | null>,
539
- showItems: boolean,
540
- setShowItems:(value: boolean) => void
541
- } | null>(null);
542
-
543
- function CollapsingCollection({children, containerRef}) {
544
- let [showItems, _setShowItems] = useState(true);
545
- let {orientation} = useContext(InternalTabsContext);
546
- let setShowItems = useCallback((value: boolean) => {
547
- if (orientation === 'vertical') {
548
- // if orientation is vertical, we always show the items
549
- _setShowItems(true);
550
- } else {
551
- _setShowItems(value);
552
- }
553
- }, [orientation]);
554
- let ctx = useMemo(() => ({
555
- containerRef,
556
- showItems: orientation === 'vertical' ? true : showItems,
557
- setShowItems
558
- }), [containerRef, showItems, setShowItems]);
559
- return (
560
- <CollapseContext.Provider value={ctx}>
561
- <UNSTABLE_CollectionRendererContext.Provider value={CollapsingCollectionRenderer}>
562
- {children}
563
- </UNSTABLE_CollectionRendererContext.Provider>
564
- </CollapseContext.Provider>
565
- );
566
- }
567
-
568
- let CollapsingCollectionRenderer: CollectionRenderer = {
569
- CollectionRoot({collection}) {
570
- return useCollectionRender(collection);
571
- },
572
- CollectionBranch({collection}) {
573
- return useCollectionRender(collection);
574
- }
575
- };
576
-
577
-
578
- let useCollectionRender = (collection: Collection<Node<unknown>>) => {
579
- let {containerRef, showItems, setShowItems} = useContext(CollapseContext) ?? {};
580
- let {density = 'regular', orientation = 'horizontal', onSelectionChange} = useContext(InternalTabsContext);
581
- let {direction} = useLocale();
582
-
583
- let children = useMemo(() => {
584
- let result: Node<any>[] = [];
585
- for (let key of collection.getKeys()) {
586
- result.push(collection.getItem(key)!);
587
- }
588
- return result;
589
- }, [collection]);
590
-
591
- let listRef = useRef<HTMLDivElement | null>(null);
592
- let updateOverflow = useEffectEvent(() => {
593
- if (orientation === 'vertical' || !listRef.current || !containerRef?.current) {
594
- return;
595
- }
596
- let container = listRef.current;
597
- let containerRect = container.getBoundingClientRect();
598
- let tabs = container.querySelectorAll('[data-hidden-tab]');
599
- let lastTab = tabs[tabs.length - 1];
600
- let lastTabRect = lastTab.getBoundingClientRect();
601
- if (direction === 'ltr') {
602
- setShowItems?.(lastTabRect.right <= containerRect.right);
603
- } else {
604
- setShowItems?.(lastTabRect.left >= containerRect.left);
605
- }
606
- });
607
-
608
- useResizeObserver({ref: containerRef, onResize: updateOverflow});
609
-
610
- useLayoutEffect(() => {
611
- if (collection.size > 0) {
612
- queueMicrotask(updateOverflow);
613
- }
614
- }, [collection.size, updateOverflow]);
615
-
616
- useEffect(() => {
617
- // Recalculate visible tags when fonts are loaded.
618
- document.fonts?.ready.then(() => updateOverflow());
619
- // eslint-disable-next-line react-hooks/exhaustive-deps
620
- }, []);
621
-
622
- return (
623
- <>
624
- <HiddenTabs items={children} density={density} listRef={listRef} />
625
- {showItems ? (
626
- children.map(node => <Fragment key={node.key}>{node.render?.(node)}</Fragment>)
627
- ) : (
628
- <>
629
- <TabsMenu items={children} onSelectionChange={onSelectionChange} />
630
- </>
631
- )}
632
- </>
633
- );
634
- };
363
+ });