@tamagui/tabs 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.
Files changed (67) hide show
  1. package/dist/cjs/StyledContext.cjs +16 -20
  2. package/dist/cjs/StyledContext.native.cjs +31 -0
  3. package/dist/cjs/StyledContext.native.js +17 -20
  4. package/dist/cjs/StyledContext.native.js.map +1 -1
  5. package/dist/cjs/Tabs.cjs +227 -68
  6. package/dist/cjs/Tabs.native.cjs +250 -0
  7. package/dist/cjs/Tabs.native.js +233 -67
  8. package/dist/cjs/Tabs.native.js.map +1 -1
  9. package/dist/cjs/index.cjs +10 -27
  10. package/dist/cjs/index.native.cjs +22 -0
  11. package/dist/cjs/index.native.js +11 -27
  12. package/dist/cjs/index.native.js.map +1 -1
  13. package/dist/esm/StyledContext.mjs +3 -4
  14. package/dist/esm/StyledContext.mjs.map +1 -1
  15. package/dist/esm/StyledContext.native.js +3 -4
  16. package/dist/esm/StyledContext.native.js.map +1 -1
  17. package/dist/esm/Tabs.mjs +206 -55
  18. package/dist/esm/Tabs.mjs.map +1 -1
  19. package/dist/esm/Tabs.native.js +212 -55
  20. package/dist/esm/Tabs.native.js.map +1 -1
  21. package/dist/esm/index.js +3 -11
  22. package/dist/esm/index.mjs +3 -11
  23. package/dist/esm/index.native.js +3 -11
  24. package/dist/jsx/StyledContext.mjs +3 -4
  25. package/dist/jsx/StyledContext.mjs.map +1 -1
  26. package/dist/jsx/StyledContext.native.cjs +31 -0
  27. package/dist/jsx/StyledContext.native.js +17 -20
  28. package/dist/jsx/StyledContext.native.js.map +1 -1
  29. package/dist/jsx/Tabs.mjs +206 -55
  30. package/dist/jsx/Tabs.mjs.map +1 -1
  31. package/dist/jsx/Tabs.native.cjs +250 -0
  32. package/dist/jsx/Tabs.native.js +233 -67
  33. package/dist/jsx/Tabs.native.js.map +1 -1
  34. package/dist/jsx/index.js +3 -11
  35. package/dist/jsx/index.mjs +3 -11
  36. package/dist/jsx/index.native.cjs +22 -0
  37. package/dist/jsx/index.native.js +11 -27
  38. package/dist/jsx/index.native.js.map +1 -1
  39. package/package.json +11 -17
  40. package/src/StyledContext.tsx +3 -3
  41. package/src/Tabs.tsx +368 -55
  42. package/src/index.ts +1 -9
  43. package/types/StyledContext.d.ts +3 -3
  44. package/types/StyledContext.d.ts.map +1 -1
  45. package/types/Tabs.d.ts +398 -35
  46. package/types/Tabs.d.ts.map +1 -1
  47. package/types/index.d.ts +1 -537
  48. package/types/index.d.ts.map +1 -1
  49. package/dist/cjs/createTabs.cjs +0 -293
  50. package/dist/cjs/createTabs.native.js +0 -308
  51. package/dist/cjs/createTabs.native.js.map +0 -1
  52. package/dist/esm/createTabs.mjs +0 -257
  53. package/dist/esm/createTabs.mjs.map +0 -1
  54. package/dist/esm/createTabs.native.js +0 -269
  55. package/dist/esm/createTabs.native.js.map +0 -1
  56. package/dist/esm/index.js.map +0 -1
  57. package/dist/esm/index.mjs.map +0 -1
  58. package/dist/esm/index.native.js.map +0 -1
  59. package/dist/jsx/createTabs.mjs +0 -257
  60. package/dist/jsx/createTabs.mjs.map +0 -1
  61. package/dist/jsx/createTabs.native.js +0 -308
  62. package/dist/jsx/createTabs.native.js.map +0 -1
  63. package/dist/jsx/index.js.map +0 -1
  64. package/dist/jsx/index.mjs.map +0 -1
  65. package/src/createTabs.tsx +0 -438
  66. package/types/createTabs.d.ts +0 -527
  67. package/types/createTabs.d.ts.map +0 -1
