@idealyst/components 1.0.83 → 1.0.85

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,169 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+ import { Theme, StylesheetStyles, Intent, Size} from '@idealyst/theme';
3
+ import { buildSizeVariants } from '../utils/buildSizeVariants';
4
+ import { SwitchIntentVariant, SwitchSizeVariant } from './types';
5
+
6
+ function createTrackSizeVariants(theme: Theme) {
7
+ return buildSizeVariants(theme, 'switch', (size) => ({
8
+ width: size.trackWidth,
9
+ height: size.trackHeight,
10
+ }));
11
+ }
12
+
13
+ function getTrackBackgroundColor(theme: Theme, checked: boolean, intent: SwitchIntentVariant) {
14
+ if (checked) {
15
+ return theme.intents[intent].primary;
16
+ }
17
+ return theme.colors.border.secondary;
18
+ }
19
+
20
+ function createThumbSizeVariants(theme: Theme) {
21
+ return buildSizeVariants(theme, 'switch', (size) => ({
22
+ width: size.thumbSize,
23
+ height: size.thumbSize,
24
+ left: 2,
25
+ }));
26
+ }
27
+
28
+ function getThumbTransform(theme: Theme, size: SwitchSizeVariant, checked: boolean) {
29
+ const sizeValue = theme.sizes.switch[size];
30
+ const translateX = checked ? sizeValue.translateX : 0;
31
+ return `translateY(-50%) translateX(${translateX}px)`;
32
+ }
33
+
34
+ function createThumbIconSizeVariants(theme: Theme) {
35
+ return buildSizeVariants(theme, 'switch', (size) => ({
36
+ width: size.thumbIconSize,
37
+ height: size.thumbIconSize,
38
+ }));
39
+ }
40
+
41
+ function getThumbIconColor(theme: Theme, checked: boolean, intent: SwitchIntentVariant) {
42
+ if (checked) {
43
+ return theme.intents[intent].primary;
44
+ }
45
+ return theme.colors.border.secondary;
46
+ }
47
+
48
+ function createSwitchTrackStyles(theme: Theme) {
49
+ return ({ checked, intent }: { checked: boolean, intent: SwitchIntentVariant }) => {
50
+ return {
51
+ borderRadius: 9999,
52
+ position: 'relative',
53
+ backgroundColor: getTrackBackgroundColor(theme, checked, intent),
54
+ variants: {
55
+ size: createTrackSizeVariants(theme),
56
+ disabled: {
57
+ true: {
58
+ opacity: 0.5,
59
+ _web: {
60
+ cursor: 'not-allowed',
61
+ },
62
+ },
63
+ false: {
64
+ opacity: 1,
65
+ _web: {
66
+ cursor: 'pointer',
67
+ _hover: {
68
+ opacity: 0.9,
69
+ },
70
+ _active: {
71
+ opacity: 0.8,
72
+ },
73
+ },
74
+ },
75
+ },
76
+ } as const,
77
+ _web: {
78
+ transition: 'background-color 0.2s ease',
79
+ },
80
+ } as const;
81
+ }
82
+ }
83
+
84
+ function createSwitchThumbStyles(theme: Theme) {
85
+ return ({ size, checked }: { size: SwitchSizeVariant, checked: boolean }) => {
86
+ return {
87
+ position: 'absolute',
88
+ backgroundColor: theme.colors.surface.primary,
89
+ borderRadius: 9999,
90
+ top: '50%',
91
+ display: 'flex',
92
+ alignItems: 'center',
93
+ justifyContent: 'center',
94
+ shadowColor: '#000',
95
+ shadowOffset: { width: 0, height: 1 },
96
+ shadowOpacity: 0.2,
97
+ shadowRadius: 3,
98
+ elevation: 2,
99
+ variants: {
100
+ size: createThumbSizeVariants(theme),
101
+ },
102
+ _web: {
103
+ boxShadow: '0 1px 3px rgba(0, 0, 0, 0.2)',
104
+ transition: 'transform 0.2s ease',
105
+ transform: getThumbTransform(theme, size, checked),
106
+ },
107
+ } as const;
108
+ }
109
+ }
110
+
111
+ function createThumbIconStyles(theme: Theme) {
112
+ return ({ checked, intent }: { checked: boolean, intent: SwitchIntentVariant }) => {
113
+ return {
114
+ display: 'flex',
115
+ alignItems: 'center',
116
+ justifyContent: 'center',
117
+ color: getThumbIconColor(theme, checked, intent),
118
+ variants: {
119
+ size: createThumbIconSizeVariants(theme),
120
+ },
121
+ } as const;
122
+ }
123
+ }
124
+
125
+ // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel
126
+ // transform on native cannot resolve function calls to extract variant structures.
127
+ export const switchStyles = StyleSheet.create((theme: Theme) => {
128
+ return {
129
+ container: {
130
+ flexDirection: 'row',
131
+ alignItems: 'center',
132
+ gap: 8,
133
+ },
134
+ switchContainer: {
135
+ justifyContent: 'center',
136
+ _web: {
137
+ border: 'none',
138
+ padding: 0,
139
+ backgroundColor: 'transparent',
140
+ width: 'fit-content',
141
+ }
142
+ },
143
+ switchTrack: createSwitchTrackStyles(theme),
144
+ switchThumb: createSwitchThumbStyles(theme),
145
+ thumbIcon: createThumbIconStyles(theme),
146
+ label: {
147
+ fontSize: 14,
148
+ color: theme.colors.text.primary,
149
+ variants: {
150
+ disabled: {
151
+ true: {
152
+ opacity: 0.5,
153
+ },
154
+ false: {
155
+ opacity: 1,
156
+ },
157
+ },
158
+ position: {
159
+ left: {
160
+ marginRight: 8,
161
+ },
162
+ right: {
163
+ marginLeft: 8,
164
+ },
165
+ },
166
+ } as const,
167
+ } as const,
168
+ };
169
+ });
@@ -0,0 +1,121 @@
1
+ import React, { isValidElement, forwardRef } from 'react';
2
+ import { getWebProps } from 'react-native-unistyles/web';
3
+ import { switchStyles } from './Switch.styles';
4
+ import type { SwitchProps } from './types';
5
+ import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
6
+ import { resolveIconPath, isIconName } from '../Icon/icon-resolver';
7
+ import useMergeRefs from '../hooks/useMergeRefs';
8
+
9
+ const Switch = forwardRef<HTMLDivElement | HTMLButtonElement, SwitchProps>(({
10
+ checked = false,
11
+ onCheckedChange,
12
+ disabled = false,
13
+ label,
14
+ labelPosition = 'right',
15
+ intent = 'primary',
16
+ size = 'md',
17
+ enabledIcon,
18
+ disabledIcon,
19
+ style,
20
+ testID,
21
+ }, ref) => {
22
+ const handleClick = () => {
23
+ if (!disabled && onCheckedChange) {
24
+ onCheckedChange(!checked);
25
+ }
26
+ };
27
+
28
+ // Apply variants using the correct Unistyles v3 pattern
29
+ switchStyles.useVariants({
30
+ size: size as 'sm' | 'md' | 'lg',
31
+ disabled: disabled as boolean,
32
+ position: labelPosition as 'left' | 'right',
33
+ });
34
+
35
+ const trackProps = getWebProps([switchStyles.switchTrack({ checked, intent })]);
36
+ const thumbProps = getWebProps([switchStyles.switchThumb({ size, checked })]);
37
+ const thumbIconProps = getWebProps([switchStyles.thumbIcon({ checked, intent })]);
38
+ const labelProps = getWebProps([switchStyles.label]);
39
+
40
+ // Helper to render icon
41
+ const renderIcon = () => {
42
+ const iconToRender = checked ? enabledIcon : disabledIcon;
43
+ if (!iconToRender) return null;
44
+
45
+ if (isIconName(iconToRender)) {
46
+ const iconPath = resolveIconPath(iconToRender);
47
+ return (
48
+ <IconSvg
49
+ path={iconPath}
50
+ {...thumbIconProps}
51
+ aria-label={iconToRender}
52
+ />
53
+ );
54
+ } else if (isValidElement(iconToRender)) {
55
+ return iconToRender;
56
+ }
57
+
58
+ return null;
59
+ };
60
+
61
+ // Computed button props with dynamic styles
62
+ const computedButtonProps = getWebProps(
63
+ switchStyles.switchContainer
64
+ );
65
+
66
+ // Computed container props with dynamic styles (for when label exists)
67
+ const computedContainerProps = getWebProps([
68
+ switchStyles.container,
69
+ style as any,
70
+ {
71
+ cursor: disabled ? 'not-allowed' : 'pointer',
72
+ display: 'inline-flex',
73
+ alignItems: 'center',
74
+ }
75
+ ]);
76
+
77
+ const mergedButtonRef = useMergeRefs(ref as React.Ref<HTMLButtonElement>, computedButtonProps.ref);
78
+ const mergedContainerRef = useMergeRefs(ref as React.Ref<HTMLDivElement>, computedContainerProps.ref);
79
+
80
+ const switchElement = (
81
+ <button
82
+ {...computedButtonProps}
83
+ ref={mergedButtonRef}
84
+ onClick={handleClick}
85
+ disabled={disabled}
86
+ data-testid={testID}
87
+ role="switch"
88
+ aria-checked={checked}
89
+ aria-disabled={disabled}
90
+ >
91
+ <div {...trackProps}>
92
+ <div {...thumbProps}>
93
+ {renderIcon()}
94
+ </div>
95
+ </div>
96
+ </button>
97
+ );
98
+
99
+ if (label) {
100
+ return (
101
+ <div
102
+ {...computedContainerProps}
103
+ ref={mergedContainerRef}
104
+ >
105
+ {labelPosition === 'left' && (
106
+ <span {...labelProps}>{label}</span>
107
+ )}
108
+ {switchElement}
109
+ {labelPosition === 'right' && (
110
+ <span {...labelProps}>{label}</span>
111
+ )}
112
+ </div>
113
+ );
114
+ }
115
+
116
+ return switchElement;
117
+ });
118
+
119
+ Switch.displayName = 'Switch';
120
+
121
+ export default Switch;
@@ -0,0 +1,3 @@
1
+ // React Native-specific Switch export
2
+ export { default } from './Switch.native';
3
+ export * from './types';
@@ -0,0 +1,5 @@
1
+ import SwitchComponent from './Switch.web';
2
+
3
+ export default SwitchComponent;
4
+ export { SwitchComponent as Switch };
5
+ export * from './types';
@@ -0,0 +1,5 @@
1
+ import SwitchComponent from './Switch.web';
2
+
3
+ export default SwitchComponent;
4
+ export { SwitchComponent as Switch };
5
+ export * from './types';
@@ -0,0 +1,21 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+ import type { IconName } from '../Icon/icon-types';
3
+ import { Intent, Size } from '@idealyst/theme';
4
+
5
+ // Component-specific type aliases for future extensibility
6
+ export type SwitchIntentVariant = Intent;
7
+ export type SwitchSizeVariant = Size;
8
+
9
+ export interface SwitchProps {
10
+ checked?: boolean;
11
+ onCheckedChange?: (checked: boolean) => void;
12
+ disabled?: boolean;
13
+ label?: string;
14
+ labelPosition?: 'left' | 'right';
15
+ intent?: SwitchIntentVariant;
16
+ size?: SwitchSizeVariant;
17
+ enabledIcon?: IconName | React.ReactNode;
18
+ disabledIcon?: IconName | React.ReactNode;
19
+ style?: StyleProp<ViewStyle>;
20
+ testID?: string;
21
+ }
@@ -0,0 +1,142 @@
1
+ import React, { useState, useRef, useEffect, forwardRef } from 'react';
2
+ import { View, TouchableOpacity, Text, ScrollView } from 'react-native';
3
+ import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
4
+ import {
5
+ tabBarContainerStyles,
6
+ tabBarTabStyles,
7
+ tabBarLabelStyles,
8
+ tabBarIndicatorStyles
9
+ } from './TabBar.styles';
10
+ import type { TabBarProps } from './types';
11
+
12
+ const TabBar = forwardRef<View, TabBarProps>(({
13
+ items,
14
+ value: controlledValue,
15
+ defaultValue,
16
+ onChange,
17
+ type = 'default',
18
+ size = 'md',
19
+ pillMode = 'light',
20
+ style,
21
+ testID,
22
+ }, ref) => {
23
+ const firstItemValue = items[0]?.value || '';
24
+ const [internalValue, setInternalValue] = useState(defaultValue || firstItemValue);
25
+
26
+ const indicatorPosition = useSharedValue(0);
27
+ const indicatorWidth = useSharedValue(0);
28
+ const tabLayouts = useRef<{ [key: string]: { x: number; width: number } }>({});
29
+
30
+ const value = controlledValue !== undefined ? controlledValue : internalValue;
31
+
32
+ const updateIndicatorPosition = (itemValue: string) => {
33
+ const layout = tabLayouts.current[itemValue];
34
+ if (layout) {
35
+ // For pills type, account for container padding
36
+ const containerPadding = type === 'pills' ? 4 : 0;
37
+
38
+ indicatorPosition.value = withSpring(layout.x + containerPadding, {
39
+ damping: 30,
40
+ stiffness: 300,
41
+ });
42
+ indicatorWidth.value = withSpring(layout.width, {
43
+ damping: 30,
44
+ stiffness: 300,
45
+ });
46
+ }
47
+ };
48
+
49
+ useEffect(() => {
50
+ updateIndicatorPosition(value);
51
+ }, [value]);
52
+
53
+ const handleTabLayout = (itemValue: string, x: number, width: number) => {
54
+ tabLayouts.current[itemValue] = { x, width };
55
+
56
+ // Update indicator for active tab
57
+ if (itemValue === value) {
58
+ updateIndicatorPosition(itemValue);
59
+ }
60
+ };
61
+
62
+ const handleTabClick = (itemValue: string, disabled?: boolean) => {
63
+ if (disabled) return;
64
+
65
+ if (controlledValue === undefined) {
66
+ setInternalValue(itemValue);
67
+ }
68
+
69
+ onChange?.(itemValue);
70
+ };
71
+
72
+ const indicatorAnimatedStyle = useAnimatedStyle(() => {
73
+ return {
74
+ transform: [{ translateX: indicatorPosition.value }],
75
+ width: indicatorWidth.value,
76
+ };
77
+ });
78
+
79
+ // Apply container and indicator types right before rendering
80
+ tabBarContainerStyles.useVariants({ size, pillMode });
81
+ tabBarIndicatorStyles.useVariants({ pillMode });
82
+
83
+ return (
84
+ <ScrollView
85
+ horizontal
86
+ showsHorizontalScrollIndicator={false}
87
+ contentContainerStyle={{ position: 'relative' }}
88
+ >
89
+ <View ref={ref} style={[tabBarContainerStyles.container, style]} testID={testID}>
90
+ {/* Animated indicator - render first so it's behind */}
91
+ <Animated.View
92
+ style={[
93
+ tabBarIndicatorStyles.indicator,
94
+ indicatorAnimatedStyle,
95
+ ]}
96
+ />
97
+
98
+ {/* Tabs - render second so they're on top */}
99
+ <View style={{ flexDirection: 'row' }}>
100
+ {items.map((item) => {
101
+ const isActive = value === item.value;
102
+
103
+ // Apply tab and label types for this specific tab
104
+ tabBarTabStyles.useVariants({
105
+ size,
106
+ active: isActive,
107
+ disabled: Boolean(item.disabled),
108
+ pillMode,
109
+ });
110
+ tabBarLabelStyles.useVariants({
111
+ size,
112
+ pillMode,
113
+ active: isActive,
114
+ disabled: Boolean(item.disabled),
115
+ });
116
+
117
+ return (
118
+ <TouchableOpacity
119
+ key={item.value}
120
+ onLayout={(event) => {
121
+ const { x, width } = event.nativeEvent.layout;
122
+ handleTabLayout(item.value, x, width);
123
+ }}
124
+ style={tabBarTabStyles.tab}
125
+ onPress={() => handleTabClick(item.value, item.disabled)}
126
+ disabled={item.disabled}
127
+ activeOpacity={0.7}
128
+ testID={`${testID}-tab-${item.value}`}
129
+ >
130
+ <Text style={tabBarLabelStyles.tabLabel}>{item.label}</Text>
131
+ </TouchableOpacity>
132
+ );
133
+ })}
134
+ </View>
135
+ </View>
136
+ </ScrollView>
137
+ );
138
+ });
139
+
140
+ TabBar.displayName = 'TabBar';
141
+
142
+ export default TabBar;