fluent-styles 1.62.1 → 1.62.3

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 (54) hide show
  1. package/README.md +81 -3709
  2. package/lib/commonjs/circularProgress/index.js +102 -83
  3. package/lib/commonjs/circularProgress/index.js.map +1 -1
  4. package/lib/commonjs/tabBar/TabBar.js +87 -139
  5. package/lib/commonjs/tabBar/TabBar.js.map +1 -1
  6. package/lib/commonjs/utiles/styled.js +26 -5
  7. package/lib/commonjs/utiles/styled.js.map +1 -1
  8. package/lib/module/circularProgress/index.js +103 -84
  9. package/lib/module/circularProgress/index.js.map +1 -1
  10. package/lib/module/tabBar/TabBar.js +88 -140
  11. package/lib/module/tabBar/TabBar.js.map +1 -1
  12. package/lib/module/utiles/styled.js +26 -5
  13. package/lib/module/utiles/styled.js.map +1 -1
  14. package/lib/typescript/badge/index.d.ts +3 -1
  15. package/lib/typescript/badge/index.d.ts.map +1 -1
  16. package/lib/typescript/button/index.d.ts +5 -1
  17. package/lib/typescript/button/index.d.ts.map +1 -1
  18. package/lib/typescript/card/index.d.ts +3 -1
  19. package/lib/typescript/card/index.d.ts.map +1 -1
  20. package/lib/typescript/circularProgress/index.d.ts +4 -5
  21. package/lib/typescript/circularProgress/index.d.ts.map +1 -1
  22. package/lib/typescript/dialog/index.d.ts +3 -1
  23. package/lib/typescript/dialog/index.d.ts.map +1 -1
  24. package/lib/typescript/divider/index.d.ts +3 -1
  25. package/lib/typescript/divider/index.d.ts.map +1 -1
  26. package/lib/typescript/header/index.d.ts +3 -1
  27. package/lib/typescript/header/index.d.ts.map +1 -1
  28. package/lib/typescript/image/index.d.ts +3 -1
  29. package/lib/typescript/image/index.d.ts.map +1 -1
  30. package/lib/typescript/pressable/index.d.ts +3 -1
  31. package/lib/typescript/pressable/index.d.ts.map +1 -1
  32. package/lib/typescript/safeAreaProvider/index.d.ts +3 -1
  33. package/lib/typescript/safeAreaProvider/index.d.ts.map +1 -1
  34. package/lib/typescript/safeAreaView/index.d.ts +3 -1
  35. package/lib/typescript/safeAreaView/index.d.ts.map +1 -1
  36. package/lib/typescript/scrollView/index.d.ts +3 -1
  37. package/lib/typescript/scrollView/index.d.ts.map +1 -1
  38. package/lib/typescript/shape/cycle.d.ts +3 -1
  39. package/lib/typescript/shape/cycle.d.ts.map +1 -1
  40. package/lib/typescript/shape/index.d.ts +3 -1
  41. package/lib/typescript/shape/index.d.ts.map +1 -1
  42. package/lib/typescript/spacer/index.d.ts +3 -1
  43. package/lib/typescript/spacer/index.d.ts.map +1 -1
  44. package/lib/typescript/stack/index.d.ts +9 -3
  45. package/lib/typescript/stack/index.d.ts.map +1 -1
  46. package/lib/typescript/tabBar/TabBar.d.ts.map +1 -1
  47. package/lib/typescript/text/index.d.ts +3 -1
  48. package/lib/typescript/text/index.d.ts.map +1 -1
  49. package/lib/typescript/utiles/styled.d.ts +4 -2
  50. package/lib/typescript/utiles/styled.d.ts.map +1 -1
  51. package/package.json +1 -1
  52. package/src/circularProgress/index.tsx +177 -164
  53. package/src/tabBar/TabBar.tsx +120 -233
  54. package/src/utiles/styled.tsx +22 -6
@@ -12,11 +12,9 @@ import {
12
12
  Easing,
13
13
  ScrollView,
14
14
  StyleSheet,
15
- Text,
16
- TouchableOpacity,
17
15
  useColorScheme,
18
- View,
19
16
  } from 'react-native'
