@react-spectrum/tabs 3.0.0-beta.0 → 3.0.0-nightly-4980928d3-240906

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Tabs.tsx CHANGED
@@ -10,83 +10,102 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {classNames, SlotProvider, unwrapDOMRef, useDOMRef, useStyleProps, useValueEffect} from '@react-spectrum/utils';
14
- import {DOMProps, DOMRef, Node, Orientation} from '@react-types/shared';
13
+ import {AriaTabPanelProps, SpectrumTabListProps, SpectrumTabPanelsProps, SpectrumTabsProps} from '@react-types/tabs';
14
+ import {classNames, SlotProvider, unwrapDOMRef, useDOMRef, useStyleProps} from '@react-spectrum/utils';
15
+ import {DOMProps, DOMRef, DOMRefValue, Key, Node, Orientation, RefObject, StyleProps} from '@react-types/shared';
16
+ import {filterDOMProps, mergeProps, useId, useLayoutEffect, useResizeObserver} from '@react-aria/utils';
15
17
  import {FocusRing} from '@react-aria/focus';
16
18
  import {Item, Picker} from '@react-spectrum/picker';
17
- import {mergeProps, useId, useLayoutEffect} from '@react-aria/utils';
18
- import React, {HTMLAttributes, Key, MutableRefObject, ReactElement, useCallback, useEffect, useRef, useState} from 'react';
19
- import {SingleSelectListState} from '@react-stately/list';
19
+ import {ListCollection} from '@react-stately/list';
20
+ import React, {
21
+ CSSProperties,
22
+ HTMLAttributes,
23
+ ReactElement,
24
+ ReactNode,
25
+ useCallback,
26
+ useContext,
27
+ useEffect,
28
+ useRef,
29
+ useState
30
+ } from 'react';
20
31
  import {SpectrumPickerProps} from '@react-types/select';
21
- import {SpectrumTabsProps} from '@react-types/tabs';
22
32
  import styles from '@adobe/spectrum-css-temp/components/tabs/vars.css';
33
+ import {TabListState, useTabListState} from '@react-stately/tabs';
23
34
  import {Text} from '@react-spectrum/text';
35
+ import {useCollection} from '@react-stately/collections';
24
36
  import {useHover} from '@react-aria/interactions';
25
37
  import {useLocale} from '@react-aria/i18n';
26
38
  import {useProvider, useProviderProps} from '@react-spectrum/provider';
27
- import {useResizeObserver} from '@react-aria/utils';
28
- import {useTab, useTabs} from '@react-aria/tabs';
29
- import {useTabsState} from '@react-stately/tabs';
39
+ import {useTab, useTabList, useTabPanel} from '@react-aria/tabs';
40
+
41
+ interface TabsContext<T> {
42
+ tabProps: SpectrumTabsProps<T>,
43
+ tabState: {
44
+ tabListState: TabListState<T> | null,
45
+ setTabListState: (state: TabListState<T>) => void,
46
+ selectedTab: HTMLElement | null,
47
+ collapsed: boolean
48
+ },
49
+ refs: {
50
+ wrapperRef: RefObject<HTMLDivElement | null>,
51
+ tablistRef: RefObject<HTMLDivElement | null>
52
+ },
53
+ tabPanelProps: HTMLAttributes<HTMLElement>,
54
+ tabLineState: Array<DOMRect>
55
+ }
56
+
57
+ const TabContext = React.createContext<TabsContext<any> | null>(null);
30
58
 
