@idealyst/components 1.0.83 → 1.0.84

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 (316) hide show
  1. package/CLAUDE.md +199 -232
  2. package/README.md +5 -5
  3. package/package.json +20 -2
  4. package/plugin/README.md +272 -0
  5. package/plugin/test-cases.jsx +112 -0
  6. package/plugin/web-legacy.js +320 -0
  7. package/plugin/web.js +422 -124
  8. package/src/Accordion/Accordion.native.tsx +182 -0
  9. package/src/Accordion/Accordion.styles.tsx +260 -0
  10. package/src/Accordion/Accordion.web.tsx +147 -0
  11. package/src/Accordion/index.native.tsx +3 -0
  12. package/src/Accordion/index.ts +3 -0
  13. package/src/Accordion/index.web.tsx +3 -0
  14. package/src/Accordion/types.ts +23 -0
  15. package/src/ActivityIndicator/ActivityIndicator.native.tsx +17 -12
  16. package/src/ActivityIndicator/ActivityIndicator.styles.tsx +83 -109
  17. package/src/ActivityIndicator/ActivityIndicator.web.tsx +23 -17
  18. package/src/ActivityIndicator/index.ts +5 -2
  19. package/src/ActivityIndicator/index.web.ts +5 -2
  20. package/src/ActivityIndicator/types.ts +15 -10
  21. package/src/Alert/Alert.native.tsx +113 -0
  22. package/src/Alert/Alert.styles.tsx +304 -0
  23. package/src/Alert/Alert.web.tsx +123 -0
  24. package/src/Alert/index.native.ts +5 -0
  25. package/src/Alert/index.ts +5 -0
  26. package/src/Alert/index.web.ts +5 -0
  27. package/src/Alert/types.ts +21 -0
  28. package/src/Avatar/Avatar.native.tsx +8 -6
  29. package/src/Avatar/Avatar.styles.tsx +64 -58
  30. package/src/Avatar/Avatar.web.tsx +13 -8
  31. package/src/Avatar/index.ts +5 -2
  32. package/src/Avatar/index.web.ts +5 -2
  33. package/src/Avatar/types.ts +19 -13
  34. package/src/Badge/Badge.native.tsx +59 -14
  35. package/src/Badge/Badge.styles.tsx +125 -139
  36. package/src/Badge/Badge.web.tsx +72 -16
  37. package/src/Badge/index.ts +5 -2
  38. package/src/Badge/index.web.ts +5 -2
  39. package/src/Badge/types.ts +23 -11
  40. package/src/Breadcrumb/Breadcrumb.native.tsx +225 -0
  41. package/src/Breadcrumb/Breadcrumb.styles.tsx +234 -0
  42. package/src/Breadcrumb/Breadcrumb.web.tsx +268 -0
  43. package/src/Breadcrumb/index.native.ts +5 -0
  44. package/src/Breadcrumb/index.ts +5 -0
  45. package/src/Breadcrumb/index.web.ts +5 -0
  46. package/src/Breadcrumb/types.ts +56 -0
  47. package/src/Button/Button.native.tsx +75 -24
  48. package/src/Button/Button.styles.tsx +248 -205
  49. package/src/Button/Button.web.tsx +82 -25
  50. package/src/Button/index.ts +5 -5
  51. package/src/Button/index.web.ts +5 -3
  52. package/src/Button/types.ts +32 -15
  53. package/src/Card/Card.native.tsx +14 -11
  54. package/src/Card/Card.styles.tsx +146 -220
  55. package/src/Card/Card.web.tsx +20 -21
  56. package/src/Card/index.ts +5 -5
  57. package/src/Card/index.web.ts +5 -3
  58. package/src/Card/types.ts +24 -17
  59. package/src/Checkbox/Checkbox.native.tsx +24 -34
  60. package/src/Checkbox/Checkbox.styles.tsx +223 -275
  61. package/src/Checkbox/Checkbox.web.tsx +30 -37
  62. package/src/Checkbox/index.ts +5 -5
  63. package/src/Checkbox/index.web.ts +5 -3
  64. package/src/Checkbox/types.ts +26 -20
  65. package/src/Chip/Chip.native.tsx +126 -0
  66. package/src/Chip/Chip.styles.tsx +138 -0
  67. package/src/Chip/Chip.web.tsx +154 -0
  68. package/src/Chip/index.native.ts +5 -0
  69. package/src/Chip/index.ts +5 -0
  70. package/src/Chip/index.web.ts +5 -0
  71. package/src/Chip/types.ts +51 -0
  72. package/src/Dialog/Dialog.native.tsx +65 -12
  73. package/src/Dialog/Dialog.styles.tsx +154 -136
  74. package/src/Dialog/Dialog.web.tsx +16 -11
  75. package/src/Dialog/index.ts +5 -2
  76. package/src/Dialog/index.web.ts +5 -2
  77. package/src/Dialog/types.ts +22 -16
  78. package/src/Divider/Divider.native.tsx +19 -14
  79. package/src/Divider/Divider.styles.tsx +273 -595
  80. package/src/Divider/Divider.web.tsx +19 -12
  81. package/src/Divider/index.ts +5 -5
  82. package/src/Divider/index.web.ts +5 -3
  83. package/src/Divider/types.ts +28 -19
  84. package/src/Icon/Icon.native.tsx +17 -24
  85. package/src/Icon/Icon.styles.tsx +64 -48
  86. package/src/Icon/Icon.web.tsx +14 -11
  87. package/src/Icon/IconSvg/IconSvg.native.tsx +42 -0
  88. package/src/Icon/IconSvg/IconSvg.web.tsx +40 -0
  89. package/src/Icon/IconSvg/index.native.ts +1 -0
  90. package/src/Icon/IconSvg/index.ts +1 -0
  91. package/src/Icon/icon-resolver.native.ts +27 -0
  92. package/src/Icon/icon-resolver.ts +70 -0
  93. package/src/Icon/index.ts +5 -5
  94. package/src/Icon/index.web.ts +5 -3
  95. package/src/Icon/types.ts +17 -11
  96. package/src/Image/Image.native.tsx +86 -0
  97. package/src/Image/Image.styles.tsx +57 -0
  98. package/src/Image/Image.web.tsx +92 -0
  99. package/src/Image/index.native.ts +5 -0
  100. package/src/Image/index.ts +5 -0
  101. package/src/Image/types.ts +21 -0
  102. package/src/Input/Input.native.tsx +103 -26
  103. package/src/Input/Input.styles.tsx +240 -177
  104. package/src/Input/Input.web.tsx +141 -38
  105. package/src/Input/index.ts +5 -5
  106. package/src/Input/index.web.ts +5 -3
  107. package/src/Input/types.ts +43 -20
  108. package/src/List/List.native.tsx +56 -0
  109. package/src/List/List.styles.tsx +257 -0
  110. package/src/List/List.web.tsx +43 -0
  111. package/src/List/ListContext.tsx +16 -0
  112. package/src/List/ListItem.native.tsx +111 -0
  113. package/src/List/ListItem.web.tsx +110 -0
  114. package/src/List/ListSection.native.tsx +31 -0
  115. package/src/List/ListSection.web.tsx +33 -0
  116. package/src/List/index.native.tsx +5 -0
  117. package/src/List/index.ts +5 -0
  118. package/src/List/index.web.tsx +5 -0
  119. package/src/List/types.ts +42 -0
  120. package/src/Menu/Menu.native.tsx +150 -0
  121. package/src/Menu/Menu.styles.tsx +185 -0
  122. package/src/Menu/Menu.web.tsx +99 -0
  123. package/src/Menu/MenuItem.native.tsx +66 -0
  124. package/src/Menu/MenuItem.styles.tsx +119 -0
  125. package/src/Menu/MenuItem.web.tsx +67 -0
  126. package/src/Menu/index.native.ts +3 -0
  127. package/src/Menu/index.ts +3 -0
  128. package/src/Menu/index.web.ts +3 -0
  129. package/src/Menu/types.ts +30 -0
  130. package/src/Popover/Popover.native.tsx +102 -32
  131. package/src/Popover/Popover.styles.tsx +100 -67
  132. package/src/Popover/Popover.web.tsx +36 -260
  133. package/src/Popover/index.ts +5 -2
  134. package/src/Popover/index.web.ts +5 -2
  135. package/src/Popover/types.ts +14 -13
  136. package/src/Pressable/Pressable.native.tsx +7 -6
  137. package/src/Pressable/Pressable.web.tsx +8 -6
  138. package/src/Pressable/index.ts +5 -2
  139. package/src/Pressable/index.web.ts +5 -2
  140. package/src/Pressable/types.ts +11 -10
  141. package/src/Progress/Progress.native.tsx +179 -0
  142. package/src/Progress/Progress.styles.tsx +164 -0
  143. package/src/Progress/Progress.web.tsx +144 -0
  144. package/src/Progress/index.native.ts +1 -0
  145. package/src/Progress/index.ts +5 -0
  146. package/src/Progress/index.web.ts +5 -0
  147. package/src/Progress/types.ts +21 -0
  148. package/src/RadioButton/RadioButton.native.tsx +88 -0
  149. package/src/RadioButton/RadioButton.styles.tsx +163 -0
  150. package/src/RadioButton/RadioButton.web.tsx +85 -0
  151. package/src/RadioButton/RadioGroup.native.tsx +43 -0
  152. package/src/RadioButton/RadioGroup.web.tsx +49 -0
  153. package/src/RadioButton/index.native.ts +2 -0
  154. package/src/RadioButton/index.ts +2 -0
  155. package/src/RadioButton/index.web.ts +2 -0
  156. package/src/RadioButton/types.ts +29 -0
  157. package/src/SVGImage/SVGImage.native.tsx +9 -7
  158. package/src/SVGImage/SVGImage.styles.tsx +63 -55
  159. package/src/SVGImage/SVGImage.web.tsx +16 -13
  160. package/src/SVGImage/index.ts +5 -5
  161. package/src/SVGImage/index.web.ts +5 -2
  162. package/src/SVGImage/types.ts +7 -3
  163. package/src/Screen/Screen.native.tsx +43 -17
  164. package/src/Screen/Screen.styles.tsx +58 -54
  165. package/src/Screen/Screen.web.tsx +11 -5
  166. package/src/Screen/index.ts +5 -2
  167. package/src/Screen/index.web.ts +5 -2
  168. package/src/Screen/types.ts +23 -9
  169. package/src/Select/Select.native.tsx +140 -63
  170. package/src/Select/Select.styles.tsx +312 -302
  171. package/src/Select/Select.web.tsx +156 -316
  172. package/src/Select/index.ts +5 -2
  173. package/src/Select/index.web.ts +5 -2
  174. package/src/Select/types.ts +13 -7
  175. package/src/Skeleton/Skeleton.native.tsx +139 -0
  176. package/src/Skeleton/Skeleton.styles.tsx +59 -0
  177. package/src/Skeleton/Skeleton.web.tsx +112 -0
  178. package/src/Skeleton/index.native.ts +4 -0
  179. package/src/Skeleton/index.ts +5 -0
  180. package/src/Skeleton/index.web.ts +5 -0
  181. package/src/Skeleton/types.ts +75 -0
  182. package/src/Slider/Slider.native.tsx +248 -0
  183. package/src/Slider/Slider.styles.tsx +241 -0
  184. package/src/Slider/Slider.web.tsx +226 -0
  185. package/src/Slider/index.native.ts +3 -0
  186. package/src/Slider/index.ts +5 -0
  187. package/src/Slider/index.web.ts +5 -0
  188. package/src/Slider/types.ts +31 -0
  189. package/src/Switch/Switch.native.tsx +131 -0
  190. package/src/Switch/Switch.styles.tsx +169 -0
  191. package/src/Switch/Switch.web.tsx +121 -0
  192. package/src/Switch/index.native.ts +3 -0
  193. package/src/Switch/index.ts +5 -0
  194. package/src/Switch/index.web.ts +5 -0
  195. package/src/Switch/types.ts +21 -0
  196. package/src/TabBar/TabBar.native.tsx +142 -0
  197. package/src/TabBar/TabBar.styles.tsx +399 -0
  198. package/src/TabBar/TabBar.web.tsx +205 -0
  199. package/src/TabBar/index.native.tsx +3 -0
  200. package/src/TabBar/index.ts +3 -0
  201. package/src/TabBar/index.web.tsx +3 -0
  202. package/src/TabBar/types.ts +26 -0
  203. package/src/Table/Table.native.tsx +122 -0
  204. package/src/Table/Table.styles.tsx +283 -0
  205. package/src/Table/Table.web.tsx +112 -0
  206. package/src/Table/index.native.tsx +3 -0
  207. package/src/Table/index.ts +3 -0
  208. package/src/Table/index.web.tsx +3 -0
  209. package/src/Table/types.ts +28 -0
  210. package/src/Text/Text.native.tsx +12 -11
  211. package/src/Text/Text.styles.tsx +76 -64
  212. package/src/Text/Text.web.tsx +14 -9
  213. package/src/Text/index.ts +5 -5
  214. package/src/Text/index.web.ts +5 -3
  215. package/src/Text/types.ts +20 -13
  216. package/src/TextArea/TextArea.native.tsx +134 -0
  217. package/src/TextArea/TextArea.styles.tsx +175 -0
  218. package/src/TextArea/TextArea.web.tsx +156 -0
  219. package/src/TextArea/index.native.ts +3 -0
  220. package/src/TextArea/index.ts +3 -0
  221. package/src/TextArea/index.web.ts +3 -0
  222. package/src/TextArea/types.ts +30 -0
  223. package/src/Tooltip/Tooltip.native.tsx +165 -0
  224. package/src/Tooltip/Tooltip.styles.tsx +73 -0
  225. package/src/Tooltip/Tooltip.web.tsx +87 -0
  226. package/src/Tooltip/index.native.ts +3 -0
  227. package/src/Tooltip/index.ts +3 -0
  228. package/src/Tooltip/types.ts +18 -0
  229. package/src/Video/Video.native.tsx +105 -0
  230. package/src/Video/Video.styles.tsx +39 -0
  231. package/src/Video/Video.web.tsx +115 -0
  232. package/src/Video/index.native.ts +5 -0
  233. package/src/Video/index.ts +5 -0
  234. package/src/Video/types.ts +29 -0
  235. package/src/View/View.native.tsx +9 -14
  236. package/src/View/View.styles.tsx +101 -93
  237. package/src/View/View.web.tsx +16 -17
  238. package/src/View/index.ts +5 -5
  239. package/src/View/index.web.ts +5 -3
  240. package/src/View/types.ts +29 -21
  241. package/src/examples/AccordionExamples.tsx +126 -0
  242. package/src/examples/AlertExamples.tsx +280 -0
  243. package/src/examples/AvatarExamples.tsx +23 -23
  244. package/src/examples/BadgeExamples.tsx +109 -41
  245. package/src/examples/BreadcrumbExamples.tsx +312 -0
  246. package/src/examples/ButtonExamples.tsx +160 -33
  247. package/src/examples/CardExamples.tsx +40 -40
  248. package/src/examples/CheckboxExamples.tsx +12 -12
  249. package/src/examples/ChipExamples.tsx +197 -0
  250. package/src/examples/DialogExamples.tsx +22 -22
  251. package/src/examples/DividerExamples.tsx +49 -49
  252. package/src/examples/IconExamples.tsx +270 -54
  253. package/src/examples/ImageExamples.tsx +174 -0
  254. package/src/examples/InputExamples.tsx +75 -17
  255. package/src/examples/ListExamples.tsx +288 -0
  256. package/src/examples/MenuExamples.tsx +144 -0
  257. package/src/examples/PopoverExamples.tsx +69 -73
  258. package/src/examples/ProgressExamples.tsx +137 -0
  259. package/src/examples/RadioButtonExamples.tsx +161 -0
  260. package/src/examples/SVGImageExamples.tsx +19 -17
  261. package/src/examples/ScreenExamples.tsx +31 -31
  262. package/src/examples/SelectExamples.tsx +67 -67
  263. package/src/examples/SkeletonExamples.tsx +206 -0
  264. package/src/examples/SliderExamples.tsx +200 -0
  265. package/src/examples/SwitchExamples.tsx +182 -0
  266. package/src/examples/TabBarExamples.tsx +143 -0
  267. package/src/examples/TableExamples.tsx +280 -0
  268. package/src/examples/TextAreaExamples.tsx +173 -0
  269. package/src/examples/TextExamples.tsx +28 -32
  270. package/src/examples/ThemeExtensionExamples.tsx +10 -10
  271. package/src/examples/TooltipExamples.tsx +126 -0
  272. package/src/examples/VideoExamples.tsx +144 -0
  273. package/src/examples/ViewExamples.tsx +64 -56
  274. package/src/examples/index.ts +17 -3
  275. package/src/hooks/useMergeRefs.ts +16 -0
  276. package/src/hooks/useSmartPosition.native.ts +169 -0
  277. package/src/index.native.ts +80 -9
  278. package/src/index.ts +71 -1
  279. package/src/internal/BoundedModalContent.native.tsx +58 -0
  280. package/src/internal/PositionedPortal.tsx +254 -0
  281. package/src/internal/SafeAreaDebugOverlay.native.tsx +173 -0
  282. package/src/unistyles.d.ts +6 -0
  283. package/src/utils/buildSizeVariants.ts +16 -0
  284. package/src/utils/deepMerge.ts +43 -0
  285. package/src/utils/positionUtils.native.ts +280 -0
  286. package/src/utils/styleHelpers.ts +48 -0
  287. package/LLM-ACCESS-GUIDE.md +0 -143
  288. package/src/ActivityIndicator/README.md +0 -132
  289. package/src/Avatar/README.md +0 -139
  290. package/src/Badge/README.md +0 -170
  291. package/src/Button/Button.types.ts +0 -12
  292. package/src/Button/README.md +0 -262
  293. package/src/Card/README.md +0 -258
  294. package/src/Checkbox/README.md +0 -102
  295. package/src/Dialog/README.md +0 -210
  296. package/src/Divider/README.md +0 -108
  297. package/src/Icon/README.md +0 -81
  298. package/src/Input/README.md +0 -100
  299. package/src/SVGImage/README.md +0 -209
  300. package/src/Screen/README.md +0 -86
  301. package/src/Select/README.md +0 -166
  302. package/src/Text/README.md +0 -94
  303. package/src/View/README.md +0 -107
  304. package/src/examples/AllExamples.tsx +0 -88
  305. package/src/examples/README.md +0 -136
  306. package/src/examples/ValidationExamples.tsx +0 -95
  307. package/src/examples/extendedTheme.ts +0 -329
  308. package/src/theme/breakpoints.ts +0 -8
  309. package/src/theme/colorResolver.ts +0 -218
  310. package/src/theme/colors.ts +0 -315
  311. package/src/theme/defaultThemes.ts +0 -326
  312. package/src/theme/index.ts +0 -188
  313. package/src/theme/themeBuilder.ts +0 -602
  314. package/src/theme/unistyles.d.ts +0 -6
  315. package/src/theme/variantHelpers.ts +0 -584
  316. package/src/theme/variants.ts +0 -56