17
+ import { Stack, StyledText, StyledPressable } from 'fluent-styles'
20
18
 
21
19
  import {
22
20
  TAB_BAR_COLORS_DARK,
@@ -30,59 +28,45 @@ import {
30
28
 
31
29
  // ─── Badge ────────────────────────────────────────────────────────────────────
32
30
 
33
- const Badge: React.FC<{
34
- value: number | string
35
- color: string
36
- }> = ({ value, color }) => {
31
+ const Badge: React.FC<{ value: number | string; color: string }> = ({ value, color }) => {
37
32
  const isDot = value === ''
38
33
  return (
39
- <View style={[badge.wrap, isDot && badge.dot_wrap]}>
34
+ <Stack
35
+ position="absolute"
36
+ top={-4}
37
+ right={isDot ? -4 : -8}
38
+ minWidth={isDot ? 8 : 16}
39
+ height={isDot ? 8 : 16}
40
+ borderRadius={isDot ? 4 : 8}
41
+ alignItems="center"
42
+ justifyContent="center"
43
+ paddingHorizontal={isDot ? 0 : 3}
44
+ backgroundColor={isDot ? undefined : '#ef4444'}
45
+ >
40
46
  {isDot ? (
41
- <View style={[badge.dot, { backgroundColor: color }]} />
47
+ <Stack width={6} height={6} borderRadius={3} backgroundColor={color} />
42
48
  ) : (
43
- <Text style={[badge.text, { color }]}>
49
+ <StyledText fontSize={9} fontWeight="700" color={color}>
44
50
  {typeof value === 'number' && value > 99 ? '99+' : value}
45
- </Text>
51
+ </StyledText>
46
52
  )}
47
- </View>
53
+ </Stack>
48
54
  )
49
55
  }
50
56
 
51
- const badge = StyleSheet.create({
52
- wrap: {
53
- position: 'absolute',
54
- top: -4,
55
- right: -8,
56
- minWidth: 16,
57
- height: 16,
58
- borderRadius: 8,
59
- alignItems: 'center',
60
- justifyContent: 'center',
61
- paddingHorizontal: 3,
62
- backgroundColor: '#ef4444',
63
- },
64
- dot_wrap: {
65
- top: 0,
66
- right: -4,
67
- minWidth: 8,
68
- height: 8,
69
- borderRadius: 4,
70
- padding: 0,
71
- },
72
- dot: { width: 6, height: 6, borderRadius: 3 },
73
- text: { fontSize: 9, fontWeight: '700', color: '#fff' },
74
- })
75
-
76
57
  // ─── Indicator ────────────────────────────────────────────────────────────────
58
+ // NOTE: Animated.View is kept here intentionally — fluent-styles Stack does not
59
+ // accept Animated values for left/width, which are required for the sliding
60
+ // animation. This is the only place native Animated.View remains.
77
61
 
78
62
  interface IndicatorProps {
79
- type: IndicatorStyle
80
- left: Animated.Value
81
- width: Animated.Value
82
- height: number
83
- color: string
84
- radius?: number
85
- visible: boolean
63
+ type: IndicatorStyle
64
+ left: Animated.Value
65
+ width: Animated.Value
66
+ height: number
67
+ color: string
68
+ radius?: number
69
+ visible: boolean
86
70
  }
87
71
 
88
72
  const Indicator: React.FC<IndicatorProps> = ({
@@ -100,7 +84,7 @@ const Indicator: React.FC<IndicatorProps> = ({
100
84
  width,
101
85
  backgroundColor: color,
102
86
  borderRadius: radius ?? 999,
103
- opacity: 0.15,
87
+ // FIX: removed opacity: 0.15 — pill is now fully opaque
104
88
  },
105
89
  ]}
106
90
  />
@@ -123,7 +107,7 @@ const Indicator: React.FC<IndicatorProps> = ({
123
107
  )
124
108
  }
125
109
 
126
- // line (default)
110
+ // line
127
111
  return (
128
112
  <Animated.View
129
113
  style={{
@@ -141,32 +125,21 @@ const Indicator: React.FC<IndicatorProps> = ({
141
125
 
142
126
  // ─── Helpers ──────────────────────────────────────────────────────────────────
143
127
 
144
- function resolveIndicatorHeight(
145
- type: IndicatorStyle | false,
146
- explicit?: number,
147
- ): number {
128
+ function resolveIndicatorHeight(type: IndicatorStyle | false, explicit?: number): number {
148
129
  if (explicit !== undefined) return explicit
149
130
  if (type === 'line') return 3
150
131
  if (type === 'dot') return 6
151
132
  return 3
152
133
  }
153
134
 
154
- function resolveBarHeight(
155
- hasIcons: boolean,
156
- hasIndicator: boolean,
157
- explicit?: number,
158
- ): number {
135
+ function resolveBarHeight(hasIcons: boolean, hasIndicator: boolean, explicit?: number): number {
159
136
  if (explicit !== undefined) return explicit
160
- if (hasIcons) return 52
161
- if (hasIndicator) return 44
137
+ if (hasIcons) return 52
138
+ if (hasIndicator) return 44
162
139
  return 48
163
140
  }
164
141
 
165
- function getVariantTabStyle(
166
- variant: TabBarVariant,
167
- isActive: boolean,
168
- colors: TabBarColors,
169
- ) {
142
+ function getVariantChipProps(variant: TabBarVariant, isActive: boolean, colors: TabBarColors) {
170
143
  if (variant === 'card' || variant === 'solid') {
171
144
  return isActive
172
145
  ? { backgroundColor: colors.activeChipBg, borderRadius: 8 }
@@ -183,27 +156,23 @@ function TabBarInner<T extends TabValue>(props: TabBarProps<T>) {
183
156
  value: controlledValue,
184
157
  defaultValue,
185
158
  onChange,
186
-
187
- indicator = false,
159
+ indicator = false,
188
160
  indicatorWidth,
189
161
  indicatorHeight,
190
162
  indicatorColor,
191
163
  indicatorRadius,
192
-
193
- tabAlign = 'center',
164
+ tabAlign = 'center',
194
165
  height,
195
- variant = 'default',
196
- labelBulge = 1,
197
- showBorder = false,
166
+ variant = 'default',
167
+ labelBulge = 1,
168
+ showBorder = false,
198
169
  tabPaddingHorizontal = 12,
199
- iconLabelGap = 4,
200
- fontSize = 14,
201
- iconFontSize = 11,
202
-
170
+ iconLabelGap = 4,
171
+ fontSize = 14,
172
+ iconFontSize = 11,
203
173
  textColor,
204
174
  activeTextColor,
205
175
  colors: colorOverrides,
206
-
207
176
  style,
208
177
  contentStyle,
209
178
  tabStyle,
@@ -212,25 +181,19 @@ function TabBarInner<T extends TabValue>(props: TabBarProps<T>) {
212
181
 
213
182
  // ── Colours ──────────────────────────────────────────────────────────────
214
183
  const scheme = useColorScheme()
215
- const baseColors: TabBarColors = scheme === 'dark'
216
- ? TAB_BAR_COLORS_DARK
217
- : TAB_BAR_COLORS_LIGHT
218
-
184
+ const baseColors: TabBarColors = scheme === 'dark' ? TAB_BAR_COLORS_DARK : TAB_BAR_COLORS_LIGHT
219
185
  const colors: TabBarColors = useMemo(
220
186
  () => colorOverrides ? { ...baseColors, ...colorOverrides } : baseColors,
221
187
  [baseColors, colorOverrides],
222
188
  )
223
-
224
- const resolvedTextColor = (textColor as string) ?? colors.text
225
- const resolvedActiveTextColor = (activeTextColor as string) ?? colors.activeText
226
- const resolvedIndicatorColor = (indicatorColor as string) ?? colors.indicator
189
+ const resolvedTextColor = (textColor as string) ?? colors.text
190
+ const resolvedActiveTextColor = (activeTextColor as string) ?? colors.activeText
191
+ const resolvedIndicatorColor = (indicatorColor as string) ?? colors.indicator
227
192
 
228
193
  // ── State ────────────────────────────────────────────────────────────────
229
194
  const isControlled = controlledValue !== undefined
230
- const [localValue, setLocalValue] = useState<T>(
231
- defaultValue ?? options[0]?.value,
232
- )
233
- const active = isControlled ? controlledValue! : localValue
195
+ const [localValue, setLocalValue] = useState<T>(defaultValue ?? options[0]?.value)
196
+ const active = isControlled ? controlledValue! : localValue
234
197
  const activeRef = useRef(active)
235
198
  activeRef.current = active
236
199
 
@@ -241,13 +204,12 @@ function TabBarInner<T extends TabValue>(props: TabBarProps<T>) {
241
204
  }, [isControlled, onChange])
242
205
 
243
206
  // ── Layout measurement ────────────────────────────────────────────────────
244
- const tabCount = options.length
245
- const layouts = useRef<{ tab?: LayoutRectangle; text?: LayoutRectangle }[]>(
207
+ const tabCount = options.length
208
+ const layouts = useRef<{ tab?: LayoutRectangle; text?: LayoutRectangle }[]>(
246
209
  Array.from({ length: tabCount }, () => ({})),
247
210
  )
248
211
  const [layoutReady, setLayoutReady] = useState(false)
249
212
 
250
- // Reset when options change
251
213
  const prevOptionsLen = useRef(tabCount)
252
214
  if (prevOptionsLen.current !== tabCount) {
253
215
  prevOptionsLen.current = tabCount
@@ -256,11 +218,10 @@ function TabBarInner<T extends TabValue>(props: TabBarProps<T>) {
256
218
  }
257
219
 
258
220
  const checkReady = useCallback(() => {
259
- const allDone = layouts.current.every(l => l.tab && l.text)
260
- if (allDone) setLayoutReady(true)
221
+ if (layouts.current.every(l => l.tab && l.text)) setLayoutReady(true)
261
222
  }, [])
262
223
 
263
- const onLayoutTab = useCallback((i: number) => (e: LayoutChangeEvent) => {
224
+ const onLayoutTab = useCallback((i: number) => (e: LayoutChangeEvent) => {
264
225
  layouts.current[i] = { ...layouts.current[i], tab: e.nativeEvent.layout }
265
226
  checkReady()
266
227
  }, [checkReady])
@@ -274,52 +235,27 @@ function TabBarInner<T extends TabValue>(props: TabBarProps<T>) {
274
235
  const indLeft = useRef(new Animated.Value(0)).current
275
236
  const indWidth = useRef(new Animated.Value(0)).current
276
237
  const indH = resolveIndicatorHeight(indicator, indicatorHeight)
277
-
278
238
  const scrollRef = useRef<ScrollView>(null)
279
239
  const barWidthRef = useRef(0)
280
240
 
281
241
  const navigateTo = useCallback((idx: number) => {
282
242
  const layout = layouts.current[idx]
283
243
  if (!layout.tab || !layout.text) return
284
-
285
- // Resolve indicator width
286
- const useTextWidth = indicatorWidth === undefined
287
- const useTabWidth = indicatorWidth === 0
288
- const iw = useTextWidth
244
+ const iw = indicatorWidth === undefined
289
245
  ? (indicator === 'pill' ? layout.tab.width : layout.text.width)
290
- : useTabWidth
246
+ : indicatorWidth === 0
291
247
  ? layout.tab.width
292
248
  : indicatorWidth!
293
-
294
- // Center the indicator within the tab
295
249
  const il = layout.tab.x + (layout.tab.width - iw) / 2
296
-
297
250
  Animated.parallel([
298
- Animated.timing(indLeft, {
299
- toValue: il,
300
- duration: 220,
301
- easing: Easing.out(Easing.cubic),
302
- useNativeDriver: false,
303
- }),
304
- Animated.timing(indWidth, {
305
- toValue: iw,
306
- duration: 220,
307
- easing: Easing.out(Easing.cubic),
308
- useNativeDriver: false,
309
- }),
251
+ Animated.timing(indLeft, { toValue: il, duration: 220, easing: Easing.out(Easing.cubic), useNativeDriver: false }),
252
+ Animated.timing(indWidth, { toValue: iw, duration: 220, easing: Easing.out(Easing.cubic), useNativeDriver: false }),
310
253
  ]).start()
311
-
312
- // Auto-scroll horizontal bar
313
254
  if (tabAlign === 'scroll') {
314
- const tabCenter = layout.tab.x + layout.tab.width / 2
315
- scrollRef.current?.scrollTo({
316
- x: tabCenter - barWidthRef.current / 2,
317
- animated: true,
318
- })
255
+ scrollRef.current?.scrollTo({ x: layout.tab.x + layout.tab.width / 2 - barWidthRef.current / 2, animated: true })
319
256
  }
320
257
  }, [indLeft, indWidth, indicatorWidth, indicator, tabAlign])
321
258
 
322
- // Navigate when active tab or layout readiness changes
323
259
  useEffect(() => {
324
260
  if (!indicator || !layoutReady) return
325
261
  const idx = options.findIndex(o => o.value === active)
@@ -329,175 +265,126 @@ function TabBarInner<T extends TabValue>(props: TabBarProps<T>) {
329
265
  // ── Dimensions ───────────────────────────────────────────────────────────
330
266
  const hasIcons = options.some(o => !!o.iconRender)
331
267
  const barHeight = resolveBarHeight(hasIcons, !!indicator, height)
332
-
333
- const bulgeFactor = typeof labelBulge === 'boolean'
334
- ? (labelBulge ? 1.2 : 1)
335
- : labelBulge
268
+ const bulgeFactor = typeof labelBulge === 'boolean' ? (labelBulge ? 1.2 : 1) : labelBulge
336
269
 
337
270
  // ── Render each tab ──────────────────────────────────────────────────────
338
271
  const renderTab = (item: (typeof options)[number], index: number) => {
339
272
  const isActive = item.value === active
340
273
  const isDisabled = item.disabled ?? false
341
274
 
275
+ // FIX: pill/line/dot use resolvedActiveTextColor (label sits on indicator bg)
276
+ // solid/card use activeChipText (label sits on per-tab chip bg)
342
277
  const labelColor = isActive
343
278
  ? (variant === 'solid' || variant === 'card') ? colors.activeChipText : resolvedActiveTextColor
344
279
  : isDisabled ? colors.disabled
345
280
  : resolvedTextColor
346
281
 
347
- const chipStyle = getVariantTabStyle(variant, isActive, colors)
282
+ const chipProps = getVariantChipProps(variant, isActive, colors)
348
283
 
349
284
  return (
350
- <TouchableOpacity
285
+ <StyledPressable
351
286
  key={String(item.value)}
352
- activeOpacity={isDisabled ? 1 : 0.7}
353
287
  disabled={isDisabled}
354
288
  onPress={() => handlePress(item.value)}
355
289
  onLayout={onLayoutTab(index)}
356
- style={[
357
- S.tab,
358
- tabAlign === 'center' && S.tab_equal,
359
- { paddingHorizontal: tabPaddingHorizontal },
360
- hasIcons && { paddingVertical: 6 },
361
- chipStyle,
362
- tabStyle,
363
- ]}
290
+ alignItems="center"
291
+ justifyContent="center"
292
+ height="100%"
293
+ flexDirection="column"
294
+ paddingHorizontal={tabPaddingHorizontal}
295
+ paddingVertical={hasIcons ? 6 : undefined}
296
+ {...(tabAlign === 'center' ? { flex: 1 } : {})}
297
+ {...chipProps}
298
+ {...(tabStyle as object)}
364
299
  accessibilityRole="tab"
365
300
  accessibilityState={{ selected: isActive, disabled: isDisabled }}
366
301
  >
367
- {/* Icon */}
368
302
  {item.iconRender?.(
369
303
  isActive ? resolvedActiveTextColor : resolvedTextColor,
370
304
  isActive,
371
305
  )}
372
306
 
373
- {/* Label + badge row */}
374
- <View style={S.label_wrap} onLayout={onLayoutText(index)}>
375
- <Text
376
- style={[
377
- S.label,
378
- {
379
- fontSize: hasIcons ? iconFontSize : fontSize,
380
- color: labelColor,
381
- fontWeight: isActive ? '600' : '400',
382
- marginTop: item.iconRender ? iconLabelGap : 0,
383
- },
384
- isActive && bulgeFactor !== 1 && {
385
- transform: [
386
- { scaleX: bulgeFactor },
387
- { scaleY: bulgeFactor },
388
- ],
389
- },
390
- ]}
307
+ <Stack
308
+ position="relative"
309
+ alignItems="center"
310
+ onLayout={onLayoutText(index)}
311
+ >
312
+ <StyledText
313
+ fontSize={hasIcons ? iconFontSize : fontSize}
314
+ color={labelColor}
315
+ fontWeight={isActive ? '600' : '400'}
316
+ textAlign="center"
317
+ marginTop={item.iconRender ? iconLabelGap : 0}
318
+ style={isActive && bulgeFactor !== 1
319
+ ? { transform: [{ scaleX: bulgeFactor }, { scaleY: bulgeFactor }] }
320
+ : undefined}
391
321
  >
392
322
  {item.label}
393
- </Text>
323
+ </StyledText>
394
324
 
395
- {/* Badge */}
396
325
  {item.badge !== undefined && (
397
326
  <Badge value={item.badge} color={colors.badge} />
398
327
  )}
399
- </View>
400
- </TouchableOpacity>
328
+ </Stack>
329
+ </StyledPressable>
401
330
  )
402
331
  }
403
332
 
404
333
  // ── Content ──────────────────────────────────────────────────────────────
405
- const tabs = options.map(renderTab)
406
-
407
334
  const indicatorEl = indicator ? (
408
335
  <Indicator
409
- type={indicator}
410
- left={indLeft}
411
- width={indWidth}
412
- height={indH}
413
- color={resolvedIndicatorColor}
414
- radius={indicatorRadius}
415
- visible={layoutReady}
336
+ type={indicator} left={indLeft} width={indWidth}
337
+ height={indH} color={resolvedIndicatorColor}
338
+ radius={indicatorRadius} visible={layoutReady}
416
339
  />
417
340
  ) : null
418
341
 
419
- // ── Bar wrapper ──────────────────────────────────────────────────────────
342
+ const borderProps = (showBorder || variant === 'underline')
343
+ ? { borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: colors.border }
344
+ : {}
345
+
420
346
  return (
421
- <View
347
+ <Stack
422
348
  testID={testID}
423
- style={[
424
- S.bar,
425
- {
426
- height: barHeight,
427
- backgroundColor: colors.background,
428
- borderTopWidth: showBorder || variant === 'underline'
429
- ? StyleSheet.hairlineWidth
430
- : 0,
431
- borderTopColor: colors.border,
432
- },
433
- style,
434
- ]}
349
+ flexDirection="row"
350
+ overflow="hidden"
351
+ height={barHeight}
352
+ backgroundColor={colors.background}
353
+ {...borderProps}
354
+ {...(style as object)}
435
355
  >
436
356
  {tabAlign === 'center' ? (
437
- <View style={[S.center_content, contentStyle]}>
357
+ <Stack
358
+ flex={1}
359
+ flexDirection="row"
360
+ alignItems="center"
361
+ position="relative"
362
+ {...(contentStyle as object)}
363
+ >
438
364
  {indicatorEl}
439
- {tabs}
440
- </View>
365
+ {options.map(renderTab)}
366
+ </Stack>
441
367
  ) : (
442
368
  <ScrollView
443
369
  ref={scrollRef}
444
370
  horizontal
445
371
  bounces={false}
446
372
  showsHorizontalScrollIndicator={false}
447
- style={S.scroll}
448
- contentContainerStyle={[S.scroll_content, contentStyle]}
373
+ style={{ flex: 1 }}
374
+ contentContainerStyle={[
375
+ { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 4, position: 'relative', flexGrow: 1 },
376
+ contentStyle,
377
+ ]}
449
378
  onLayout={e => { barWidthRef.current = e.nativeEvent.layout.width }}
450
379
  >
451
380
  {indicatorEl}
452
- {tabs}
381
+ {options.map(renderTab)}
453
382
  </ScrollView>
454
383
  )}
455
- </View>
384
+ </Stack>
456
385
  )
457
386
  }
458
387
 
459
- // ── Static styles ─────────────────────────────────────────────────────────────
460
-
461
- const S = StyleSheet.create({
462
- bar: {
463
- flexDirection: 'row',
464
- overflow: 'hidden',
465
- },
466
- center_content: {
467
- flex: 1,
468
- flexDirection: 'row',
469
- alignItems: 'center',
470
- position: 'relative',
471
- },
472
- scroll: {
473
- flex: 1,
474
- },
475
- scroll_content: {
476
- flexDirection: 'row',
477
- alignItems: 'center',
478
- paddingHorizontal: 4,
479
- position: 'relative',
480
- flexGrow: 1,
481
- },
482
- tab: {
483
- alignItems: 'center',
484
- justifyContent: 'center',
485
- height: '100%',
486
- flexDirection: 'column',
487
- },
488
- tab_equal: {
489
- flex: 1,
490
- },
491
- label_wrap: {
492
- position: 'relative',
493
- alignItems: 'center',
494
- },
495
- label: {
496
- textAlign: 'center',
497
- includeFontPadding: false,
498
- },
499
- })
500
-
501
388
  export const TabBar = memo(TabBarInner) as <T extends TabValue>(
502
389
  p: TabBarProps<T>,
503
- ) => React.JSX.Element
390
+ ) => React.JSX.Element
@@ -1,5 +1,5 @@
1
1
 
2
- import React, { forwardRef, ComponentType } from "react";
2
+ import React, { ComponentType, Ref, forwardRef } from "react";
3
3
  import { ViewStyle, TextStyle, ImageStyle } from "react-native";
4
4
 
5
5
  type Style = ViewStyle | TextStyle | ImageStyle;
@@ -13,13 +13,15 @@ interface StyledOptions {
13
13
  };
14
14
  }
15
15
 
16
+ // React 19 passes ref as a regular prop; React 18 and below do not.
17
+ const isReact19 = typeof React.version === "string" && parseInt(React.version) >= 19;
18
+
16
19
  const styled = <P extends object>(
17
20
  Component: ComponentType<P>,
18
21
  { base, variants }: StyledOptions = {}
19
22
  ) => {
20
- return forwardRef<any, P>((props, ref) => {
23
+ function buildStyles(options: Record<string, any>): Style {
21
24
  const styles: Style = { ...(base || {}) };
22
- const options = props as Record<string, any>;
23
25
 
24
26
  if (variants) {
25
27
  Object.keys(variants).forEach((category) => {
@@ -28,9 +30,7 @@ const styled = <P extends object>(
28
30
 
29
31
  if (typeof variantValue === "function") {
30
32
  const style = variantValue(variantSelected, options);
31
- if (style) {
32
- Object.assign(styles, style);
33
- }
33
+ if (style) Object.assign(styles, style);
34
34
  } else if (variantValue && variantValue[variantSelected]) {
35
35
  const value = variantValue[variantSelected];
36
36
  Object.assign(
@@ -41,6 +41,22 @@ const styled = <P extends object>(
41
41
  });
42
42
  }
43
43
 
44
+ return styles;
45
+ }
46
+
47
+ if (isReact19) {
48
+ // React 19: ref is a plain prop
49
+ function StyledComponent19(props: P & { ref?: Ref<any> }) {
50
+ const { ref, ...rest } = props as any;
51
+ const styles = buildStyles(rest);
52
+ return <Component {...(rest as any)} style={styles} ref={ref} />;
53
+ }
54
+ return StyledComponent19;
55
+ }
56
+
57
+ // React 18 and below: use forwardRef
58
+ return forwardRef<any, P>((props, ref) => {
59
+ const styles = buildStyles(props as Record<string, any>);
44
60
  return <Component {...(props as any)} style={styles} ref={ref} />;
45
61
  });
46
62
  };