31
59
  function Tabs<T extends object>(props: SpectrumTabsProps<T>, ref: DOMRef<HTMLDivElement>) {
32
60
  props = useProviderProps(props);
33
61
  let {
34
62
  orientation = 'horizontal' as Orientation,
35
- isDisabled,
36
- isQuiet,
37
63
  density = 'regular',
38
64
  children,
39
65
  ...otherProps
40
66
  } = props;
41
67
 
42
68
  let domRef = useDOMRef(ref);
43
- let tablistRef = useRef<HTMLDivElement>();
44
- let wrapperRef = useRef<HTMLDivElement>();
45
- let state = useTabsState(props);
69
+ let tablistRef = useRef<HTMLDivElement>(null);
70
+ let wrapperRef = useRef<HTMLDivElement>(null);
46
71
 
47
72
  let {direction} = useLocale();
48
73
  let {styleProps} = useStyleProps(otherProps);
49
- let {tabListProps, tabPanelProps} = useTabs(props, state, tablistRef);
50
- let [collapse, setCollapse] = useValueEffect(false);
51
- let [selectedTab, setSelectedTab] = useState<HTMLElement>();
74
+ let [collapsed, setCollapsed] = useState(false);
75
+ let [selectedTab, setSelectedTab] = useState<HTMLElement | null>(null);
76
+ const [tabListState, setTabListState] = useState<TabListState<T> | null>(null);
77
+ let [tabPositions, setTabPositions] = useState<DOMRect[]>([]);
78
+ let prevTabPositions = useRef<DOMRect[]>(tabPositions);
52
79
 
53
80
  useEffect(() => {
54
81
  if (tablistRef.current) {
55
- let selectedTab: HTMLElement = tablistRef.current.querySelector(`[data-key="${state.selectedKey}"]`);
82
+ let selectedTab: HTMLElement | null = tablistRef.current.querySelector(`[data-key="${CSS.escape(tabListState?.selectedKey?.toString() ?? '')}"]`);
56
83
 
57
84
  if (selectedTab != null) {
58
85
  setSelectedTab(selectedTab);
59
86
  }
60
87
  }
61
88
  // collapse is in the dep array so selectedTab can be updated for TabLine positioning
62
- }, [children, state.selectedKey, collapse, tablistRef]);
89
+ }, [children, tabListState?.selectedKey, collapsed, tablistRef]);
63
90
 
64
91
  let checkShouldCollapse = useCallback(() => {
65
- let computeShouldCollapse = () => {
66
- if (wrapperRef.current) {
67
- let tabsComponent = wrapperRef.current;
68
- let tabs = tablistRef.current.querySelectorAll('[role="tab"]');
69
- let lastTab = tabs[tabs.length - 1];
70
-
71
- let end = direction === 'rtl' ? 'left' : 'right';
72
- let farEdgeTabList = tabsComponent.getBoundingClientRect()[end];
73
- let farEdgeLastTab = lastTab?.getBoundingClientRect()[end];
74
- let shouldCollapse = direction === 'rtl' ? farEdgeLastTab < farEdgeTabList : farEdgeTabList < farEdgeLastTab;
75
-
76
- return shouldCollapse;
92
+ if (wrapperRef.current && orientation !== 'vertical') {
93
+ let tabsComponent = wrapperRef.current;
94
+ let tabs: NodeListOf<Element> = tablistRef.current?.querySelectorAll('[role="tab"]') ?? new NodeList() as NodeListOf<Element>;
95
+ let tabDimensions = [...tabs].map((tab: Element) => tab.getBoundingClientRect());
96
+
97
+ let end = direction === 'rtl' ? 'left' : 'right';
98
+ let farEdgeTabList = tabsComponent.getBoundingClientRect()[end];
99
+ let farEdgeLastTab = tabDimensions[tabDimensions.length - 1][end];
100
+ let shouldCollapse = direction === 'rtl' ? farEdgeLastTab < farEdgeTabList : farEdgeTabList < farEdgeLastTab;
101
+ setCollapsed(shouldCollapse);
102
+ if (tabDimensions.length !== prevTabPositions.current.length
103
+ || tabDimensions.some((box, index) => box?.left !== prevTabPositions.current[index]?.left || box?.right !== prevTabPositions.current[index]?.right)) {
104
+ setTabPositions(tabDimensions);
105
+ prevTabPositions.current = tabDimensions;
77
106
  }
78
- };
79
-
80
- if (orientation !== 'vertical') {
81
- setCollapse(function* () {
82
- // Make Tabs render in non-collapsed state
83
- yield false;
84
-
85
- // Compute if Tabs should collapse and update
86
- yield computeShouldCollapse();
87
- });
88
107
  }
89
- }, [tablistRef, wrapperRef, direction, orientation, setCollapse]);
108
+ }, [tablistRef, wrapperRef, direction, orientation, setCollapsed, prevTabPositions, setTabPositions]);
90
109
 
