@idealyst/components 1.0.82 → 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 +25 -7
  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 +347 -0
  170. package/src/Select/Select.styles.tsx +335 -0
  171. package/src/Select/Select.web.tsx +276 -0
  172. package/src/Select/index.native.ts +2 -0
  173. package/src/Select/index.ts +5 -0
  174. package/src/Select/index.web.ts +5 -0
  175. package/src/Select/types.ts +124 -0
  176. package/src/Skeleton/Skeleton.native.tsx +139 -0
  177. package/src/Skeleton/Skeleton.styles.tsx +59 -0
  178. package/src/Skeleton/Skeleton.web.tsx +112 -0
  179. package/src/Skeleton/index.native.ts +4 -0
  180. package/src/Skeleton/index.ts +5 -0
  181. package/src/Skeleton/index.web.ts +5 -0
  182. package/src/Skeleton/types.ts +75 -0
  183. package/src/Slider/Slider.native.tsx +248 -0
  184. package/src/Slider/Slider.styles.tsx +241 -0
  185. package/src/Slider/Slider.web.tsx +226 -0
  186. package/src/Slider/index.native.ts +3 -0
  187. package/src/Slider/index.ts +5 -0
  188. package/src/Slider/index.web.ts +5 -0
  189. package/src/Slider/types.ts +31 -0
  190. package/src/Switch/Switch.native.tsx +131 -0
  191. package/src/Switch/Switch.styles.tsx +169 -0
  192. package/src/Switch/Switch.web.tsx +121 -0
  193. package/src/Switch/index.native.ts +3 -0
  194. package/src/Switch/index.ts +5 -0
  195. package/src/Switch/index.web.ts +5 -0
  196. package/src/Switch/types.ts +21 -0
  197. package/src/TabBar/TabBar.native.tsx +142 -0
  198. package/src/TabBar/TabBar.styles.tsx +399 -0
  199. package/src/TabBar/TabBar.web.tsx +205 -0
  200. package/src/TabBar/index.native.tsx +3 -0
  201. package/src/TabBar/index.ts +3 -0
  202. package/src/TabBar/index.web.tsx +3 -0
  203. package/src/TabBar/types.ts +26 -0
  204. package/src/Table/Table.native.tsx +122 -0
  205. package/src/Table/Table.styles.tsx +283 -0
  206. package/src/Table/Table.web.tsx +112 -0
  207. package/src/Table/index.native.tsx +3 -0
  208. package/src/Table/index.ts +3 -0
  209. package/src/Table/index.web.tsx +3 -0
  210. package/src/Table/types.ts +28 -0
  211. package/src/Text/Text.native.tsx +12 -11
  212. package/src/Text/Text.styles.tsx +76 -64
  213. package/src/Text/Text.web.tsx +14 -9
  214. package/src/Text/index.ts +5 -5
  215. package/src/Text/index.web.ts +5 -3
  216. package/src/Text/types.ts +20 -13
  217. package/src/TextArea/TextArea.native.tsx +134 -0
  218. package/src/TextArea/TextArea.styles.tsx +175 -0
  219. package/src/TextArea/TextArea.web.tsx +156 -0
  220. package/src/TextArea/index.native.ts +3 -0
  221. package/src/TextArea/index.ts +3 -0
  222. package/src/TextArea/index.web.ts +3 -0
  223. package/src/TextArea/types.ts +30 -0
  224. package/src/Tooltip/Tooltip.native.tsx +165 -0
  225. package/src/Tooltip/Tooltip.styles.tsx +73 -0
  226. package/src/Tooltip/Tooltip.web.tsx +87 -0
  227. package/src/Tooltip/index.native.ts +3 -0
  228. package/src/Tooltip/index.ts +3 -0
  229. package/src/Tooltip/types.ts +18 -0
  230. package/src/Video/Video.native.tsx +105 -0
  231. package/src/Video/Video.styles.tsx +39 -0
  232. package/src/Video/Video.web.tsx +115 -0
  233. package/src/Video/index.native.ts +5 -0
  234. package/src/Video/index.ts +5 -0
  235. package/src/Video/types.ts +29 -0
  236. package/src/View/View.native.tsx +9 -14
  237. package/src/View/View.styles.tsx +101 -93
  238. package/src/View/View.web.tsx +16 -17
  239. package/src/View/index.ts +5 -5
  240. package/src/View/index.web.ts +5 -3
  241. package/src/View/types.ts +29 -21
  242. package/src/examples/AccordionExamples.tsx +126 -0
  243. package/src/examples/AlertExamples.tsx +280 -0
  244. package/src/examples/AvatarExamples.tsx +23 -23
  245. package/src/examples/BadgeExamples.tsx +109 -41
  246. package/src/examples/BreadcrumbExamples.tsx +312 -0
  247. package/src/examples/ButtonExamples.tsx +160 -33
  248. package/src/examples/CardExamples.tsx +40 -40
  249. package/src/examples/CheckboxExamples.tsx +12 -12
  250. package/src/examples/ChipExamples.tsx +197 -0
  251. package/src/examples/DialogExamples.tsx +22 -22
  252. package/src/examples/DividerExamples.tsx +49 -49
  253. package/src/examples/IconExamples.tsx +270 -54
  254. package/src/examples/ImageExamples.tsx +174 -0
  255. package/src/examples/InputExamples.tsx +75 -17
  256. package/src/examples/ListExamples.tsx +288 -0
  257. package/src/examples/MenuExamples.tsx +144 -0
  258. package/src/examples/PopoverExamples.tsx +69 -73
  259. package/src/examples/ProgressExamples.tsx +137 -0
  260. package/src/examples/RadioButtonExamples.tsx +161 -0
  261. package/src/examples/SVGImageExamples.tsx +19 -17
  262. package/src/examples/ScreenExamples.tsx +31 -31
  263. package/src/examples/SelectExamples.tsx +423 -0
  264. package/src/examples/SkeletonExamples.tsx +206 -0
  265. package/src/examples/SliderExamples.tsx +200 -0
  266. package/src/examples/SwitchExamples.tsx +182 -0
  267. package/src/examples/TabBarExamples.tsx +143 -0
  268. package/src/examples/TableExamples.tsx +280 -0
  269. package/src/examples/TextAreaExamples.tsx +173 -0
  270. package/src/examples/TextExamples.tsx +28 -32
  271. package/src/examples/ThemeExtensionExamples.tsx +10 -10
  272. package/src/examples/TooltipExamples.tsx +126 -0
  273. package/src/examples/VideoExamples.tsx +144 -0
  274. package/src/examples/ViewExamples.tsx +64 -56
  275. package/src/examples/index.ts +18 -3
  276. package/src/hooks/useMergeRefs.ts +16 -0
  277. package/src/hooks/useSmartPosition.native.ts +169 -0
  278. package/src/index.native.ts +80 -9
  279. package/src/index.ts +75 -1
  280. package/src/internal/BoundedModalContent.native.tsx +58 -0
  281. package/src/internal/PositionedPortal.tsx +254 -0
  282. package/src/internal/SafeAreaDebugOverlay.native.tsx +173 -0
  283. package/src/unistyles.d.ts +6 -0
  284. package/src/utils/buildSizeVariants.ts +16 -0
  285. package/src/utils/deepMerge.ts +43 -0
  286. package/src/utils/positionUtils.native.ts +280 -0
  287. package/src/utils/styleHelpers.ts +48 -0
  288. package/LLM-ACCESS-GUIDE.md +0 -143
  289. package/src/ActivityIndicator/README.md +0 -132
  290. package/src/Avatar/README.md +0 -139
  291. package/src/Badge/README.md +0 -170
  292. package/src/Button/Button.types.ts +0 -12
  293. package/src/Button/README.md +0 -262
  294. package/src/Card/README.md +0 -258
  295. package/src/Checkbox/README.md +0 -102
  296. package/src/Dialog/README.md +0 -210
  297. package/src/Divider/README.md +0 -108
  298. package/src/Icon/README.md +0 -81
  299. package/src/Input/README.md +0 -100
  300. package/src/SVGImage/README.md +0 -209
  301. package/src/Screen/README.md +0 -86
  302. package/src/Text/README.md +0 -94
  303. package/src/View/README.md +0 -107
  304. package/src/examples/AllExamples.tsx +0 -84
  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,3 @@