@@ -0,0 +1,399 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+ import { Theme, StylesheetStyles, CompoundVariants, Size } from '@idealyst/theme';
3
+ import { buildSizeVariants } from '../utils/buildSizeVariants';
4
+ import { TabBarPillMode, TabBarSizeVariant, TabBarType } from './types';
5
+
6
+ type TabBarContainerVariants = {
7
+ type: TabBarType;
8
+ size: TabBarSizeVariant;
9
+ pillMode: TabBarPillMode;
10
+ }
11
+
12
+ type TabBarTabVariants = {
13
+ size: TabBarSizeVariant;
14
+ type: TabBarType;
15
+ active: boolean;
16
+ disabled: boolean;
17
+ pillMode: TabBarPillMode;
18
+ }
19
+
20
+ type TabBarLabelVariants = {
21
+ size: TabBarSizeVariant;
22
+ type: TabBarType;
23
+ active: boolean;
24
+ disabled: boolean;
25
+ pillMode: TabBarPillMode;
26
+ }
27
+
28
+ type TabBarIndicatorVariants = {
29
+ type: TabBarType;
30
+ pillMode: TabBarPillMode;
31
+ }
32
+
33
+ /**
34
+ * Create compound variants for container
35
+ */
36
+ function createContainerCompoundVariants(theme: Theme): CompoundVariants<keyof TabBarContainerVariants> {
37
+ return [
38
+ {
39
+ type: 'pills',
40
+ pillMode: 'light',
41
+ styles: {
42
+ backgroundColor: theme.colors.surface.secondary,
43
+ },
44
+ },
45
+ {
46
+ type: 'pills',
47
+ pillMode: 'dark',
48
+ styles: {
49
+ backgroundColor: theme.colors.surface.inverse,
50
+ },
51
+ },
52
+ ];
53
+ }
54
+
55
+ /**
56
+ * Create size variants for tab
57
+ */
58
+ function createTabSizeVariants(theme: Theme) {
59
+ return buildSizeVariants(theme, 'tabBar', (size) => ({
60
+ fontSize: size.fontSize,
61
+ padding: size.padding,
62
+ lineHeight: size.lineHeight,
63
+ }));
64
+ }
65
+
66
+ /**
67
+ * Create compound variants for tab
68
+ */
69
+ function createTabCompoundVariants(theme: Theme): CompoundVariants<keyof TabBarTabVariants> {
70
+ return [
71
+ // Pills variant - compact padding for all sizes
72
+ {
73
+ type: 'pills',
74
+ size: 'xs',
75
+ styles: {
76
+ paddingVertical: 2,
77
+ paddingHorizontal: 10,
78
+ },
79
+ },
80
+ {
81
+ type: 'pills',
82
+ size: 'sm',
83
+ styles: {
84
+ paddingVertical: 4,
85
+ paddingHorizontal: 12,
86
+ },
87
+ },
88
+ {
89
+ type: 'pills',
90
+ size: 'md',
91
+ styles: {
92
+ paddingVertical: 6,
93
+ paddingHorizontal: 16,
94
+ },
95
+ },
96
+ {
97
+ type: 'pills',
98
+ size: 'lg',
99
+ styles: {
100
+ paddingVertical: 8,
101
+ paddingHorizontal: 20,
102
+ },
103
+ },
104
+ {
105
+ type: 'pills',
106
+ size: 'xl',
107
+ styles: {
108
+ paddingVertical: 10,
109
+ paddingHorizontal: 24,
110
+ },
111
+ },
112
+ // Pills variant - active text color
113
+ {
114
+ type: 'pills',
115
+ active: true,
116
+ styles: {
117
+ color: theme.intents.primary.contrast,
118
+ },
119
+ },
120
+ // Underline variant - active text color
121
+ {
122
+ type: 'underline',
123
+ active: true,
124
+ styles: {
125
+ color: theme.intents.primary.primary,
126
+ },
127
+ },
128
+ ] as const;
129
+ }
130
+
131
+ /**
132
+ * Create size variants for label
133
+ */
134
+ function createLabelSizeVariants(theme: Theme) {
135
+ return buildSizeVariants(theme, 'tabBar', (size) => ({
136
+ fontSize: size.fontSize,
137
+ lineHeight: size.lineHeight,
138
+ }));
139
+ }
140
+
141
+ /**
142
+ * Create compound variants for label
143
+ */
144
+ function createLabelCompoundVariants(theme: Theme): CompoundVariants<keyof TabBarLabelVariants> {
145
+ return [
146
+ // Pills light mode - light text on active (dark pill)
147
+ {
148
+ type: 'pills',
149
+ pillMode: 'light',
150
+ active: true,
151
+ styles: {
152
+ color: theme.colors.text.primary,
153
+ },
154
+ },
155
+ // Pills dark mode - dark text on active (light pill)
156
+ {
157
+ type: 'pills',
158
+ pillMode: 'dark',
159
+ active: true,
160
+ styles: {
161
+ color: theme.colors.text.primary,
162
+ },
163
+ },
164
+ // Underline variant - active text color
165
+ {
166
+ type: 'underline',
167
+ active: true,
168
+ styles: {
169
+ color: theme.intents.primary.primary,
170
+ },
171
+ },
172
+ ] as const;
173
+ }
174
+
175
+ /**
176
+ * Create compound variants for indicator
177
+ */
178
+ function createIndicatorCompoundVariants(theme: Theme): CompoundVariants<keyof TabBarIndicatorVariants> {
179
+ return [
180
+ // Pills light mode - darker pill
181
+ {
182
+ type: 'pills',
183
+ pillMode: 'light',
184
+ styles: {
185
+ backgroundColor: theme.colors.surface.tertiary,
186
+ },
187
+ },
188
+ // Pills dark mode - lighter pill
189
+ {
190
+ type: 'pills',
191
+ pillMode: 'dark',
192
+ styles: {
193
+ backgroundColor: theme.colors.surface.secondary,
194
+ },
195
+ },
196
+ ] as const;
197
+ }
198
+
199
+ const createContainerStyles = (theme: Theme) => {
200
+ return {
201
+ display: 'flex',
202
+ flexDirection: 'row',
203
+ gap: 0,
204
+ position: 'relative',
205
+ borderBottomWidth: 1,
206
+ borderBottomStyle: 'solid',
207
+ borderBottomColor: theme.colors.border.primary,
208
+ variants: {
209
+ type: {
210
+ standard: {},
211
+ pills: {
212
+ borderBottomWidth: 0,
213
+ padding: 4,
214
+ gap: 4,
215
+ backgroundColor: theme.colors.surface.secondary,
216
+ overflow: 'hidden',
217
+ alignSelf: 'flex-start',
218
+ },
219
+ underline: {},
220
+ },
221
+ size: {
222
+ xs: {},
223
+ sm: {},
224
+ md: {},
225
+ lg: {},
226
+ xl: {},
227
+ },
228
+ pillMode: {
229
+ light: {},
230
+ dark: {},
231
+ },
232
+ } as const,
233
+ compoundVariants: createContainerCompoundVariants(theme),
234
+ } as const;
235
+ }
236
+
237
+ const createTabStyles = (theme: Theme) => {
238
+ return {
239
+ display: 'flex',
240
+ flexDirection: 'row',
241
+ alignItems: 'center',
242
+ justifyContent: 'center',
243
+ fontWeight: '500',
244
+ color: theme.colors.text.secondary,
245
+ position: 'relative',
246
+ zIndex: 2,
247
+ backgroundColor: 'transparent',
248
+ variants: {
249
+ size: createTabSizeVariants(theme),
250
+ type: {
251
+ standard: {},
252
+ pills: {
253
+ borderRadius: 9999,
254
+ marginRight: 0,
255
+ backgroundColor: 'transparent',
256
+ },
257
+ underline: {},
258
+ },
259
+ active: {
260
+ true: {
261
+ color: theme.colors.text.primary,
262
+ },
263
+ false: {},
264
+ },
265
+ disabled: {
266
+ true: {
267
+ opacity: 0.5,
268
+ _web: {
269
+ cursor: 'not-allowed',
270
+ },
271
+ },
272
+ false: {
273
+ _web: {
274
+ _hover: {
275
+ color: theme.colors.text.primary,
276
+ },
277
+ },
278
+ },
279
+ },
280
+ pillMode: {
281
+ light: {},
282
+ dark: {},
283
+ },
284
+ } as const,
285
+ compoundVariants: createTabCompoundVariants(theme),
286
+ _web: {
287
+ border: 'none',
288
+ cursor: 'pointer',
289
+ outline: 'none',
290
+ transition: 'color 0.2s ease',
291
+ },
292
+ } as const;
293
+ }
294
+
295
+ const createTabLabelStyles = (theme: Theme) => {
296
+ return {
297
+ position: 'relative',
298
+ zIndex: 3,
299
+ fontWeight: '500',
300
+ color: theme.colors.text.secondary,
301
+ variants: {
302
+ size: createLabelSizeVariants(theme),
303
+ type: {
304
+ standard: {},
305
+ pills: {},
306
+ underline: {},
307
+ },
308
+ active: {
309
+ true: {
310
+ color: theme.colors.text.primary,
311
+ },
312
+ false: {},
313
+ },
314
+ disabled: {
315
+ true: {
316
+ opacity: 0.5,
317
+ },
318
+ false: {},
319
+ },
320
+ pillMode: {
321
+ light: {},
322
+ dark: {},
323
+ },
324
+ },
325
+ compoundVariants: createLabelCompoundVariants(theme),
326
+ } as const;
327
+ }
328
+
329
+ const createIndicatorStyles = (theme: Theme) => {
330
+ return {
331
+ position: 'absolute',
332
+ pointerEvents: 'none',
333
+ zIndex: 1,
334
+ variants: {
335
+ type: {
336
+ standard: {
337
+ bottom: -1,
338
+ height: 2,
339
+ backgroundColor: theme.intents.primary.primary,
340
+ },
341
+ pills: {
342
+ borderRadius: 9999,
343
+ bottom: 4,
344
+ top: 4,
345
+ left: 0,
346
+ },
347
+ underline: {
348
+ bottom: -1,
349
+ height: 2,
350
+ backgroundColor: theme.intents.primary.primary,
351
+ },
352
+ },
353
+ pillMode: {
354
+ light: {},
355
+ dark: {},
356
+ },
357
+ },
358
+ compoundVariants: createIndicatorCompoundVariants(theme),
359
+ _web: {
360
+ transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
361
+ },
362
+ } as const;
363
+ }
364
+
365
+ // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel transform on native cannot resolve function calls to extract variant structures.
366
+ // @ts-ignore - TS language server needs restart to pick up theme structure changes
367
+ export const tabBarStyles = StyleSheet.create((theme: Theme) => {
368
+ return {
369
+ container: createContainerStyles(theme),
370
+ tab: createTabStyles(theme),
371
+ tabLabel: createTabLabelStyles(theme),
372
+ indicator: createIndicatorStyles(theme),
373
+ };
374
+ });
375
+
376
+ // Export individual style sheets for backwards compatibility
377
+ export const tabBarContainerStyles = StyleSheet.create((theme: Theme) => {
378
+ return {
379
+ container: createContainerStyles(theme),
380
+ } as const;
381
+ });
382
+
383
+ export const tabBarTabStyles = StyleSheet.create((theme: Theme) => {
384
+ return {
385
+ tab: createTabStyles(theme),
386
+ } as const;
387
+ });
388
+
389
+ export const tabBarLabelStyles = StyleSheet.create((theme: Theme) => {
390
+ return {
391
+ tabLabel: createTabLabelStyles(theme),
392
+ } as const;
393
+ });
394
+
395
+ export const tabBarIndicatorStyles = StyleSheet.create((theme: Theme) => {
396
+ return {
397
+ indicator: createIndicatorStyles(theme),
398
+ } as const;
399
+ });
@@ -0,0 +1,205 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { getWebProps } from 'react-native-unistyles/web';
3
+ import {
4
+ tabBarContainerStyles,
5
+ tabBarTabStyles,
6
+ tabBarLabelStyles,
7
+ tabBarIndicatorStyles
8
+ } from './TabBar.styles';
9
+ import type { TabBarProps, TabBarItem } from './types';
10
+ import useMergeRefs from '../hooks/useMergeRefs';
11
+
12
+ interface TabProps {
13
+ item: TabBarItem;
14
+ isActive: boolean;
15
+ onClick: () => void;
16
+ size: TabBarProps['size'];
17
+ type: TabBarProps['type'];
18
+ pillMode: TabBarProps['pillMode'];
19
+ testID?: string;
20
+ tabRef: (el: HTMLButtonElement | null) => void;
21
+ }
22
+
23
+ const Tab: React.FC<TabProps> = ({
24
+ item,
25
+ isActive,
26
+ onClick,
27
+ size,
28
+ type,
29
+ pillMode,
30
+ testID,
31
+ tabRef,
32
+ }) => {
33
+ // Apply tab and label types for this specific tab
34
+ tabBarTabStyles.useVariants({
35
+ size,
36
+ type,
37
+ active: isActive,
38
+ disabled: Boolean(item.disabled),
39
+ pillMode,
40
+ });
41
+ tabBarLabelStyles.useVariants({
42
+ size,
43
+ type,
44
+ pillMode,
45
+ active: isActive,
46
+ disabled: Boolean(item.disabled),
47
+ });
48
+
49
+ const tabProps = getWebProps([tabBarTabStyles.tab]);
50
+ const labelProps = getWebProps([tabBarLabelStyles.tabLabel]);
51
+
52
+ // Merge refs from getWebProps with our tracking ref
53
+ const mergedRef = useMergeRefs<HTMLButtonElement>(
54
+ tabProps.ref as React.Ref<HTMLButtonElement>,
55
+ tabRef
56
+ );
57
+
58
+ return (
59
+ <button
60
+ {...tabProps}
61
+ ref={mergedRef}
62
+ onClick={onClick}
63
+ disabled={item.disabled}
64
+ role="tab"
65
+ aria-selected={isActive}
66
+ aria-disabled={item.disabled}
67
+ data-testid={`${testID}-tab-${item.value}`}
68
+ >
69
+ <span {...labelProps}>{item.label}</span>
70
+ </button>
71
+ );
72
+ };
73
+
74
+ const TabBar: React.FC<TabBarProps> = ({
75
+ items,
76
+ value: controlledValue,
77
+ defaultValue,
78
+ onChange,
79
+ type = 'standard',
80
+ size = 'md',
81
+ pillMode = 'light',
82
+ style,
83
+ testID,
84
+ }) => {
85
+ const firstItemValue = items[0]?.value || '';
86
+ const [internalValue, setInternalValue] = useState(defaultValue || firstItemValue);
87
+ const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 });
88
+
89
+ const containerRef = useRef<HTMLDivElement>(null);
90
+ const tabRefs = useRef<{ [key: string]: HTMLButtonElement | null }>({});
91
+
92
+ const value = controlledValue !== undefined ? controlledValue : internalValue;
93
+
94
+ const updateIndicator = () => {
95
+ const activeButton = tabRefs.current[value];
96
+ const container = containerRef.current;
97
+
98
+ if (activeButton && container) {
99
+ const containerRect = container.getBoundingClientRect();
100
+ const buttonRect = activeButton.getBoundingClientRect();
101
+
102
+ const newStyle = {
103
+ left: buttonRect.left - containerRect.left,
104
+ width: buttonRect.width,
105
+ };
106
+ setIndicatorStyle(newStyle);
107
+ }
108
+ };
109
+
110
+ // Update indicator when value changes
111
+ useEffect(() => {
112
+ updateIndicator();
113
+ }, [value]);
114
+
115
+ // Update indicator on mount and window resize
116
+ useEffect(() => {
117
+ // Small delay to ensure DOM is ready
118
+ const timer = setTimeout(updateIndicator, 0);
119
+ window.addEventListener('resize', updateIndicator);
120
+ return () => {
121
+ clearTimeout(timer);
122
+ window.removeEventListener('resize', updateIndicator);
123
+ };
124
+ }, []);
125
+
126
+ // Update indicator when items change
127
+ useEffect(() => {
128
+ updateIndicator();
129
+ }, [items]);
130
+
131
+ const handleTabClick = (itemValue: string, disabled?: boolean) => {
132
+ if (disabled) return;
133
+
134
+ if (controlledValue === undefined) {
135
+ setInternalValue(itemValue);
136
+ }
137
+
138
+ onChange?.(itemValue);
139
+ };
140
+
141
+ // Apply container and indicator types
142
+ tabBarContainerStyles.useVariants({ type, size, pillMode });
143
+ const containerProps = getWebProps([tabBarContainerStyles.container, style as any]);
144
+
145
+ tabBarIndicatorStyles.useVariants({ type, pillMode });
146
+ const indicatorProps = getWebProps([tabBarIndicatorStyles.indicator]);
147
+
148
+ // Merge container ref with getWebProps ref
149
+ const mergedContainerRef = useMergeRefs<HTMLDivElement>(
150
+ containerProps.ref as React.Ref<HTMLDivElement>,
151
+ containerRef
152
+ );
153
+
154
+ // For pills type, calculate height from parent
155
+ const indicatorInlineStyle: React.CSSProperties = {
156
+ left: `${indicatorStyle.left}px`,
157
+ width: `${indicatorStyle.width}px`,
158
+ };
159
+
160
+ // For pills type, use calc() to set height based on top/bottom
161
+ if (type === 'pills') {
162
+ indicatorInlineStyle.height = 'calc(100% - 8px)'; // 100% minus top(4px) + bottom(4px)
163
+ }
164
+
165
+ return (
166
+ <div
167
+ {...containerProps}
168
+ ref={mergedContainerRef}
169
+ role="tablist"
170
+ data-testid={testID}
171
+ >
172
+ {/* Sliding indicator */}
173
+ <div
174
+ {...indicatorProps}
175
+ style={indicatorInlineStyle}
176
+ />
177
+
178
+ {items.map((item) => {
179
+ const isActive = value === item.value;
180
+
181
+ return (
182
+ <Tab
183
+ key={item.value}
184
+ item={item}
185
+ isActive={isActive}
186
+ onClick={() => handleTabClick(item.value, item.disabled)}
187
+ size={size}
188
+ type={type}
189
+ pillMode={pillMode}
190
+ testID={testID}
191
+ tabRef={(el) => {
192
+ tabRefs.current[item.value] = el;
193
+ // Update indicator when active tab ref is set
194
+ if (el && isActive) {
195
+ requestAnimationFrame(() => updateIndicator());
196
+ }
197
+ }}
198
+ />
199
+ );
200
+ })}
201
+ </div>
202
+ );
203
+ };
204
+
205
+ export default TabBar;
@@ -0,0 +1,3 @@
1
+ export { default } from './TabBar.native';
2
+ export { default as TabBar } from './TabBar.native';
3
+ export * from './types';
@@ -0,0 +1,3 @@
1
+ export { default } from './TabBar.web';
2
+ export { default as TabBar } from './TabBar.web';
3
+ export * from './types';
@@ -0,0 +1,3 @@
1
+ export { default } from './TabBar.web';
2
+ export { default as TabBar } from './TabBar.web';
3
+ export * from './types';
@@ -0,0 +1,26 @@
1
+ import { Size } from '@idealyst/theme';
2
+ import type { StyleProp, ViewStyle } from 'react-native';
3
+
4
+ // Component-specific type aliases for future extensibility
5
+ export type TabBarSizeVariant = Size;
6
+ export type TabBarType = 'standard' | 'pills' | 'underline';
7
+ export type TabBarPillMode = 'light' | 'dark';
8
+
9
+ export interface TabBarItem {
10
+ value: string;
11
+ label: string;
12
+ disabled?: boolean;
13
+ }
14
+
15
+ export interface TabBarProps {
16
+ items: TabBarItem[];
17
+ value?: string;
18
+ defaultValue?: string;
19
+ onChange?: (value: string) => void;
20
+ type?: TabBarType;
21
+ size?: TabBarSizeVariant;
22
+ /** Mode for pills variant: 'light' for light backgrounds (dark pill), 'dark' for dark backgrounds (light pill) */
23
+ pillMode?: TabBarPillMode;
24
+ style?: StyleProp<ViewStyle>;
25
+ testID?: string;
26
+ }