91
110
  useEffect(() => {
92
111
  checkShouldCollapse();
@@ -94,74 +113,63 @@ function Tabs<T extends object>(props: SpectrumTabsProps<T>, ref: DOMRef<HTMLDiv
94
113
 
95
114
  useResizeObserver({ref: wrapperRef, onResize: checkShouldCollapse});
96
115
 
116
+ let tabPanelProps: HTMLAttributes<HTMLElement> = {
117
+ 'aria-labelledby': undefined
118
+ };
119
+
97
120
  // When the tabs are collapsed, the tabPanel should be labelled by the Picker button element.
98
121
  let collapsibleTabListId = useId();
99
- if (collapse && orientation !== 'vertical') {
122
+ if (collapsed && orientation !== 'vertical') {
100
123
  tabPanelProps['aria-labelledby'] = collapsibleTabListId;
101
124
  }
102
-
103
125
  return (
104
- <div
105
- {...styleProps}
106
- ref={domRef}
107
- className={classNames(
108
- styles,
109
- 'spectrum-TabsPanel',
110
- `spectrum-TabsPanel--${orientation}`,
111
- styleProps.className
112
- )}>
113
- {orientation === 'vertical' &&
114
- <TabList
115
- {...tabListProps}
116
- ref={tablistRef}
117
- orientation={orientation}
118
- density={density}
119
- isQuiet={isQuiet}
120
- isDisabled={isDisabled}
121
- state={state}
122
- selectedTab={selectedTab} />
123
- }
124
- {orientation !== 'vertical' &&
125
- <CollapsibleTabList
126
- {...props}
127
- id={collapsibleTabListId}
128
- wrapperRef={wrapperRef}
129
- collapse={collapse}
130
- tabListProps={tabListProps}
131
- state={state}
132
- selectedTab={selectedTab}
133
- ref={tablistRef} />
134
- }
135
- <div {...tabPanelProps} className={classNames(styles, 'spectrum-TabsPanel-tabpanel')}>
136
- {state.selectedItem && state.selectedItem.props.children}
126
+ <TabContext.Provider
127
+ value={{
128
+ tabProps: {...props, orientation, density},
129
+ tabState: {tabListState, setTabListState, selectedTab, collapsed},
130
+ refs: {tablistRef, wrapperRef},
131
+ tabPanelProps,
132
+ tabLineState: tabPositions
133
+ }}>
134
+ <div
135
+ {...filterDOMProps(otherProps)}
136
+ {...styleProps}
137
+ ref={domRef}
138
+ className={classNames(
139
+ styles,
140
+ 'spectrum-TabsPanel',
141
+ `spectrum-TabsPanel--${orientation}`,
142
+ styleProps.className
143
+ )}>
144
+ {props.children}
137
145
  </div>
138
- </div>
146
+ </TabContext.Provider>
139
147
  );
140
148
  }
141
149
 
142
150
  interface TabProps<T> extends DOMProps {
143
151
  item: Node<T>,
144
- state: SingleSelectListState<T>,
152
+ state: TabListState<T>,
145
153
  isDisabled?: boolean,
146
154
  orientation?: Orientation
147
155
  }
148
156
 
149
- export function Tab<T>(props: TabProps<T>) {
150
- let {item, state, isDisabled: propsDisabled} = props;
157
+ // @private
158
+ function Tab<T>(props: TabProps<T>) {
159
+ let {item, state} = props;
151
160
  let {key, rendered} = item;
152
- let isDisabled = propsDisabled || state.disabledKeys.has(key);
153
161
 
154
- let ref = useRef<HTMLDivElement>();
155
- let {tabProps} = useTab({item, isDisabled}, state, ref);
162
+ let ref = useRef<any>(undefined);
163
+ let {tabProps, isSelected, isDisabled} = useTab({key}, state, ref);
156
164
 
157
165
  let {hoverProps, isHovered} = useHover({
158
166
  ...props
159
167
  });
160
- let isSelected = state.selectedKey === key;
168
+ let ElementType: React.ElementType = item.props.href ? 'a' : 'div';
161
169
 
162
170
  return (
163
171
  <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
164
- <div
172
+ <ElementType
165
173
  {...mergeProps(tabProps, hoverProps)}
166
174
  ref={ref}
167
175
  className={classNames(
@@ -187,17 +195,18 @@ export function Tab<T>(props: TabProps<T>) {
187
195
  ? <Text>{rendered}</Text>
188
196
  : rendered}
189
197
  </SlotProvider>
190
- </div>
198
+ </ElementType>
191
199
  </FocusRing>
192
200
  );
193
201
  }
194
202
 
195
203
  interface TabLineProps {
196
204
  orientation?: Orientation,
197
- selectedTab?: HTMLElement,
198
- selectedKey?: Key
205
+ selectedTab?: HTMLElement | null,
206
+ selectedKey?: Key | null
199
207
  }
200
208
 
209
+ // @private
201
210
  function TabLine(props: TabLineProps) {
202
211
  let {
203
212
  orientation,
@@ -207,164 +216,200 @@ function TabLine(props: TabLineProps) {
207
216
  selectedKey
208
217
  } = props;
209
218
 
210
- let verticalSelectionIndicatorOffset = 12;
211
219
  let {direction} = useLocale();
212
220
  let {scale} = useProvider();
221
+ let {tabLineState} = useContext(TabContext)!;
213
222
 
214
- let [style, setStyle] = useState({
223
+ let [style, setStyle] = useState<CSSProperties>({
215
224
  width: undefined,
216
225
  height: undefined
217
226
  });
218
227
 
219
- useLayoutEffect(() => {
228
+ let onResize = useCallback(() => {
220
229
  if (selectedTab) {
221
- let styleObj = {transform: undefined, width: undefined, height: undefined};
230
+ let styleObj: CSSProperties = {transform: undefined, width: undefined, height: undefined};
222
231
  // In RTL, calculate the transform from the right edge of the tablist so that resizing the window doesn't break the Tabline position due to offsetLeft changes
223
- let offset = direction === 'rtl' ? -1 * ((selectedTab.offsetParent as HTMLElement)?.offsetWidth - selectedTab.offsetWidth - selectedTab.offsetLeft) : selectedTab.offsetLeft;
232
+ let offset = direction === 'rtl' ?
233
+ -1 * ((selectedTab.offsetParent as HTMLElement)?.offsetWidth - selectedTab.offsetWidth - selectedTab.offsetLeft) :
234
+ selectedTab.offsetLeft;
224
235
  styleObj.transform = orientation === 'vertical'
225
- ? `translateY(${selectedTab.offsetTop + verticalSelectionIndicatorOffset / 2}px)`
236
+ ? `translateY(${selectedTab.offsetTop}px)`
226
237
  : `translateX(${offset}px)`;
227
238
 
228
239
  if (orientation === 'horizontal') {
229
240
  styleObj.width = `${selectedTab.offsetWidth}px`;
230
241
  } else {
231
- styleObj.height = `${selectedTab.offsetHeight - verticalSelectionIndicatorOffset}px`;
242
+ styleObj.height = `${selectedTab.offsetHeight}px`;
232
243
  }
233
244
  setStyle(styleObj);
234
245
  }
246
+ }, [direction, setStyle, selectedTab, orientation]);
235
247
 
236
- }, [direction, setStyle, selectedTab, orientation, scale, verticalSelectionIndicatorOffset, selectedKey]);
248
+ useLayoutEffect(() => {
249
+ onResize();
250
+ }, [onResize, scale, selectedKey, tabLineState]);
237
251
 
238
252
  return <div className={classNames(styles, 'spectrum-Tabs-selectionIndicator')} role="presentation" style={style} />;
239
253
  }
240
254
 
241
- interface CollapsibleTabListProps<T> extends TabListProps<T>, TabPickerProps<T> {
242
- tabListProps?: HTMLAttributes<HTMLElement>,
243
- wrapperRef: MutableRefObject<HTMLDivElement>,
244
- collapse?: boolean,
245
- tabListclassName?: string
246
- }
247
-
248
- const CollapsibleTabList = React.forwardRef(function <T> (props: CollapsibleTabListProps<T>, ref: MutableRefObject<HTMLDivElement>) {
249
- let {
250
- tabListProps,
251
- density,
252
- isQuiet,
253
- isDisabled,
254
- state,
255
- selectedTab,
256
- wrapperRef,
257
- collapse
258
- } = props;
255
+ /**
256
+ * A TabList is used within Tabs to group tabs that a user can switch between.
257
+ * The keys of the items within the <TabList> must match up with a corresponding item inside the <TabPanels>.
258
+ */
259
+ export function TabList<T>(props: SpectrumTabListProps<T>) {
260
+ const tabContext = useContext(TabContext)!;
261
+ const {refs, tabState, tabProps, tabPanelProps} = tabContext;
262
+ const {isQuiet, density, isEmphasized, orientation} = tabProps;
263
+ const {selectedTab, collapsed, setTabListState} = tabState;
264
+ const {tablistRef, wrapperRef} = refs;
265
+ // Pass original Tab props but override children to create the collection.
266
+ const state = useTabListState({...tabProps, children: props.children});
267
+
268
+ let {styleProps} = useStyleProps(props);
269
+ const {tabListProps} = useTabList({...tabProps, ...props}, state, tablistRef);
259
270
 
260
- let tabListclassName = classNames(styles, 'spectrum-TabsPanel-tabs');
271
+ useEffect(() => {
272
+ // Passing back to root as useTabPanel needs the TabListState
273
+ setTabListState(state);
274
+ // eslint-disable-next-line react-hooks/exhaustive-deps
275
+ }, [state.disabledKeys, state.selectedItem, state.selectedKey, props.children]);
261
276
 
262
- return (
263
- <div
264
- ref={wrapperRef}
265
- className={classNames(
266
- styles,
267
- 'spectrum-TabsPanel-collapseWrapper'
268
- )}>
269
- {collapse && <TabPicker {...props} className={tabListclassName} />}
270
- {!collapse && (
271
- <TabList
272
- {...tabListProps}
273
- density={density}
274
- isQuiet={isQuiet}
275
- isDisabled={isDisabled}
276
- state={state}
277
- selectedTab={selectedTab}
278
- ref={ref}
279
- orientation="horizontal"
280
- className={tabListclassName} />
281
- )}
282
- </div>
283
- );
284
- });
277
+ let collapseStyle : React.CSSProperties = collapsed && orientation !== 'vertical' ? {maxWidth: 'calc(100% + 1px)', overflow: 'hidden', visibility: 'hidden', position: 'absolute'} : {maxWidth: 'calc(100% + 1px)'};
278
+ let stylePropsFinal = orientation === 'vertical' ? styleProps : {style: collapseStyle};
285
279
 
286
- interface TabListProps<T> {
287
- isQuiet?: boolean,
288
- density?: 'compact' | 'regular',
289
- isDisabled?: boolean,
290
- orientation?: Orientation,
291
- state: SingleSelectListState<T>,
292
- selectedTab: HTMLElement,
293
- className?: string
294
- }
280
+ if (collapsed && orientation !== 'vertical') {
281
+ tabListProps['aria-hidden'] = true;
282
+ }
295
283
 
296
- const TabList = React.forwardRef(function <T> (props: TabListProps<T>, ref: MutableRefObject<HTMLDivElement>) {
297
- let {
298
- isQuiet,
299
- density,
300
- state,
301
- isDisabled,
302
- orientation,
303
- selectedTab,
304
- className,
305
- ...otherProps
306
- } = props;
284
+ let tabListclassName = classNames(styles, 'spectrum-TabsPanel-tabs');
307
285
 
308
- return (
286
+ const tabContent = (
309
287
  <div
310
- {...otherProps}
311
- ref={ref}
288
+ {...stylePropsFinal}
289
+ {...tabListProps}
290
+ ref={tablistRef}
312
291
  className={classNames(
313
292
  styles,
314
293
  'spectrum-Tabs',
315
294
  `spectrum-Tabs--${orientation}`,
295
+ tabListclassName,
316
296
  {
317
297
  'spectrum-Tabs--quiet': isQuiet,
298
+ 'spectrum-Tabs--emphasized': isEmphasized,
318
299
  ['spectrum-Tabs--compact']: density === 'compact'
319
300
  },
320
- className
321
- )}>
301
+ orientation === 'vertical' && styleProps.className
302
+ )
303
+ }>
322
304
  {[...state.collection].map((item) => (
323
- <Tab key={item.key} item={item} state={state} isDisabled={isDisabled} orientation={orientation} />
305
+ <Tab key={item.key} item={item} state={state} orientation={orientation} />
324
306
  ))}
325
307
  <TabLine orientation={orientation} selectedTab={selectedTab} />
326
308
  </div>
327
309
  );
328
- });
329
310
 
330
- interface TabPickerProps<T> extends SpectrumPickerProps<T> {
311
+
312
+ if (orientation === 'vertical') {
313
+ return tabContent;
314
+ } else {
315
+ return (
316
+ <div
317
+ {...styleProps}
318
+ ref={wrapperRef}
319
+ className={classNames(
320
+ styles,
321
+ 'spectrum-TabsPanel-collapseWrapper',
322
+ styleProps.className
323
+ )}>
324
+ <TabPicker {...props} {...tabProps} visible={collapsed} id={tabPanelProps['aria-labelledby']} state={state} className={tabListclassName} />
325
+ {tabContent}
326
+ </div>
327
+ );
328
+ }
329
+ }
330
+
331
+ /**
332
+ * TabPanels is used within Tabs as a container for the content of each tab.
333
+ * The keys of the items within the <TabPanels> must match up with a corresponding item inside the <TabList>.
334
+ */
335
+ export function TabPanels<T extends object>(props: SpectrumTabPanelsProps<T>) {
336
+ const {tabState, tabProps} = useContext(TabContext)!;
337
+ const {tabListState} = tabState;
338
+
339
+ const factory = useCallback((nodes: Iterable<Node<T>>) => new ListCollection(nodes), []);
340
+ const collection = useCollection({items: tabProps.items, ...props}, factory, {suppressTextValueWarning: true});
341
+ const selectedItem = tabListState && tabListState.selectedKey != null ? collection.getItem(tabListState.selectedKey) : null;
342
+
343
+ return (
344
+ <TabPanel {...props} key={tabListState?.selectedKey}>
345
+ {selectedItem && selectedItem.props.children}
346
+ </TabPanel>
347
+ );
348
+ }
349
+
350
+ interface TabPanelProps extends AriaTabPanelProps, StyleProps {
351
+ children?: ReactNode
352
+ }
353
+
354
+ // @private
355
+ function TabPanel(props: TabPanelProps) {
356
+ const {tabState, tabPanelProps: ctxTabPanelProps} = useContext(TabContext)!;
357
+ const {tabListState} = tabState;
358
+ let ref = useRef<HTMLDivElement | null>(null);
359
+ const {tabPanelProps} = useTabPanel(props, tabListState, ref);
360
+ let {styleProps} = useStyleProps(props);
361
+
362
+ if (ctxTabPanelProps['aria-labelledby']) {
363
+ tabPanelProps['aria-labelledby'] = ctxTabPanelProps['aria-labelledby'];
364
+ }
365
+
366
+ return (
367
+ <FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
368
+ <div {...styleProps} {...tabPanelProps} ref={ref} className={classNames(styles, 'spectrum-TabsPanel-tabpanel', styleProps.className)}>
369
+ {props.children}
370
+ </div>
371
+ </FocusRing>
372
+ );
373
+ }
374
+
375
+ interface TabPickerProps<T> extends Omit<SpectrumPickerProps<T>, 'children'> {
331
376
  density?: 'compact' | 'regular',
332
- state: SingleSelectListState<T>,
333
- className?: string
377
+ isEmphasized?: boolean,
378
+ state: TabListState<T>,
379
+ className?: string,
380
+ visible: boolean
334
381
  }
335
382
 
336
383
  function TabPicker<T>(props: TabPickerProps<T>) {
337
384
  let {
338
385
  isDisabled,
386
+ isEmphasized,
339
387
  isQuiet,
340
388
  state,
341
389
  'aria-labelledby': ariaLabeledBy,
342
390
  'aria-label': ariaLabel,
343
391
  density,
344
392
  className,
345
- id
393
+ id,
394
+ visible
346
395
  } = props;
347
396
 
348
- let ref = useRef();
349
- let [pickerNode, setPickerNode] = useState(null);
397
+ let ref = useRef<DOMRefValue<HTMLDivElement>>(null);
398
+ let [pickerNode, setPickerNode] = useState<HTMLElement | null>(null);
350
399
 
351
400
  useEffect(() => {
352
401
  let node = unwrapDOMRef(ref);
353
402
  setPickerNode(node.current);
354
403
  }, [ref]);
355
404
 
356
- let items = [...state.collection].map((item) => ({
357
- rendered: item.rendered,
358
- textValue: item.textValue,
359
- id: item.key
360
- }));
361
-
405
+ let items = [...state.collection];
362
406
  let pickerProps = {
363
407
  'aria-labelledby': ariaLabeledBy,
364
408
  'aria-label': ariaLabel
365
409
  };
366
410
 
367
- // TODO: Figure out if tabListProps should go onto the div here, v2 doesn't do it
411
+ const style : React.CSSProperties = visible ? {} : {visibility: 'hidden', position: 'absolute'};
412
+
368
413
  return (
369
414
  <div
370
415
  className={classNames(
@@ -374,10 +419,13 @@ function TabPicker<T>(props: TabPickerProps<T>) {
374
419
  'spectrum-Tabs--isCollapsed',
375
420
  {
376
421
  'spectrum-Tabs--quiet': isQuiet,
377
- ['spectrum-Tabs--compact']: density === 'compact'
422
+ ['spectrum-Tabs--compact']: density === 'compact',
423
+ 'spectrum-Tabs--emphasized': isEmphasized
378
424
  },
379
425
  className
380
- )}>
426
+ )}
427
+ style={style}
428
+ aria-hidden={visible ? undefined : true}>
381
429
  <SlotProvider
382
430
  slots={{
383
431
  icon: {
@@ -394,11 +442,12 @@ function TabPicker<T>(props: TabPickerProps<T>) {
394
442
  items={items}
395
443
  ref={ref}
396
444
  isQuiet
397
- isDisabled={isDisabled}
445
+ isDisabled={!visible || isDisabled}
398
446
  selectedKey={state.selectedKey}
399
447
  disabledKeys={state.disabledKeys}
400
- onSelectionChange={state.setSelectedKey}>
401
- {item => <Item textValue={item.textValue}>{item.rendered}</Item>}
448
+ onSelectionChange={state.setSelectedKey}
449
+ UNSAFE_className={classNames(styles, 'spectrum-Tabs-picker')}>
450
+ {item => <Item {...item.props}>{item.rendered}</Item>}
402
451
  </Picker>
403
452
  {pickerNode && <TabLine orientation="horizontal" selectedTab={pickerNode} selectedKey={state.selectedKey} />}
404
453
  </SlotProvider>
package/src/index.ts CHANGED
@@ -12,5 +12,6 @@
12
12
 
13
13
  /// <reference types="css-module-types" />
14
14
 
15
- export * from './Tabs';
15
+ export {TabList, TabPanels, Tabs} from './Tabs';
16
16
  export {Item} from '@react-stately/collections';
17
+ export type {SpectrumTabsProps, SpectrumTabListProps, SpectrumTabPanelsProps} from '@react-types/tabs';