@tamagui/tabs-headless 2.7.7 → 3.0.0-beta.643.1
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/cjs/index.cjs +10 -11
- package/dist/cjs/index.native.cjs +21 -0
- package/dist/cjs/index.native.js +10 -10
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/useTabs.cjs +114 -280
- package/dist/cjs/useTabs.native.cjs +150 -0
- package/dist/cjs/useTabs.native.js +124 -292
- package/dist/cjs/useTabs.native.js.map +1 -1
- package/dist/esm/index.js +1 -2
- package/dist/esm/index.mjs +1 -2
- package/dist/esm/index.native.js +1 -2
- package/dist/esm/useTabs.mjs +95 -254
- package/dist/esm/useTabs.mjs.map +1 -1
- package/dist/esm/useTabs.native.js +104 -266
- package/dist/esm/useTabs.native.js.map +1 -1
- package/dist/jsx/index.js +1 -2
- package/dist/jsx/index.mjs +1 -2
- package/dist/jsx/index.native.cjs +21 -0
- package/dist/jsx/index.native.js +10 -10
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/useTabs.mjs +95 -254
- package/dist/jsx/useTabs.mjs.map +1 -1
- package/dist/jsx/useTabs.native.cjs +150 -0
- package/dist/jsx/useTabs.native.js +124 -292
- package/dist/jsx/useTabs.native.js.map +1 -1
- package/package.json +5 -5
- package/src/useTabs.tsx +112 -296
- package/types/useTabs.d.ts +54 -75
- package/types/useTabs.d.ts.map +1 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/index.mjs.map +0 -1
- package/dist/esm/index.native.js.map +0 -1
- package/dist/jsx/index.js.map +0 -1
- package/dist/jsx/index.mjs.map +0 -1
package/src/useTabs.tsx
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isWeb } from '@tamagui/constants'
|
|
2
2
|
import { useControllableState } from '@tamagui/use-controllable-state'
|
|
3
3
|
import { useDirection } from '@tamagui/use-direction'
|
|
4
4
|
import * as React from 'react'
|
|
5
|
-
import { useCallback, useId, useRef, useState } from 'react'
|
|
6
5
|
|
|
7
6
|
export type TabsActivationMode = 'automatic' | 'manual'
|
|
8
7
|
export type TabsOrientation = 'horizontal' | 'vertical'
|
|
9
8
|
export type Direction = 'ltr' | 'rtl'
|
|
10
9
|
|
|
10
|
+
// the skinned Tabs and any headless consumer must derive the same ids, or
|
|
11
|
+
// aria-controls / aria-labelledby stop pointing at each other
|
|
12
|
+
export function makeTriggerId(baseId: string, value: string) {
|
|
13
|
+
return `${baseId}-trigger-${value}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function makeContentId(baseId: string, value: string) {
|
|
17
|
+
return `${baseId}-content-${value}`
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
// -------------------------------------------------------------------------------------------------
|
|
12
|
-
// useTabs
|
|
21
|
+
// useTabs - root controller
|
|
13
22
|
// -------------------------------------------------------------------------------------------------
|
|
14
23
|
|
|
15
24
|
export interface UseTabsProps {
|
|
@@ -20,387 +29,194 @@ export interface UseTabsProps {
|
|
|
20
29
|
/** A function called when a new tab is selected */
|
|
21
30
|
onValueChange?: (value: string) => void
|
|
22
31
|
/**
|
|
23
|
-
* The orientation the tabs are
|
|
24
|
-
* Mainly so arrow navigation is done accordingly (left & right vs. up & down)
|
|
32
|
+
* The orientation the tabs are laid out.
|
|
25
33
|
* @defaultValue horizontal
|
|
26
34
|
*/
|
|
27
35
|
orientation?: TabsOrientation
|
|
28
|
-
/**
|
|
29
|
-
* The direction of navigation between toolbar items.
|
|
30
|
-
*/
|
|
36
|
+
/** The direction of navigation between tab triggers. */
|
|
31
37
|
dir?: Direction
|
|
32
38
|
/**
|
|
33
|
-
* Whether a tab is activated automatically (on focus) or manually (on
|
|
39
|
+
* Whether a tab is activated automatically (on focus) or manually (on press/enter).
|
|
40
|
+
* Automatic activation is web-only; native always activates manually.
|
|
34
41
|
* @defaultValue automatic
|
|
35
42
|
*/
|
|
36
43
|
activationMode?: TabsActivationMode
|
|
37
|
-
/**
|
|
38
|
-
* Whether keyboard navigation should loop from last to first and vice versa.
|
|
39
|
-
* @defaultValue true
|
|
40
|
-
*/
|
|
41
|
-
loop?: boolean
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
export
|
|
45
|
-
/** The currently selected tab value */
|
|
46
|
-
value: string
|
|
47
|
-
/** Function to change the selected tab */
|
|
48
|
-
setValue: (value: string) => void
|
|
49
|
-
/** The resolved text direction */
|
|
50
|
-
direction: Direction
|
|
51
|
-
/** Props to spread on the tabs container element */
|
|
52
|
-
tabsProps: {
|
|
53
|
-
'data-orientation': TabsOrientation
|
|
54
|
-
dir: Direction
|
|
55
|
-
}
|
|
56
|
-
/** Props to spread on the tab list element */
|
|
57
|
-
listProps: {
|
|
58
|
-
role: 'tablist'
|
|
59
|
-
'aria-orientation': TabsOrientation
|
|
60
|
-
}
|
|
61
|
-
/** Function to get props for a tab trigger */
|
|
62
|
-
getTabProps: (tabValue: string, disabled?: boolean) => TabTriggerProps
|
|
63
|
-
/** Function to get props for a tab content panel */
|
|
64
|
-
getContentProps: (tabValue: string) => TabContentProps
|
|
65
|
-
/** Context value to provide to child components */
|
|
66
|
-
contextValue: TabsContextValue
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface TabTriggerProps {
|
|
70
|
-
role: 'tab'
|
|
71
|
-
id: string
|
|
72
|
-
'aria-selected': boolean
|
|
73
|
-
'aria-controls': string
|
|
74
|
-
'data-state': 'active' | 'inactive'
|
|
75
|
-
'data-disabled'?: ''
|
|
76
|
-
disabled?: boolean
|
|
77
|
-
tabIndex: number
|
|
78
|
-
onKeyDown: (event: React.KeyboardEvent) => void
|
|
79
|
-
onClick: (event: React.MouseEvent) => void
|
|
80
|
-
onFocus: (event: React.FocusEvent) => void
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export interface TabContentProps {
|
|
84
|
-
role: 'tabpanel'
|
|
85
|
-
id: string
|
|
86
|
-
'aria-labelledby': string
|
|
87
|
-
'data-state': 'active' | 'inactive'
|
|
88
|
-
'data-orientation': TabsOrientation
|
|
89
|
-
hidden: boolean
|
|
90
|
-
tabIndex: 0
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export interface TabsContextValue {
|
|
94
|
-
baseId: string
|
|
95
|
-
value: string
|
|
96
|
-
setValue: (value: string) => void
|
|
97
|
-
orientation: TabsOrientation
|
|
98
|
-
direction: Direction
|
|
99
|
-
activationMode: TabsActivationMode
|
|
100
|
-
loop: boolean
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function useTabs(props: UseTabsProps = {}): UseTabsReturn {
|
|
46
|
+
export function useTabs(props: UseTabsProps = {}) {
|
|
104
47
|
const {
|
|
105
48
|
value: valueProp,
|
|
106
49
|
onValueChange,
|
|
107
|
-
defaultValue
|
|
50
|
+
defaultValue,
|
|
108
51
|
orientation = 'horizontal',
|
|
109
52
|
dir,
|
|
110
53
|
activationMode = 'automatic',
|
|
111
|
-
loop = true,
|
|
112
54
|
} = props
|
|
113
55
|
|
|
114
56
|
const direction = useDirection(dir)
|
|
115
|
-
const baseId = useId()
|
|
57
|
+
const baseId = React.useId()
|
|
116
58
|
|
|
117
59
|
const [value, setValue] = useControllableState({
|
|
118
60
|
prop: valueProp,
|
|
119
61
|
onChange: onValueChange,
|
|
120
|
-
defaultProp: defaultValue,
|
|
62
|
+
defaultProp: defaultValue ?? '',
|
|
121
63
|
})
|
|
122
64
|
|
|
123
|
-
|
|
124
|
-
const [
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
tabRefs.current.set(tabValue, element)
|
|
129
|
-
setTabValues((prev) => (prev.includes(tabValue) ? prev : [...prev, tabValue]))
|
|
130
|
-
} else {
|
|
131
|
-
tabRefs.current.delete(tabValue)
|
|
132
|
-
setTabValues((prev) => prev.filter((v) => v !== tabValue))
|
|
133
|
-
}
|
|
134
|
-
}, [])
|
|
135
|
-
|
|
136
|
-
const focusTab = useCallback((tabValue: string) => {
|
|
137
|
-
const element = tabRefs.current.get(tabValue)
|
|
138
|
-
element?.focus()
|
|
139
|
-
}, [])
|
|
140
|
-
|
|
141
|
-
const getNextTab = useCallback(
|
|
142
|
-
(currentValue: string, direction: 1 | -1): string | null => {
|
|
143
|
-
const currentIndex = tabValues.indexOf(currentValue)
|
|
144
|
-
if (currentIndex === -1) return null
|
|
145
|
-
|
|
146
|
-
let nextIndex = currentIndex + direction
|
|
147
|
-
if (loop) {
|
|
148
|
-
nextIndex = (nextIndex + tabValues.length) % tabValues.length
|
|
149
|
-
} else {
|
|
150
|
-
if (nextIndex < 0 || nextIndex >= tabValues.length) return null
|
|
151
|
-
}
|
|
152
|
-
return tabValues[nextIndex] ?? null
|
|
153
|
-
},
|
|
154
|
-
[tabValues, loop]
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
const handleKeyDown = useCallback(
|
|
158
|
-
(tabValue: string) => (event: React.KeyboardEvent) => {
|
|
159
|
-
const isHorizontal = orientation === 'horizontal'
|
|
160
|
-
const isRtl = direction === 'rtl'
|
|
161
|
-
|
|
162
|
-
let nextTab: string | null = null
|
|
163
|
-
|
|
164
|
-
switch (event.key) {
|
|
165
|
-
case 'ArrowRight':
|
|
166
|
-
if (isHorizontal) {
|
|
167
|
-
nextTab = getNextTab(tabValue, isRtl ? -1 : 1)
|
|
168
|
-
}
|
|
169
|
-
break
|
|
170
|
-
case 'ArrowLeft':
|
|
171
|
-
if (isHorizontal) {
|
|
172
|
-
nextTab = getNextTab(tabValue, isRtl ? 1 : -1)
|
|
173
|
-
}
|
|
174
|
-
break
|
|
175
|
-
case 'ArrowDown':
|
|
176
|
-
if (!isHorizontal) {
|
|
177
|
-
nextTab = getNextTab(tabValue, 1)
|
|
178
|
-
}
|
|
179
|
-
break
|
|
180
|
-
case 'ArrowUp':
|
|
181
|
-
if (!isHorizontal) {
|
|
182
|
-
nextTab = getNextTab(tabValue, -1)
|
|
183
|
-
}
|
|
184
|
-
break
|
|
185
|
-
case 'Home':
|
|
186
|
-
nextTab = tabValues[0] ?? null
|
|
187
|
-
break
|
|
188
|
-
case 'End':
|
|
189
|
-
nextTab = tabValues[tabValues.length - 1] ?? null
|
|
190
|
-
break
|
|
191
|
-
case ' ':
|
|
192
|
-
case 'Enter':
|
|
193
|
-
if (activationMode === 'manual') {
|
|
194
|
-
setValue(tabValue)
|
|
195
|
-
event.preventDefault()
|
|
196
|
-
}
|
|
197
|
-
return
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (nextTab) {
|
|
201
|
-
event.preventDefault()
|
|
202
|
-
focusTab(nextTab)
|
|
203
|
-
if (activationMode === 'automatic') {
|
|
204
|
-
setValue(nextTab)
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
[orientation, direction, getNextTab, tabValues, activationMode, setValue, focusTab]
|
|
65
|
+
// triggers report their presence so consumers can re-measure when the set changes
|
|
66
|
+
const [triggersCount, setTriggersCount] = React.useState(0)
|
|
67
|
+
const registerTrigger = React.useCallback(
|
|
68
|
+
() => setTriggersCount((count) => count + 1),
|
|
69
|
+
[]
|
|
209
70
|
)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const isSelected = value === tabValue
|
|
214
|
-
const triggerId = makeTriggerId(baseId, tabValue)
|
|
215
|
-
const contentId = makeContentId(baseId, tabValue)
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
role: 'tab',
|
|
219
|
-
id: triggerId,
|
|
220
|
-
'aria-selected': isSelected,
|
|
221
|
-
'aria-controls': contentId,
|
|
222
|
-
'data-state': isSelected ? 'active' : 'inactive',
|
|
223
|
-
...(disabled && { 'data-disabled': '' as const }),
|
|
224
|
-
disabled,
|
|
225
|
-
tabIndex: isSelected ? 0 : -1,
|
|
226
|
-
onKeyDown: disabled ? () => {} : handleKeyDown(tabValue),
|
|
227
|
-
onClick: (event: React.MouseEvent) => {
|
|
228
|
-
if (!disabled && !isSelected) {
|
|
229
|
-
setValue(tabValue)
|
|
230
|
-
}
|
|
231
|
-
},
|
|
232
|
-
onFocus: (event: React.FocusEvent) => {
|
|
233
|
-
registerTab(tabValue, event.currentTarget as HTMLElement)
|
|
234
|
-
if (!disabled && !isSelected && activationMode === 'automatic') {
|
|
235
|
-
setValue(tabValue)
|
|
236
|
-
}
|
|
237
|
-
},
|
|
238
|
-
}
|
|
239
|
-
},
|
|
240
|
-
[value, baseId, handleKeyDown, setValue, activationMode, registerTab]
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
const getContentProps = useCallback(
|
|
244
|
-
(tabValue: string): TabContentProps => {
|
|
245
|
-
const isSelected = value === tabValue
|
|
246
|
-
const triggerId = makeTriggerId(baseId, tabValue)
|
|
247
|
-
const contentId = makeContentId(baseId, tabValue)
|
|
248
|
-
|
|
249
|
-
return {
|
|
250
|
-
role: 'tabpanel',
|
|
251
|
-
id: contentId,
|
|
252
|
-
'aria-labelledby': triggerId,
|
|
253
|
-
'data-state': isSelected ? 'active' : 'inactive',
|
|
254
|
-
'data-orientation': orientation,
|
|
255
|
-
hidden: !isSelected,
|
|
256
|
-
tabIndex: 0,
|
|
257
|
-
}
|
|
258
|
-
},
|
|
259
|
-
[value, baseId, orientation]
|
|
71
|
+
const unregisterTrigger = React.useCallback(
|
|
72
|
+
() => setTriggersCount((count) => count - 1),
|
|
73
|
+
[]
|
|
260
74
|
)
|
|
261
75
|
|
|
262
|
-
const contextValue: TabsContextValue = {
|
|
263
|
-
baseId,
|
|
264
|
-
value,
|
|
265
|
-
setValue,
|
|
266
|
-
orientation,
|
|
267
|
-
direction,
|
|
268
|
-
activationMode,
|
|
269
|
-
loop,
|
|
270
|
-
}
|
|
271
|
-
|
|
272
76
|
return {
|
|
273
77
|
value,
|
|
274
78
|
setValue,
|
|
79
|
+
baseId,
|
|
275
80
|
direction,
|
|
81
|
+
orientation,
|
|
82
|
+
activationMode,
|
|
83
|
+
triggersCount,
|
|
84
|
+
registerTrigger,
|
|
85
|
+
unregisterTrigger,
|
|
276
86
|
tabsProps: {
|
|
277
87
|
'data-orientation': orientation,
|
|
278
|
-
dir: direction,
|
|
279
|
-
},
|
|
280
|
-
listProps: {
|
|
281
|
-
role: 'tablist',
|
|
282
|
-
'aria-orientation': orientation,
|
|
283
88
|
},
|
|
284
|
-
getTabProps,
|
|
285
|
-
getContentProps,
|
|
286
|
-
contextValue,
|
|
287
89
|
}
|
|
288
90
|
}
|
|
289
91
|
|
|
290
92
|
// -------------------------------------------------------------------------------------------------
|
|
291
|
-
//
|
|
93
|
+
// useTabsList - the tablist container
|
|
292
94
|
// -------------------------------------------------------------------------------------------------
|
|
293
95
|
|
|
294
|
-
|
|
96
|
+
export interface UseTabsListProps {
|
|
97
|
+
orientation?: TabsOrientation
|
|
98
|
+
/** Disables every trigger in the list. */
|
|
99
|
+
disabled?: boolean
|
|
100
|
+
}
|
|
295
101
|
|
|
296
|
-
export
|
|
102
|
+
export function useTabsList(props: UseTabsListProps) {
|
|
103
|
+
const { orientation = 'horizontal', disabled = false } = props
|
|
297
104
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
105
|
+
return {
|
|
106
|
+
listProps: {
|
|
107
|
+
role: 'tablist' as const,
|
|
108
|
+
'aria-orientation': orientation,
|
|
109
|
+
'aria-disabled': disabled || undefined,
|
|
110
|
+
'data-orientation': orientation,
|
|
111
|
+
'data-disabled': disabled ? ('' as const) : undefined,
|
|
112
|
+
},
|
|
302
113
|
}
|
|
303
|
-
return context
|
|
304
114
|
}
|
|
305
115
|
|
|
306
116
|
// -------------------------------------------------------------------------------------------------
|
|
307
|
-
// useTab -
|
|
117
|
+
// useTab - an individual trigger
|
|
308
118
|
// -------------------------------------------------------------------------------------------------
|
|
309
119
|
|
|
120
|
+
/** the fields the primary-pointer check reads off a web press event */
|
|
121
|
+
export type TabPressEvent = {
|
|
122
|
+
button?: number
|
|
123
|
+
ctrlKey?: boolean
|
|
124
|
+
}
|
|
125
|
+
|
|
310
126
|
export interface UseTabProps {
|
|
127
|
+
baseId: string
|
|
128
|
+
/** The value this trigger selects. */
|
|
311
129
|
value: string
|
|
130
|
+
/** The currently selected value. */
|
|
131
|
+
selectedValue?: string
|
|
312
132
|
disabled?: boolean
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
onFocus?: (event: React.FocusEvent) => void
|
|
133
|
+
activationMode?: TabsActivationMode
|
|
134
|
+
onChange: (value: string) => void
|
|
316
135
|
}
|
|
317
136
|
|
|
318
137
|
export function useTab(props: UseTabProps) {
|
|
319
|
-
const {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
138
|
+
const {
|
|
139
|
+
baseId,
|
|
140
|
+
value,
|
|
141
|
+
selectedValue,
|
|
142
|
+
disabled = false,
|
|
143
|
+
activationMode = 'automatic',
|
|
144
|
+
onChange,
|
|
145
|
+
} = props
|
|
327
146
|
|
|
328
|
-
const
|
|
147
|
+
const isSelected = value === selectedValue
|
|
148
|
+
const triggerId = makeTriggerId(baseId, value)
|
|
149
|
+
const contentId = makeContentId(baseId, value)
|
|
329
150
|
|
|
330
151
|
return {
|
|
331
152
|
isSelected,
|
|
153
|
+
triggerId,
|
|
154
|
+
contentId,
|
|
332
155
|
tabProps: {
|
|
333
|
-
ref,
|
|
334
156
|
role: 'tab' as const,
|
|
335
157
|
id: triggerId,
|
|
336
158
|
'aria-selected': isSelected,
|
|
337
159
|
'aria-controls': contentId,
|
|
338
|
-
'data-state': isSelected ? 'active' : 'inactive',
|
|
339
|
-
|
|
160
|
+
'data-state': isSelected ? ('active' as const) : ('inactive' as const),
|
|
161
|
+
'data-disabled': disabled ? ('' as const) : undefined,
|
|
340
162
|
disabled,
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
}),
|
|
349
|
-
onPress: composeEventHandlers(onPress, () => {
|
|
350
|
-
if (!disabled && !isSelected) {
|
|
351
|
-
setValue(tabValue)
|
|
352
|
-
}
|
|
353
|
-
}),
|
|
354
|
-
onFocus: composeEventHandlers(onFocus, () => {
|
|
355
|
-
if (!disabled && !isSelected && activationMode === 'automatic') {
|
|
356
|
-
setValue(tabValue)
|
|
163
|
+
onPress: (event?: TabPressEvent) => {
|
|
164
|
+
// ignore secondary and ctrl-clicks on web; native has no such notion
|
|
165
|
+
const isPrimaryPointer =
|
|
166
|
+
!isWeb || (event?.button === 0 && event?.ctrlKey === false)
|
|
167
|
+
if (!disabled && !isSelected && isPrimaryPointer) {
|
|
168
|
+
onChange(value)
|
|
357
169
|
}
|
|
170
|
+
},
|
|
171
|
+
...(isWeb && {
|
|
172
|
+
onKeyDown: (event: React.KeyboardEvent) => {
|
|
173
|
+
if (!disabled && [' ', 'Enter'].includes(event.key)) {
|
|
174
|
+
onChange(value)
|
|
175
|
+
event.preventDefault()
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
onFocus: () => {
|
|
179
|
+
if (!isSelected && !disabled && activationMode !== 'manual') {
|
|
180
|
+
onChange(value)
|
|
181
|
+
}
|
|
182
|
+
},
|
|
358
183
|
}),
|
|
359
184
|
},
|
|
360
185
|
}
|
|
361
186
|
}
|
|
362
187
|
|
|
363
188
|
// -------------------------------------------------------------------------------------------------
|
|
364
|
-
// useTabContent -
|
|
189
|
+
// useTabContent - a content panel
|
|
365
190
|
// -------------------------------------------------------------------------------------------------
|
|
366
191
|
|
|
367
192
|
export interface UseTabContentProps {
|
|
193
|
+
baseId: string
|
|
194
|
+
/** The value that selects this content. */
|
|
368
195
|
value: string
|
|
196
|
+
/** The currently selected value. */
|
|
197
|
+
selectedValue?: string
|
|
198
|
+
orientation?: TabsOrientation
|
|
199
|
+
/** Mounts the content even when its value is not selected. */
|
|
369
200
|
forceMount?: boolean
|
|
370
201
|
}
|
|
371
202
|
|
|
372
203
|
export function useTabContent(props: UseTabContentProps) {
|
|
373
|
-
const { value
|
|
374
|
-
const context = useTabsContext()
|
|
375
|
-
const { value, baseId, orientation } = context
|
|
204
|
+
const { baseId, value, selectedValue, orientation = 'horizontal', forceMount } = props
|
|
376
205
|
|
|
377
|
-
const isSelected = value ===
|
|
378
|
-
const
|
|
379
|
-
const contentId = makeContentId(baseId, tabValue)
|
|
206
|
+
const isSelected = value === selectedValue
|
|
207
|
+
const shouldMount = Boolean(forceMount) || isSelected
|
|
380
208
|
|
|
381
209
|
return {
|
|
382
210
|
isSelected,
|
|
383
|
-
shouldMount
|
|
211
|
+
shouldMount,
|
|
384
212
|
contentProps: {
|
|
385
213
|
role: 'tabpanel' as const,
|
|
386
|
-
id:
|
|
387
|
-
'aria-labelledby':
|
|
388
|
-
'data-state': isSelected ? 'active' : 'inactive',
|
|
214
|
+
id: makeContentId(baseId, value),
|
|
215
|
+
'aria-labelledby': makeTriggerId(baseId, value),
|
|
216
|
+
'data-state': isSelected ? ('active' as const) : ('inactive' as const),
|
|
389
217
|
'data-orientation': orientation,
|
|
390
|
-
hidden: !
|
|
218
|
+
hidden: !shouldMount,
|
|
391
219
|
tabIndex: 0,
|
|
392
220
|
},
|
|
393
221
|
}
|
|
394
222
|
}
|
|
395
|
-
|
|
396
|
-
// -------------------------------------------------------------------------------------------------
|
|
397
|
-
// Helpers
|
|
398
|
-
// -------------------------------------------------------------------------------------------------
|
|
399
|
-
|
|
400
|
-
function makeTriggerId(baseId: string, value: string) {
|
|
401
|
-
return `${baseId}-trigger-${value}`
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function makeContentId(baseId: string, value: string) {
|
|
405
|
-
return `${baseId}-content-${value}`
|
|
406
|
-
}
|