@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.
- package/dist/ActionBar.cjs +11 -8
- package/dist/ActionBar.cjs.map +1 -1
- package/dist/ActionBar.css +15 -32
- package/dist/ActionBar.css.map +1 -1
- package/dist/ActionBar.mjs +12 -9
- package/dist/ActionBar.mjs.map +1 -1
- package/dist/Modal.cjs +15 -21
- package/dist/Modal.cjs.map +1 -1
- package/dist/Modal.css +22 -46
- package/dist/Modal.css.map +1 -1
- package/dist/Modal.mjs +15 -21
- package/dist/Modal.mjs.map +1 -1
- package/dist/Popover.cjs +19 -37
- package/dist/Popover.cjs.map +1 -1
- package/dist/Popover.css +28 -99
- package/dist/Popover.css.map +1 -1
- package/dist/Popover.mjs +19 -37
- package/dist/Popover.mjs.map +1 -1
- package/dist/Tabs.cjs +177 -414
- package/dist/Tabs.cjs.map +1 -1
- package/dist/Tabs.css +120 -208
- package/dist/Tabs.css.map +1 -1
- package/dist/Tabs.mjs +179 -416
- package/dist/Tabs.mjs.map +1 -1
- package/dist/Tooltip.cjs +19 -16
- package/dist/Tooltip.cjs.map +1 -1
- package/dist/Tooltip.css +25 -40
- package/dist/Tooltip.css.map +1 -1
- package/dist/Tooltip.mjs +19 -16
- package/dist/Tooltip.mjs.map +1 -1
- package/dist/en-US.cjs +0 -1
- package/dist/en-US.cjs.map +1 -1
- package/dist/en-US.mjs +0 -1
- package/dist/en-US.mjs.map +1 -1
- package/dist/he-IL.cjs +0 -1
- package/dist/he-IL.cjs.map +1 -1
- package/dist/he-IL.mjs +0 -1
- package/dist/he-IL.mjs.map +1 -1
- package/dist/types.d.ts +7 -11
- package/dist/types.d.ts.map +1 -1
- package/package.json +19 -19
- package/src/ActionBar.tsx +15 -18
- package/src/Modal.tsx +17 -44
- package/src/Popover.tsx +21 -89
- package/src/Tabs.tsx +156 -427
- package/src/Tooltip.tsx +25 -42
- package/dist/TabsPicker.cjs +0 -388
- package/dist/TabsPicker.cjs.map +0 -1
- package/dist/TabsPicker.css +0 -440
- package/dist/TabsPicker.css.map +0 -1
- package/dist/TabsPicker.mjs +0 -382
- package/dist/TabsPicker.mjs.map +0 -1
- package/src/TabsPicker.tsx +0 -341
package/src/Tabs.tsx
CHANGED
|
@@ -11,35 +11,29 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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,
|
|
31
|
-
import {createContext, forwardRef,
|
|
32
|
-
import {focusRing,
|
|
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 {
|
|
42
|
-
import {useLocale
|
|
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
|
|
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
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
}
|
|
122
|
+
});
|
|
90
123
|
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
{
|
|
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,
|
|
202
|
+
className={renderProps => tablist({...renderProps, density})} />
|
|
216
203
|
{orientation === 'horizontal' &&
|
|
217
|
-
<TabLine
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
350
|
+
<RACTabs
|
|
421
351
|
{...props}
|
|
352
|
+
ref={domRef}
|
|
422
353
|
style={props.UNSAFE_style}
|
|
423
|
-
className={(props.UNSAFE_className || '') +
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
+
});
|