1
+ // React Native-specific Slider export
2
+ export { default } from './Slider.native';
3
+ export * from './types';
@@ -0,0 +1,5 @@
1
+ import SliderComponent from './Slider.web';
2
+
3
+ export default SliderComponent;
4
+ export { SliderComponent as Slider };
5
+ export * from './types';
@@ -0,0 +1,5 @@
1
+ import SliderComponent from './Slider.web';
2
+
3
+ export default SliderComponent;
4
+ export { SliderComponent as Slider };
5
+ export * from './types';
@@ -0,0 +1,31 @@
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 SliderIntentVariant = Intent;
7
+ export type SliderSizeVariant = Size;
8
+
9
+ export interface SliderProps {
10
+ value?: number;
11
+ defaultValue?: number;
12
+ min?: number;
13
+ max?: number;
14
+ step?: number;
15
+ disabled?: boolean;
16
+ showValue?: boolean;
17
+ showMinMax?: boolean;
18
+ marks?: SliderMark[];
19
+ intent?: SliderIntentVariant;
20
+ size?: SliderSizeVariant;
21
+ icon?: IconName | React.ReactNode;
22
+ onValueChange?: (value: number) => void;
23
+ onValueCommit?: (value: number) => void;
24
+ style?: StyleProp<ViewStyle>;
25
+ testID?: string;
26
+ }
27
+
28
+ export interface SliderMark {
29
+ value: number;
30
+ label?: string;
31
+ }
@@ -0,0 +1,131 @@
1
+ import React, { ComponentRef, forwardRef } from 'react';
2
+ import { Pressable } from 'react-native';
3
+ import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
4
+ import { switchStyles } from './Switch.styles';
5
+ import Text from '../Text';
6
+ import type { SwitchProps } from './types';
7
+
8
+ const Switch = forwardRef<ComponentRef<typeof Pressable>, SwitchProps>(({
9
+ checked = false,
10
+ onCheckedChange,
11
+ disabled = false,
12
+ label,
13
+ labelPosition = 'right',
14
+ intent = 'primary',
15
+ size = 'md',
16
+ style,
17
+ testID,
18
+ }, ref) => {
19
+ switchStyles.useVariants({
20
+ size,
21
+ disabled,
22
+ position: labelPosition,
23
+ });
24
+
25
+ const progress = useSharedValue(checked ? 1 : 0);
26
+
27
+ React.useEffect(() => {
28
+ progress.value = withSpring(checked ? 1 : 0, {
29
+ damping: 15,
30
+ stiffness: 150,
31
+ });
32
+ }, [checked, progress]);
33
+
34
+ const handlePress = () => {
35
+ if (!disabled && onCheckedChange) {
36
+ onCheckedChange(!checked);
37
+ }
38
+ };
39
+
40
+ const getThumbDistance = () => {
41
+ if (size === 'sm') return 16;
42
+ if (size === 'lg') return 24;
43
+ return 20;
44
+ };
45
+
46
+ // Native-specific thumb styles
47
+ const getThumbSize = () => {
48
+ if (size === 'sm') return 16;
49
+ if (size === 'lg') return 24;
50
+ return 20;
51
+ };
52
+
53
+ const getTrackHeight = () => {
54
+ if (size === 'sm') return 20;
55
+ if (size === 'lg') return 28;
56
+ return 24;
57
+ };
58
+
59
+ const thumbSize = getThumbSize();
60
+ const thumbDistance = getThumbDistance();
61
+ const trackHeight = getTrackHeight();
62
+ const thumbTop = (trackHeight - thumbSize) / 2;
63
+
64
+ const thumbAnimatedStyle = useAnimatedStyle(() => {
65
+ return {
66
+ transform: [
67
+ {
68
+ translateX: progress.value * thumbDistance + 2,
69
+ },
70
+ ],
71
+ };
72
+ });
73
+
74
+ const switchElement = (
75
+ <Pressable
76
+ ref={!label ? ref : undefined}
77
+ onPress={handlePress}
78
+ disabled={disabled}
79
+ style={switchStyles.switchContainer}
80
+ testID={testID}
81
+ accessibilityRole="switch"
82
+ accessibilityState={{ checked, disabled }}
83
+ >
84
+ <Animated.View style={switchStyles.switchTrack({ checked, intent })}>
85
+ <Animated.View
86
+ style={[
87
+ {
88
+ position: 'absolute',
89
+ top: thumbTop,
90
+ width: thumbSize,
91
+ height: thumbSize,
92
+ backgroundColor: 'white',
93
+ borderRadius: thumbSize / 2,
94
+ shadowColor: '#000',
95
+ shadowOffset: { width: 0, height: 1 },
96
+ shadowOpacity: 0.2,
97
+ shadowRadius: 3,
98
+ elevation: 2,
99
+ },
100
+ thumbAnimatedStyle,
101
+ ]}
102
+ />
103
+ </Animated.View>
104
+ </Pressable>
105
+ );
106
+
107
+ if (label) {
108
+ return (
109
+ <Pressable
110
+ ref={ref}
111
+ onPress={handlePress}
112
+ disabled={disabled}
113
+ style={[switchStyles.container, style]}
114
+ >
115
+ {labelPosition === 'left' && (
116
+ <Text style={switchStyles.label}>{label}</Text>
117
+ )}
118
+ {switchElement}
119
+ {labelPosition === 'right' && (
120
+ <Text style={switchStyles.label}>{label}</Text>
121
+ )}
122
+ </Pressable>
123
+ );
124
+ }
125
+
126
+ return switchElement;
127
+ });
128
+
129
+ Switch.displayName = 'Switch';
130
+
131
+ export default Switch;
@@ -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;