@react-spectrum/tabs 3.0.0-beta.0 → 3.0.0-nightly-641446f65-240905
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/dist/Tabs.main.js +405 -0
- package/dist/Tabs.main.js.map +1 -0
- package/dist/Tabs.mjs +398 -0
- package/dist/Tabs.module.js +398 -0
- package/dist/Tabs.module.js.map +1 -0
- package/dist/import.mjs +20 -0
- package/dist/main.js +19 -418
- package/dist/main.js.map +1 -1
- package/dist/module.js +14 -360
- package/dist/module.js.map +1 -1
- package/dist/tabs_vars_css.main.js +86 -0
- package/dist/tabs_vars_css.main.js.map +1 -0
- package/dist/tabs_vars_css.mjs +88 -0
- package/dist/tabs_vars_css.module.js +88 -0
- package/dist/tabs_vars_css.module.js.map +1 -0
- package/dist/types.d.ts +16 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/vars.8b47c0b1.css +590 -0
- package/dist/vars.8b47c0b1.css.map +1 -0
- package/package.json +26 -23
- package/src/Tabs.tsx +233 -184
- package/src/index.ts +2 -1
- package/LICENSE +0 -201
- package/dist/main.css +0 -2
- package/dist/main.css.map +0 -1
package/src/Tabs.tsx
CHANGED
|
@@ -10,83 +10,102 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
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 {
|
|
18
|
-
import React, {
|
|
19
|
-
|
|
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 {
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
50
|
-
let [
|
|
51
|
-
|
|
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="${
|
|
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,
|
|
89
|
+
}, [children, tabListState?.selectedKey, collapsed, tablistRef]);
|
|
63
90
|
|
|
64
91
|
let checkShouldCollapse = useCallback(() => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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,
|
|
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 (
|
|
122
|
+
if (collapsed && orientation !== 'vertical') {
|
|
100
123
|
tabPanelProps['aria-labelledby'] = collapsibleTabListId;
|
|
101
124
|
}
|
|
102
|
-
|
|
103
125
|
return (
|
|
104
|
-
<
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
</
|
|
146
|
+
</TabContext.Provider>
|
|
139
147
|
);
|
|
140
148
|
}
|
|
141
149
|
|
|
142
150
|
interface TabProps<T> extends DOMProps {
|
|
143
151
|
item: Node<T>,
|
|
144
|
-
state:
|
|
152
|
+
state: TabListState<T>,
|
|
145
153
|
isDisabled?: boolean,
|
|
146
154
|
orientation?: Orientation
|
|
147
155
|
}
|
|
148
156
|
|
|
149
|
-
|
|
150
|
-
|
|
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<
|
|
155
|
-
let {tabProps} = useTab({
|
|
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
|
|
168
|
+
let ElementType: React.ElementType = item.props.href ? 'a' : 'div';
|
|
161
169
|
|
|
162
170
|
return (
|
|
163
171
|
<FocusRing focusRingClass={classNames(styles, 'focus-ring')}>
|
|
164
|
-
<
|
|
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
|
-
</
|
|
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
|
-
|
|
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' ?
|
|
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
|
|
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
|
|
242
|
+
styleObj.height = `${selectedTab.offsetHeight}px`;
|
|
232
243
|
}
|
|
233
244
|
setStyle(styleObj);
|
|
234
245
|
}
|
|
246
|
+
}, [direction, setStyle, selectedTab, orientation]);
|
|
235
247
|
|
|
236
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
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
|
-
|
|
263
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
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
|
-
|
|
286
|
+
const tabContent = (
|
|
309
287
|
<div
|
|
310
|
-
{...
|
|
311
|
-
|
|
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}
|
|
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
|
-
|
|
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
|
-
|
|
333
|
-
|
|
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]
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
|
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';
|