package/src/Tabs.tsx CHANGED
@@ -1,69 +1,382 @@
1
- import { styled, View } from '@tamagui/core'
2
- import { getButtonSized } from '@tamagui/get-button-sized'
3
- import { SizableStack, ThemeableStack } from '@tamagui/stacks'
1
+ import { composeRefs } from '@tamagui/compose-refs'
2
+ import { isWeb } from '@tamagui/constants'
3
+ import { createStyledHOC, styled, View } from '@tamagui/core'
4
+ import { composeEventHandlers, withStaticProperties } from '@tamagui/helpers'
5
+ import { RovingFocusGroup, type RovingFocusGroupProps } from '@tamagui/roving-focus'
6
+ import { SizeContext, type TokenSize } from '@tamagui/size'
7
+ import { useTab, useTabContent, useTabs, useTabsList } from '@tamagui/tabs-headless'
8
+ import type { GetProps, TamaguiElement } from '@tamagui/web'
9
+ import { useEvent } from '@tamagui/web'
10
+ import * as React from 'react'
11
+ import type { LayoutRectangle } from 'react-native'
12
+ import { TabsProvider, useTabsContext } from './StyledContext'
4
13
 
5
- const TABS_NAME = 'Tabs'
6
- export const DefaultTabsFrame = styled(SizableStack, {
7
- name: TABS_NAME,
14
+ const TABS_CONTEXT = 'TabsContext'
15
+
16
+ export const TabsFrame = styled(View, {
17
+ displayName: 'Tabs',
18
+ context: SizeContext,
8
19
  })
9
20
 
10
- const TRIGGER_NAME = 'TabsTrigger'
21
+ export const TabsListFrame = styled(View, {
22
+ displayName: 'TabsList',
23
+ context: SizeContext,
24
+ role: 'tablist',
25
+ })
11
26
 
12
- export const DefaultTabsTabFrame = styled(
13
- View,
14
- {
15
- name: TRIGGER_NAME,
16
- role: 'tab',
27
+ export const TabsTabFrame = styled(View, {
28
+ displayName: 'TabsTrigger',
29
+ context: SizeContext,
30
+ role: 'tab',
17
31
 
18
- variants: {
19
- size: {
20
- '...size': getButtonSized,
32
+ variants: {
33
+ disabled: {
34
+ true: {
35
+ pointerEvents: 'none',
21
36
  },
37
+ },
38
+ } as const,
39
+ })
22
40
 
23
- disabled: {
24
- true: {
25
- pointerEvents: 'none',
26
- },
27
- },
41
+ export const TabsContentFrame = styled(View, {
42
+ displayName: 'TabsContent',
43
+ context: SizeContext,
44
+ })
28
45
 
29
- unstyled: {
30
- false: {
31
- borderWidth: 0,
32
- backgroundColor: '$background',
33
- userSelect: 'none',
34
- justifyContent: 'center',
35
- alignItems: 'center',
36
- flexWrap: 'nowrap',
37
- flexDirection: 'row',
38
- cursor: 'pointer',
39
-
40
- pressStyle: {
41
- backgroundColor: '$backgroundPress',
42
- },
43
-
44
- focusVisibleStyle: {
45
- outlineColor: '$outlineColor',
46
- outlineWidth: 2,
47
- outlineStyle: 'solid',
48
- zIndex: 10,
49
- },
50
- },
51
- },
52
- } as const,
46
+ type TabsScopeProps = {
47
+ __scopeTabs?: string
48
+ }
53
49
 
54
- defaultVariants: {
55
- unstyled: process.env.TAMAGUI_HEADLESS === '1',
56
- },
57
- },
58
- {
59
- accept: {
60
- activeStyle: 'style',
61
- } as const,
50
+ type TabsFrameProps = GetProps<typeof TabsFrame>
51
+
52
+ type TabsExtraProps<Tab = string> = TabsScopeProps & {
53
+ /** The value for the selected tab, if controlled */
54
+ value?: string
55
+ /** The value of the tab to select by default, if uncontrolled */
56
+ defaultValue?: Tab
57
+ /** A function called when a new tab is selected */
58
+ onValueChange?: (value: Tab) => void
59
+ /** Coordinates a size value with styled descendants. */
60
+ size?: TokenSize
61
+ /**
62
+ * The orientation the tabs are laid out.
63
+ * Mainly so arrow navigation is done accordingly (left & right vs. up & down).
64
+ * @defaultValue horizontal
65
+ */
66
+ orientation?: RovingFocusGroupProps['orientation']
67
+ /** The direction of navigation between tab triggers. */
68
+ dir?: RovingFocusGroupProps['dir']
69
+ /**
70
+ * Whether a tab is activated automatically or manually. Automatic activation is only
71
+ * supported on web; native tabs always activate manually.
72
+ * @defaultValue automatic
73
+ */
74
+ activationMode?: 'automatic' | 'manual'
75
+ }
76
+
77
+ export type TabsProps<Tab = string> = TabsFrameProps & TabsExtraProps<Tab>
78
+
79
+ type TabsListExtraProps = TabsScopeProps & {
80
+ /** Whether keyboard navigation loops after the first or last trigger. */
81
+ loop?: boolean
82
+ /** Disables every trigger in the list. */
83
+ disabled?: boolean
84
+ }
85
+
86
+ export type TabsListProps = GetProps<typeof TabsListFrame> & TabsListExtraProps
87
+
88
+ export type InteractionType = 'select' | 'focus' | 'hover'
89
+ export type TabLayout = LayoutRectangle
90
+ export type TabsTriggerLayout = LayoutRectangle
91
+
92
+ type TabsTabExtraProps = TabsScopeProps & {
93
+ /** The value selected when this trigger is activated. */
94
+ value: string
95
+ /** Reports the measured trigger rectangle for custom indicators. */
96
+ onInteraction?: (type: InteractionType, layout: TabLayout | null) => void
97
+ /** Custom styles to apply while this trigger is selected. */
98
+ activeStyle?: GetProps<typeof TabsTabFrame>
99
+ /** Theme to apply while this trigger is selected. */
100
+ activeTheme?: string | null
101
+ }
102
+
103
+ export type TabsTabProps = GetProps<typeof TabsTabFrame> & TabsTabExtraProps
104
+
105
+ type TabsContentExtraProps = TabsScopeProps & {
106
+ /** The value that selects this content. */
107
+ value: string
108
+ /** Mounts the content even when its value is not selected. */
109
+ forceMount?: boolean
110
+ }
111
+
112
+ export type TabsContentProps = GetProps<typeof TabsContentFrame> & TabsContentExtraProps
113
+
114
+ const TabsListDisabledContext = React.createContext(false)
115
+
116
+ export const TabsList = createStyledHOC(
117
+ TabsListFrame,
118
+ function TabsList(props: TabsListProps, forwardedRef) {
119
+ const { __scopeTabs, loop = true, disabled = false, children, ...listProps } = props
120
+ const context = useTabsContext(__scopeTabs)
121
+ const { listProps: headlessListProps } = useTabsList({
122
+ orientation: context.orientation,
123
+ disabled,
124
+ })
125
+
126
+ return (
127
+ <TabsListDisabledContext.Provider value={disabled}>
128
+ <RovingFocusGroup
129
+ __scopeRovingFocusGroup={__scopeTabs || TABS_CONTEXT}
130
+ orientation={context.orientation}
131
+ dir={context.dir}
132
+ loop={loop}
133
+ asChild
134
+ >
135
+ <TabsListFrame
136
+ {...headlessListProps}
137
+ flexDirection={context.orientation === 'vertical' ? 'column' : 'row'}
138
+ ref={forwardedRef}
139
+ {...listProps}
140
+ >
141
+ {children}
142
+ </TabsListFrame>
143
+ </RovingFocusGroup>
144
+ </TabsListDisabledContext.Provider>
145
+ )
62
146
  }
63
147
  )
64
148
 
65
- const CONTENT_NAME = 'TabsContent'
149
+ export const TabsTab = createStyledHOC(
150
+ TabsTabFrame,
151
+ function TabsTab(props: TabsTabProps, forwardedRef) {
152
+ const {
153
+ __scopeTabs,
154
+ value,
155
+ disabled: disabledProp,
156
+ onInteraction,
157
+ activeStyle,
158
+ activeTheme,
159
+ onLayout,
160
+ onMouseEnter,
161
+ onMouseLeave,
162
+ onPress,
163
+ onKeyDown,
164
+ onFocus,
165
+ onBlur,
166
+ ...triggerProps
167
+ } = props
168
+ const context = useTabsContext(__scopeTabs)
169
+ const listDisabled = React.useContext(TabsListDisabledContext)
170
+ const disabled = disabledProp ?? listDisabled
171
+ const { isSelected, tabProps } = useTab({
172
+ baseId: context.baseId,
173
+ value,
174
+ selectedValue: context.value,
175
+ disabled,
176
+ activationMode: context.activationMode,
177
+ onChange: context.onChange,
178
+ })
179
+ const {
180
+ onPress: activateOnPress,
181
+ onKeyDown: activateOnKeyDown,
182
+ onFocus: activateOnFocus,
183
+ ...tabA11yProps
184
+ } = tabProps
185
+ const [layout, setLayout] = React.useState<TabLayout | null>(null)
186
+ const triggerRef = React.useRef<TamaguiElement>(null)
187
+ const emitInteraction = useEvent(
188
+ (type: InteractionType, layout: TabLayout | null) => {
189
+ onInteraction?.(type, layout)
190
+ }
191
+ )
192
+
193
+ React.useEffect(() => {
194
+ context.registerTrigger()
195
+ return () => context.unregisterTrigger()
196
+ }, [])
197
+
198
+ React.useEffect(() => {
199
+ if (!triggerRef.current || !isWeb) return
200
+
201
+ const element = triggerRef.current as unknown as HTMLElement
202
+
203
+ function updateTriggerSize() {
204
+ const next = {
205
+ width: element.offsetWidth,
206
+ height: element.offsetHeight,
207
+ x: element.offsetLeft,
208
+ y: element.offsetTop,
209
+ }
210
+ setLayout((previous) =>
211
+ previous &&
212
+ previous.width === next.width &&
213
+ previous.height === next.height &&
214
+ previous.x === next.x &&
215
+ previous.y === next.y
216
+ ? previous
217
+ : next
218
+ )
219
+ }
220
+
221
+ updateTriggerSize()
222
+ const observer = new ResizeObserver(updateTriggerSize)
223
+ observer.observe(element)
224
+
225
+ return () => observer.disconnect()
226
+ }, [context.triggersCount])
227
+
228
+ React.useEffect(() => {
229
+ if (isSelected && layout) {
230
+ emitInteraction('select', layout)
231
+ }
232
+ }, [emitInteraction, isSelected, layout])
233
+
234
+ return (
235
+ <RovingFocusGroup.Item
236
+ __scopeRovingFocusGroup={__scopeTabs || TABS_CONTEXT}
237
+ asChild
238
+ tabIndex={disabled ? -1 : 0}
239
+ active={isSelected}
240
+ >
241
+ <TabsTabFrame
242
+ onLayout={
243
+ isWeb
244
+ ? onLayout
245
+ : composeEventHandlers(onLayout, (event) => {
246
+ setLayout(event.nativeEvent.layout)
247
+ })
248
+ }
249
+ onMouseEnter={composeEventHandlers(onMouseEnter, () => {
250
+ if (layout) {
251
+ emitInteraction('hover', layout)
252
+ }
253
+ })}
254
+ onMouseLeave={composeEventHandlers(onMouseLeave, () => {
255
+ emitInteraction('hover', null)
256
+ })}
257
+ {...tabA11yProps}
258
+ theme={isSelected ? (activeTheme ?? null) : null}
259
+ size={context.size}
260
+ {...triggerProps}
261
+ // after triggerProps so active styles beat base styles from styled() skins
262
+ {...(isSelected && activeStyle)}
263
+ ref={composeRefs(forwardedRef, triggerRef)}
264
+ onPress={composeEventHandlers(onPress ?? undefined, activateOnPress as any)}
265
+ {...(isWeb && {
266
+ onKeyDown: composeEventHandlers(onKeyDown, activateOnKeyDown),
267
+ // emit the focus interaction before activating, matching the order
268
+ // the indicator animations were built against
269
+ onFocus: composeEventHandlers(
270
+ onFocus,
271
+ composeEventHandlers(() => {
272
+ if (layout) {
273
+ emitInteraction('focus', layout)
274
+ }
275
+ }, activateOnFocus)
276
+ ),
277
+ onBlur: composeEventHandlers(onBlur, () => {
278
+ emitInteraction('focus', null)
279
+ }),
280
+ })}
281
+ />
282
+ </RovingFocusGroup.Item>
283
+ )
284
+ }
285
+ )
286
+
287
+ export const TabsContent = createStyledHOC(
288
+ TabsContentFrame,
289
+ function TabsContent(props: TabsContentProps, forwardedRef) {
290
+ const { __scopeTabs, value, forceMount, children, ...contentProps } = props
291
+ const context = useTabsContext(__scopeTabs)
292
+ const { shouldMount, contentProps: headlessContentProps } = useTabContent({
293
+ baseId: context.baseId,
294
+ value,
295
+ selectedValue: context.value,
296
+ orientation: context.orientation,
297
+ forceMount,
298
+ })
299
+
300
+ if (!shouldMount) {
301
+ return null
302
+ }
303
+
304
+ return (
305
+ <TabsContentFrame
306
+ key={value}
307
+ // @ts-ignore hidden is a web-only accessibility attribute
308
+ {...headlessContentProps}
309
+ {...contentProps}
310
+ ref={forwardedRef}
311
+ >
312
+ {children}
313
+ </TabsContentFrame>
314
+ )
315
+ }
316
+ )
317
+
318
+ const TabsComponent = createStyledHOC(
319
+ TabsFrame,
320
+ function Tabs(props: TabsProps, forwardedRef) {
321
+ const {
322
+ __scopeTabs,
323
+ value: valueProp,
324
+ onValueChange,
325
+ defaultValue,
326
+ orientation = 'horizontal',
327
+ dir,
328
+ activationMode = 'automatic',
329
+ size = true,
330
+ ...tabsProps
331
+ } = props
332
+ const {
333
+ value,
334
+ setValue,
335
+ baseId,
336
+ direction,
337
+ triggersCount,
338
+ registerTrigger,
339
+ unregisterTrigger,
340
+ tabsProps: headlessTabsProps,
341
+ } = useTabs({
342
+ value: valueProp,
343
+ onValueChange,
344
+ defaultValue,
345
+ orientation,
346
+ dir,
347
+ activationMode,
348
+ })
349
+
350
+ return (
351
+ <SizeContext.Provider size={size}>
352
+ <TabsProvider
353
+ scope={__scopeTabs}
354
+ baseId={baseId}
355
+ value={value}
356
+ onChange={setValue}
357
+ orientation={orientation}
358
+ dir={direction}
359
+ activationMode={activationMode}
360
+ size={size}
361
+ registerTrigger={registerTrigger}
362
+ triggersCount={triggersCount}
363
+ unregisterTrigger={unregisterTrigger}
364
+ >
365
+ <TabsFrame
366
+ direction={direction}
367
+ {...headlessTabsProps}
368
+ {...tabsProps}
369
+ ref={forwardedRef}
370
+ />
371
+ </TabsProvider>
372
+ </SizeContext.Provider>
373
+ )
374
+ }
375
+ )
66
376
 
67
- export const DefaultTabsContentFrame = styled(ThemeableStack, {
68
- name: CONTENT_NAME,
377
+ export const Tabs = withStaticProperties(TabsComponent, {
378
+ Frame: TabsFrame,
379
+ List: TabsList,
380
+ Tab: TabsTab,
381
+ Content: TabsContent,
69
382
  })
package/src/index.ts CHANGED
@@ -1,10 +1,2 @@
1
- import { createTabs } from './createTabs'
2
- import { DefaultTabsContentFrame, DefaultTabsFrame, DefaultTabsTabFrame } from './Tabs'
3
- export * from './createTabs'
4
1
  export * from './StyledContext'
5
-
6
- export const Tabs = createTabs({
7
- ContentFrame: DefaultTabsContentFrame,
8
- TabFrame: DefaultTabsTabFrame,
9
- TabsFrame: DefaultTabsFrame,
10
- })
2
+ export * from './Tabs'
@@ -1,5 +1,5 @@
1
- import type { SizeTokens } from '@tamagui/core';
2
- import type { TabsProps } from './createTabs';
1
+ import type { TokenSize } from '@tamagui/size';
2
+ import type { TabsProps } from './Tabs';
3
3
  export type TabsContextValue = {
4
4
  baseId: string;
5
5
  value?: string;
@@ -7,7 +7,7 @@ export type TabsContextValue = {
7
7
  orientation?: TabsProps['orientation'];
8
8
  dir?: TabsProps['dir'];
9
9
  activationMode?: TabsProps['activationMode'];
10
- size: SizeTokens;
10
+ size: TokenSize;
11
11
  registerTrigger: () => void;
12
12
  unregisterTrigger: () => void;
13
13
  triggersCount: number;
@@ -1 +1 @@
1
- {"version":3,"file":"StyledContext.d.ts","sourceRoot":"","sources":["../src/StyledContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAE/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,WAAW,CAAC,EAAE,SAAS,CAAC,aAAa,CAAC,CAAA;IACtC,GAAG,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC,CAAA;IACtB,cAAc,CAAC,EAAE,SAAS,CAAC,gBAAgB,CAAC,CAAA;IAC5C,IAAI,EAAE,UAAU,CAAA;IAChB,eAAe,EAAE,MAAM,IAAI,CAAA;IAC3B,iBAAiB,EAAE,MAAM,IAAI,CAAA;IAC7B,aAAa,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,eAAO,MAAkB,YAAY;;;IAAoB,cAAc,sCAC9B,CAAA"}
1
+ {"version":3,"file":"StyledContext.d.ts","sourceRoot":"","sources":["../src/StyledContext.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAEvC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,WAAW,CAAC,EAAE,SAAS,CAAC,aAAa,CAAC,CAAA;IACtC,GAAG,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC,CAAA;IACtB,cAAc,CAAC,EAAE,SAAS,CAAC,gBAAgB,CAAC,CAAA;IAC5C,IAAI,EAAE,SAAS,CAAA;IACf,eAAe,EAAE,MAAM,IAAI,CAAA;IAC3B,iBAAiB,EAAE,MAAM,IAAI,CAAA;IAC7B,aAAa,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,eAAO,MAAkB,YAAY;;;IAAoB,cAAc,sCAC9B,CAAA"}