@idealyst/components 1.3.2 → 1.3.4

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.
@@ -34,6 +34,77 @@ function renderIcon(
34
34
  return icon;
35
35
  }
36
36
 
37
+ /**
38
+ * Individual tab component to isolate useVariants calls per tab.
39
+ */
40
+ const TabItem = ({
41
+ item,
42
+ isActive,
43
+ size,
44
+ type,
45
+ iconPosition,
46
+ justify,
47
+ onPress,
48
+ onLayout,
49
+ testID,
50
+ }: {
51
+ item: TabBarItem;
52
+ isActive: boolean;
53
+ size: TabBarProps['size'];
54
+ type: TabBarProps['type'];
55
+ iconPosition: TabBarProps['iconPosition'];
56
+ justify: TabBarProps['justify'];
57
+ onPress: () => void;
58
+ onLayout: (e: LayoutChangeEvent) => void;
59
+ testID?: string;
60
+ }) => {
61
+ const iconSize = ICON_SIZES[size || 'md'] || 18;
62
+
63
+ // Apply tab variants per tab (active/disabled differ per item)
64
+ tabBarTabStyles.useVariants({
65
+ size,
66
+ type,
67
+ active: isActive,
68
+ disabled: Boolean(item.disabled),
69
+ iconPosition,
70
+ justify,
71
+ });
72
+
73
+ // Apply label variants
74
+ tabBarLabelStyles.useVariants({
75
+ size,
76
+ type,
77
+ active: isActive,
78
+ disabled: Boolean(item.disabled),
79
+ });
80
+
81
+ // Apply icon variants
82
+ tabBarIconStyles.useVariants({
83
+ size,
84
+ disabled: Boolean(item.disabled),
85
+ iconPosition,
86
+ });
87
+
88
+ const icon = renderIcon(item.icon, isActive, iconSize);
89
+
90
+ return (
91
+ <TouchableOpacity
92
+ onLayout={onLayout}
93
+ style={tabBarTabStyles.tab as any}
94
+ onPress={onPress}
95
+ disabled={item.disabled}
96
+ activeOpacity={0.7}
97
+ testID={`${testID}-tab-${item.value}`}
98
+ accessibilityRole="tab"
99
+ accessibilityLabel={item.label}
100
+ accessibilityState={{ selected: isActive, disabled: item.disabled }}
101
+ >
102
+ {icon && <View style={tabBarIconStyles.tabIcon as any}>{icon}</View>}
103
+ <Text style={tabBarLabelStyles.tabLabel as any}>{item.label}</Text>
104
+ </TouchableOpacity>
105
+ );
106
+ };
107
+
37
108
  const TabBar = forwardRef<IdealystElement, TabBarProps>(({
38
109
  items,
39
110
  value: controlledValue,
@@ -129,8 +200,10 @@ const TabBar = forwardRef<IdealystElement, TabBarProps>(({
129
200
  };
130
201
  });
131
202
 
132
- // Apply container variants (for spacing only)
203
+ // Apply container variants
133
204
  tabBarContainerStyles.useVariants({
205
+ type,
206
+ pillMode,
134
207
  justify,
135
208
  gap,
136
209
  padding,
@@ -141,9 +214,11 @@ const TabBar = forwardRef<IdealystElement, TabBarProps>(({
141
214
  marginHorizontal,
142
215
  });
143
216
 
144
- // Compute dynamic container and indicator styles
145
- const containerStyle = (tabBarContainerStyles.container as any)({ type, pillMode });
146
- const indicatorStyle = (tabBarIndicatorStyles.indicator as any)({ type, pillMode });
217
+ // Apply indicator variants
218
+ tabBarIndicatorStyles.useVariants({
219
+ type,
220
+ pillMode,
221
+ });
147
222
 
148
223
  return (
149
224
  <ScrollView
@@ -155,11 +230,11 @@ const TabBar = forwardRef<IdealystElement, TabBarProps>(({
155
230
  }}
156
231
  style={{ width: '100%' }}
157
232
  >
158
- <View ref={ref as any} nativeID={id} style={[containerStyle, style]} testID={testID} {...nativeA11yProps}>
233
+ <View ref={ref as any} nativeID={id} style={[tabBarContainerStyles.container as any, style]} testID={testID} {...nativeA11yProps}>
159
234
  {/* Animated indicator - render first so it's behind */}
160
235
  <Animated.View
161
236
  style={[
162
- indicatorStyle,
237
+ tabBarIndicatorStyles.indicator as any,
163
238
  indicatorAnimatedStyle,
164
239
  ]}
165
240
  />
@@ -168,40 +243,23 @@ const TabBar = forwardRef<IdealystElement, TabBarProps>(({
168
243
  <View style={{ flexDirection: 'row', flex: 1 }}>
169
244
  {items.map((item) => {
170
245
  const isActive = value === item.value;
171
- const iconSize = ICON_SIZES[size] || 18;
172
-
173
- // Apply icon variants (size, disabled, iconPosition)
174
- tabBarIconStyles.useVariants({
175
- size,
176
- disabled: Boolean(item.disabled),
177
- iconPosition,
178
- });
179
-
180
- // Compute dynamic styles for this tab - call as functions for theme reactivity
181
- const tabStyle = (tabBarTabStyles.tab as any)({ type, size, active: isActive, pillMode, justify });
182
- const labelStyle = (tabBarLabelStyles.tabLabel as any)({ type, active: isActive, pillMode });
183
-
184
- const icon = renderIcon(item.icon, isActive, iconSize);
185
246
 
186
247
  return (
187
- <TouchableOpacity
248
+ <TabItem
188
249
  key={item.value}
250
+ item={item}
251
+ isActive={isActive}
252
+ size={size}
253
+ type={type}
254
+ iconPosition={iconPosition}
255
+ justify={justify}
256
+ onPress={() => handleTabClick(item.value, item.disabled)}
189
257
  onLayout={(event: LayoutChangeEvent) => {
190
258
  const { x, width } = event.nativeEvent.layout;
191
259
  handleTabLayout(item.value, x, width);
192
260
  }}
193
- style={tabStyle}
194
- onPress={() => handleTabClick(item.value, item.disabled)}
195
- disabled={item.disabled}
196
- activeOpacity={0.7}
197
- testID={`${testID}-tab-${item.value}`}
198
- accessibilityRole="tab"
199
- accessibilityLabel={item.label}
200
- accessibilityState={{ selected: isActive, disabled: item.disabled }}
201
- >
202
- {icon && <View style={tabBarIconStyles.tabIcon as any}>{icon}</View>}
203
- <Text style={labelStyle}>{item.label}</Text>
204
- </TouchableOpacity>
261
+ testID={testID}
262
+ />
205
263
  );
206
264
  })}
207
265
  </View>
@@ -1,9 +1,9 @@
1
1
  /**
2
- * TabBar styles using defineStyle with dynamic props.
2
+ * TabBar styles using defineStyle with static variants + compoundVariants.
3
3
  */
4
4
  import { StyleSheet } from 'react-native-unistyles';
5
5
  import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
6
- import type { Theme as BaseTheme, Size } from '@idealyst/theme';
6
+ import type { Theme as BaseTheme } from '@idealyst/theme';
7
7
  import { ViewStyleSize } from '../utils/viewStyleProps';
8
8
 
9
9
  // Required: Unistyles must see StyleSheet usage in original source to process this file
@@ -12,19 +12,14 @@ void StyleSheet;
12
12
  // Wrap theme for $iterator support
13
13
  type Theme = ThemeStyleWrapper<BaseTheme>;
14
14
 
15
- type TabBarType = 'standard' | 'underline' | 'pills';
16
- type TabBarPillMode = 'light' | 'dark';
17
- type TabBarIconPosition = 'left' | 'top';
18
- type TabBarJustify = 'start' | 'center' | 'equal' | 'space-between';
19
-
20
- export type TabBarDynamicProps = {
21
- size?: Size;
22
- type?: TabBarType;
23
- pillMode?: TabBarPillMode;
15
+ export type TabBarVariants = {
16
+ size?: ViewStyleSize;
17
+ type?: 'standard' | 'underline' | 'pills';
18
+ pillMode?: 'light' | 'dark';
24
19
  active?: boolean;
25
20
  disabled?: boolean;
26
- iconPosition?: TabBarIconPosition;
27
- justify?: TabBarJustify;
21
+ iconPosition?: 'left' | 'top';
22
+ justify?: 'start' | 'center' | 'equal' | 'space-between';
28
23
  gap?: ViewStyleSize;
29
24
  padding?: ViewStyleSize;
30
25
  paddingVertical?: ViewStyleSize;
@@ -35,140 +30,158 @@ export type TabBarDynamicProps = {
35
30
  };
36
31
 
37
32
  /**
38
- * TabBar styles with type/pillMode/active handling.
33
+ * TabBar styles with static variants and compoundVariants.
39
34
  */
40
35
  export const tabBarStyles = defineStyle('TabBar', (theme: Theme) => ({
41
- container: ({ type = 'standard', pillMode = 'light', justify = 'start' }: TabBarDynamicProps) => {
42
- const backgroundColor = type === 'pills'
43
- ? (pillMode === 'dark' ? theme.colors.surface.inverse : theme.colors.surface.secondary)
44
- : undefined;
45
-
46
- const justifyContent = {
47
- start: 'flex-start',
48
- center: 'center',
49
- equal: 'stretch',
50
- 'space-between': 'space-between',
51
- }[justify];
52
-
53
- return {
54
- display: 'flex' as const,
55
- flexDirection: 'row' as const,
56
- gap: type === 'pills' ? 4 : 0,
57
- position: 'relative' as const,
58
- borderBottomWidth: type === 'pills' ? 0 : 1,
59
- borderBottomStyle: 'solid' as const,
60
- borderBottomColor: theme.colors.border.primary,
61
- padding: type === 'pills' ? 4 : undefined,
62
- backgroundColor: backgroundColor || (type === 'pills' ? theme.colors.surface.secondary : undefined),
63
- overflow: type === 'pills' ? ('hidden' as const) : undefined,
64
- alignSelf: type === 'pills' ? ('flex-start' as const) : undefined,
65
- width: type === 'pills' ? undefined : '100%',
66
- borderRadius: type === 'pills' ? 9999 : undefined,
67
- justifyContent: justifyContent as any,
68
- variants: {
69
- gap: {
70
- gap: theme.sizes.$view.padding,
71
- },
72
- padding: {
73
- padding: theme.sizes.$view.padding,
74
- },
75
- paddingVertical: {
76
- paddingVertical: theme.sizes.$view.padding,
77
- },
78
- paddingHorizontal: {
79
- paddingHorizontal: theme.sizes.$view.padding,
80
- },
81
- margin: {
82
- margin: theme.sizes.$view.padding,
83
- },
84
- marginVertical: {
85
- marginVertical: theme.sizes.$view.padding,
86
- },
87
- marginHorizontal: {
88
- marginHorizontal: theme.sizes.$view.padding,
36
+ container: {
37
+ display: 'flex' as const,
38
+ flexDirection: 'row' as const,
39
+ position: 'relative' as const,
40
+ borderBottomWidth: 1,
41
+ borderBottomStyle: 'solid' as const,
42
+ borderBottomColor: theme.colors.border.primary,
43
+ width: '100%',
44
+ variants: {
45
+ type: {
46
+ standard: {},
47
+ underline: {},
48
+ pills: {
49
+ gap: 4,
50
+ borderBottomWidth: 0,
51
+ padding: 4,
52
+ backgroundColor: theme.colors.surface.secondary,
53
+ overflow: 'hidden' as const,
54
+ alignSelf: 'flex-start' as const,
55
+ width: undefined,
56
+ borderRadius: 9999,
89
57
  },
90
58
  },
91
- } as const;
59
+ justify: {
60
+ start: { justifyContent: 'flex-start' as const },
61
+ center: { justifyContent: 'center' as const },
62
+ equal: { justifyContent: 'stretch' as const },
63
+ 'space-between': { justifyContent: 'space-between' as const },
64
+ },
65
+ gap: {
66
+ gap: theme.sizes.$view.padding,
67
+ },
68
+ padding: {
69
+ padding: theme.sizes.$view.padding,
70
+ },
71
+ paddingVertical: {
72
+ paddingVertical: theme.sizes.$view.padding,
73
+ },
74
+ paddingHorizontal: {
75
+ paddingHorizontal: theme.sizes.$view.padding,
76
+ },
77
+ margin: {
78
+ margin: theme.sizes.$view.padding,
79
+ },
80
+ marginVertical: {
81
+ marginVertical: theme.sizes.$view.padding,
82
+ },
83
+ marginHorizontal: {
84
+ marginHorizontal: theme.sizes.$view.padding,
85
+ },
86
+ },
87
+ compoundVariants: [
88
+ { type: 'pills', pillMode: 'dark', styles: { backgroundColor: theme.colors.surface.inverse } },
89
+ ],
92
90
  },
93
91
 
94
- tab: ({ type = 'standard', size = 'md', active = false, pillMode: _pillMode = 'light', disabled = false, iconPosition = 'left', justify = 'start' }: TabBarDynamicProps) => {
95
- // Resolve padding at runtime — can't use $iterator with runtime `type` check
96
- // Use explicit top/bottom/left/right for cross-platform compatibility
97
- const pillsPaddingMap: Record<Size, { paddingTop: number; paddingBottom: number; paddingLeft: number; paddingRight: number }> = {
98
- xs: { paddingTop: 2, paddingBottom: 2, paddingLeft: 8, paddingRight: 8 },
99
- sm: { paddingTop: 3, paddingBottom: 3, paddingLeft: 10, paddingRight: 10 },
100
- md: { paddingTop: 4, paddingBottom: 4, paddingLeft: 12, paddingRight: 12 },
101
- lg: { paddingTop: 6, paddingBottom: 6, paddingLeft: 16, paddingRight: 16 },
102
- xl: { paddingTop: 8, paddingBottom: 8, paddingLeft: 20, paddingRight: 20 },
103
- };
104
- const sizeValues = theme.sizes.tabBar[size];
105
- const tabPadding = type === 'pills'
106
- ? pillsPaddingMap[size]
107
- : {
108
- paddingTop: sizeValues.padding,
109
- paddingBottom: sizeValues.padding,
110
- paddingLeft: sizeValues.padding,
111
- paddingRight: sizeValues.padding,
112
- };
113
-
114
- // Color based on type and active state
115
- let color = active ? theme.colors.text.primary : theme.colors.text.secondary;
116
- if (active) {
117
- if (type === 'pills') color = theme.intents.primary.contrast;
118
- else if (type === 'underline') color = theme.intents.primary.primary;
119
- }
120
-
121
- return {
122
- display: 'flex' as const,
123
- flexDirection: iconPosition === 'top' ? ('column' as const) : ('row' as const),
124
- alignItems: 'center' as const,
125
- justifyContent: 'center' as const,
126
- fontWeight: '500' as const,
127
- flex: justify === 'equal' ? 1 : undefined,
128
- color,
129
- position: 'relative' as const,
130
- zIndex: 2,
131
- backgroundColor: 'transparent' as const,
132
- gap: 6,
133
- borderRadius: type === 'pills' ? 9999 : undefined,
134
- opacity: disabled ? 0.5 : 1,
135
- ...tabPadding,
136
- variants: {
137
- size: {
138
- fontSize: theme.sizes.$tabBar.fontSize,
139
- lineHeight: theme.sizes.$tabBar.lineHeight,
92
+ tab: {
93
+ display: 'flex' as const,
94
+ flexDirection: 'row' as const,
95
+ alignItems: 'center' as const,
96
+ justifyContent: 'center' as const,
97
+ fontWeight: '500' as const,
98
+ color: theme.colors.text.secondary,
99
+ position: 'relative' as const,
100
+ zIndex: 2,
101
+ backgroundColor: 'transparent' as const,
102
+ gap: 6,
103
+ opacity: 0.9,
104
+ variants: {
105
+ size: {
106
+ fontSize: theme.sizes.$tabBar.fontSize,
107
+ lineHeight: theme.sizes.$tabBar.lineHeight,
108
+ paddingTop: theme.sizes.$tabBar.padding,
109
+ paddingBottom: theme.sizes.$tabBar.padding,
110
+ paddingLeft: theme.sizes.$tabBar.padding,
111
+ paddingRight: theme.sizes.$tabBar.padding,
112
+ },
113
+ type: {
114
+ standard: {},
115
+ underline: {},
116
+ pills: {
117
+ borderRadius: 9999,
118
+ },
119
+ },
120
+ active: {
121
+ true: {
122
+ color: theme.colors.text.primary,
123
+ opacity: 1,
140
124
  },
125
+ false: {},
126
+ },
127
+ disabled: {
128
+ true: { opacity: 0.5 },
129
+ false: {},
130
+ },
131
+ iconPosition: {
132
+ top: { flexDirection: 'column' as const },
133
+ left: { flexDirection: 'row' as const },
141
134
  },
142
- _web: {
143
- border: 'none',
144
- cursor: disabled ? 'not-allowed' : 'pointer',
145
- outline: 'none',
146
- transition: 'color 0.2s ease',
147
- _hover: disabled ? {} : { color: theme.colors.text.primary },
135
+ justify: {
136
+ start: {},
137
+ center: {},
138
+ equal: { flex: 1 },
139
+ 'space-between': {},
148
140
  },
149
- } as const;
141
+ },
142
+ compoundVariants: [
143
+ // Active + underline: use primary intent color
144
+ { type: 'underline', active: true, styles: { color: theme.intents.primary.primary } },
145
+ // Active + pills: use contrast color
146
+ { type: 'pills', active: true, styles: { color: theme.intents.primary.contrast } },
147
+ // Pills type: tighter padding per size (from theme)
148
+ { type: 'pills', size: 'xs', styles: { paddingTop: theme.sizes.tabBar.xs.pillPaddingVertical, paddingBottom: theme.sizes.tabBar.xs.pillPaddingVertical, paddingLeft: theme.sizes.tabBar.xs.pillPaddingHorizontal, paddingRight: theme.sizes.tabBar.xs.pillPaddingHorizontal } },
149
+ { type: 'pills', size: 'sm', styles: { paddingTop: theme.sizes.tabBar.sm.pillPaddingVertical, paddingBottom: theme.sizes.tabBar.sm.pillPaddingVertical, paddingLeft: theme.sizes.tabBar.sm.pillPaddingHorizontal, paddingRight: theme.sizes.tabBar.sm.pillPaddingHorizontal } },
150
+ { type: 'pills', size: 'md', styles: { paddingTop: theme.sizes.tabBar.md.pillPaddingVertical, paddingBottom: theme.sizes.tabBar.md.pillPaddingVertical, paddingLeft: theme.sizes.tabBar.md.pillPaddingHorizontal, paddingRight: theme.sizes.tabBar.md.pillPaddingHorizontal } },
151
+ { type: 'pills', size: 'lg', styles: { paddingTop: theme.sizes.tabBar.lg.pillPaddingVertical, paddingBottom: theme.sizes.tabBar.lg.pillPaddingVertical, paddingLeft: theme.sizes.tabBar.lg.pillPaddingHorizontal, paddingRight: theme.sizes.tabBar.lg.pillPaddingHorizontal } },
152
+ { type: 'pills', size: 'xl', styles: { paddingTop: theme.sizes.tabBar.xl.pillPaddingVertical, paddingBottom: theme.sizes.tabBar.xl.pillPaddingVertical, paddingLeft: theme.sizes.tabBar.xl.pillPaddingHorizontal, paddingRight: theme.sizes.tabBar.xl.pillPaddingHorizontal } },
153
+ ],
154
+ _web: {
155
+ border: 'none',
156
+ cursor: 'pointer',
157
+ outline: 'none',
158
+ transition: 'color 0.2s ease, opacity 0.2s ease',
159
+ },
150
160
  },
151
161
 
152
- tabLabel: ({ type = 'standard', active = false, pillMode: _pillMode = 'light', disabled = false }: TabBarDynamicProps) => {
153
- let color = active ? theme.colors.text.primary : theme.colors.text.secondary;
154
- if (active) {
155
- if (type === 'pills') color = theme.colors.text.primary;
156
- else if (type === 'underline') color = theme.intents.primary.primary;
157
- }
158
-
159
- return {
160
- position: 'relative' as const,
161
- zIndex: 3,
162
- fontWeight: '500' as const,
163
- color,
164
- opacity: disabled ? 0.5 : 1,
165
- variants: {
166
- size: {
167
- fontSize: theme.sizes.$tabBar.fontSize,
168
- lineHeight: theme.sizes.$tabBar.lineHeight,
169
- },
162
+ tabLabel: {
163
+ position: 'relative' as const,
164
+ zIndex: 3,
165
+ fontWeight: '500' as const,
166
+ color: theme.colors.text.secondary,
167
+ variants: {
168
+ size: {
169
+ fontSize: theme.sizes.$tabBar.fontSize,
170
+ lineHeight: theme.sizes.$tabBar.lineHeight,
170
171
  },
171
- } as const;
172
+ active: {
173
+ true: { color: theme.colors.text.primary },
174
+ false: {},
175
+ },
176
+ disabled: {
177
+ true: { opacity: 0.5 },
178
+ false: { opacity: 1 },
179
+ },
180
+ },
181
+ compoundVariants: [
182
+ { type: 'underline', active: true, styles: { color: theme.intents.primary.primary } },
183
+ { type: 'pills', active: true, styles: { color: theme.colors.text.primary } },
184
+ ],
172
185
  },
173
186
 
174
187
  tabIcon: {
@@ -191,31 +204,33 @@ export const tabBarStyles = defineStyle('TabBar', (theme: Theme) => ({
191
204
  },
192
205
  },
193
206
 
194
- indicator: ({ type = 'standard', pillMode = 'light' }: TabBarDynamicProps) => {
195
- const backgroundColor = type === 'pills'
196
- ? (pillMode === 'dark' ? theme.colors.surface.secondary : theme.colors.surface.tertiary)
197
- : theme.intents.primary.primary;
198
-
199
- const typeStyles = type === 'pills' ? {
200
- borderRadius: 9999,
201
- top: 4,
202
- bottom: 4,
203
- left: 0,
204
- } : {
205
- bottom: -1,
206
- height: 2,
207
- };
208
-
209
- return {
210
- position: 'absolute' as const,
211
- pointerEvents: 'none' as const,
212
- zIndex: 1,
213
- backgroundColor,
214
- ...typeStyles,
215
- _web: {
216
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
217
- },
218
- } as const;
207
+ indicator: {
208
+ position: 'absolute' as const,
209
+ pointerEvents: 'none' as const,
210
+ zIndex: 1,
211
+ backgroundColor: theme.intents.primary.primary,
212
+ bottom: -1,
213
+ height: 2,
214
+ variants: {
215
+ type: {
216
+ standard: {},
217
+ underline: {},
218
+ pills: {
219
+ borderRadius: 9999,
220
+ top: 4,
221
+ bottom: 4,
222
+ height: undefined,
223
+ left: 0,
224
+ backgroundColor: theme.colors.surface.tertiary,
225
+ },
226
+ },
227
+ },
228
+ compoundVariants: [
229
+ { type: 'pills', pillMode: 'dark', styles: { backgroundColor: theme.colors.surface.secondary } },
230
+ ],
231
+ _web: {
232
+ transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
233
+ },
219
234
  },
220
235
  }));
221
236
 
@@ -66,29 +66,33 @@ const Tab: React.FC<TabProps> = ({
66
66
  }) => {
67
67
  const iconSize = ICON_SIZES[size || 'md'] || 18;
68
68
 
69
- // Apply tab variants (size) for variant expansion (padding, fontSize, lineHeight)
69
+ // Apply tab variants
70
70
  tabBarTabStyles.useVariants({
71
71
  size,
72
+ type,
73
+ active: isActive,
74
+ disabled: Boolean(item.disabled),
75
+ iconPosition,
76
+ justify,
72
77
  });
73
78
 
74
- // Apply label variants (size) for variant expansion
79
+ // Apply label variants
75
80
  tabBarLabelStyles.useVariants({
76
81
  size,
82
+ type,
83
+ active: isActive,
84
+ disabled: Boolean(item.disabled),
77
85
  });
78
86
 
79
- // Apply icon variants (size, disabled, iconPosition)
87
+ // Apply icon variants
80
88
  tabBarIconStyles.useVariants({
81
89
  size,
82
90
  disabled: Boolean(item.disabled),
83
91
  iconPosition,
84
92
  });
85
93
 
86
- // Compute dynamic styles for this tab
87
- const tabStyle = (tabBarTabStyles.tab as any)({ type, size, active: isActive, pillMode, justify });
88
- const labelStyle = (tabBarLabelStyles.tabLabel as any)({ type, active: isActive, pillMode });
89
-
90
- const tabProps = getWebProps([tabStyle]);
91
- const labelProps = getWebProps([labelStyle]);
94
+ const tabProps = getWebProps([tabBarTabStyles.tab as any]);
95
+ const labelProps = getWebProps([tabBarLabelStyles.tabLabel as any]);
92
96
  const iconProps = getWebProps([tabBarIconStyles.tabIcon as any]);
93
97
 
94
98
  // Merge refs from getWebProps with our tracking ref
@@ -262,8 +266,10 @@ const TabBar: React.FC<TabBarProps> = ({
262
266
  onChange?.(itemValue);
263
267
  };
264
268
 
265
- // Apply container variants (for spacing only)
266
- (tabBarContainerStyles.useVariants as any)({
269
+ // Apply container variants
270
+ tabBarContainerStyles.useVariants({
271
+ type,
272
+ pillMode,
267
273
  justify,
268
274
  gap,
269
275
  padding,
@@ -274,12 +280,14 @@ const TabBar: React.FC<TabBarProps> = ({
274
280
  marginHorizontal,
275
281
  });
276
282
 
277
- // Compute dynamic container and indicator styles
278
- const containerStyle = (tabBarContainerStyles.container as any)({ type, pillMode });
279
- const containerProps = getWebProps([containerStyle, style as any]);
283
+ // Apply indicator variants
284
+ tabBarIndicatorStyles.useVariants({
285
+ type,
286
+ pillMode,
287
+ });
280
288
 
281
- const indicatorVisualStyle = (tabBarIndicatorStyles.indicator as any)({ type, pillMode });
282
- const indicatorProps = getWebProps([indicatorVisualStyle]);
289
+ const containerProps = getWebProps([tabBarContainerStyles.container as any, style as any]);
290
+ const indicatorProps = getWebProps([tabBarIndicatorStyles.indicator as any]);
283
291
 
284
292
  // Merge container ref with getWebProps ref
285
293
  const mergedContainerRef = useMergeRefs<HTMLDivElement>(