@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,241 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+ import { Theme, Size, Styles} from '@idealyst/theme';
3
+ import { buildSizeVariants } from '../utils/buildSizeVariants';
4
+ import { SliderIntentVariant } from './types';
5
+
6
+ /**
7
+ * Create size variants for track
8
+ */
9
+ function createTrackSizeVariants(theme: Theme) {
10
+ const variants = {} as Record<Size, Styles>;
11
+ for (const sizeKey in theme.sizes.slider) {
12
+ const size = sizeKey as Size;
13
+ variants[size] = {
14
+ height: theme.sizes.slider[size].trackHeight,
15
+ };
16
+ }
17
+ return variants;
18
+ }
19
+
20
+ /**
21
+ * Get filled track color based on intent
22
+ */
23
+ function getFilledTrackColor(theme: Theme, intent: SliderIntentVariant) {
24
+ return theme.intents[intent].primary;
25
+ }
26
+
27
+ /**
28
+ * Create size variants for thumb
29
+ */
30
+ function createThumbSizeVariants(theme: Theme) {
31
+ return buildSizeVariants(theme, 'slider', (size) => ({
32
+ width: size.thumbSize,
33
+ height: size.thumbSize,
34
+ }));
35
+ }
36
+
37
+ /**
38
+ * Get thumb border color based on intent
39
+ */
40
+ function getThumbBorderColor(theme: Theme, intent: SliderIntentVariant) {
41
+ return theme.intents[intent].primary;
42
+ }
43
+
44
+ /**
45
+ * Create size variants for thumb icon
46
+ */
47
+ function createThumbIconSizeVariants(theme: Theme) {
48
+ return buildSizeVariants(theme, 'slider', (size) => ({
49
+ width: size.thumbIconSize,
50
+ height: size.thumbIconSize,
51
+ minWidth: size.thumbIconSize,
52
+ maxWidth: size.thumbIconSize,
53
+ minHeight: size.thumbIconSize,
54
+ maxHeight: size.thumbIconSize,
55
+ }));
56
+ }
57
+
58
+ /**
59
+ * Get thumb icon color based on intent
60
+ */
61
+ function getThumbIconColor(theme: Theme, intent: SliderIntentVariant){
62
+ return theme.intents[intent].primary;
63
+ }
64
+
65
+ /**
66
+ * Create size variants for mark
67
+ */
68
+ function createMarkSizeVariants(theme: Theme) {
69
+ return buildSizeVariants(theme, 'slider', (size) => ({
70
+ height: size.markHeight,
71
+ }));
72
+ }
73
+
74
+ const createFilledTrackStyles = (theme: Theme) => {
75
+ return ({ intent }: { intent: SliderIntentVariant }) => {
76
+ return {
77
+ position: 'absolute',
78
+ height: '100%',
79
+ borderRadius: 9999,
80
+ top: 0,
81
+ left: 0,
82
+ backgroundColor: getFilledTrackColor(theme, intent),
83
+ } as const;
84
+ }
85
+ }
86
+
87
+ const createThumbStyles = (theme: Theme) => {
88
+ return ({ intent, disabled }: { intent: SliderIntentVariant, disabled: boolean }) => {
89
+ return {
90
+ position: 'absolute',
91
+ backgroundColor: theme.colors.surface.primary,
92
+ borderRadius: 9999,
93
+ borderWidth: 2,
94
+ borderStyle: 'solid',
95
+ borderColor: getThumbBorderColor(theme, intent),
96
+ top: '50%',
97
+ display: 'flex',
98
+ alignItems: 'center',
99
+ justifyContent: 'center',
100
+ shadowColor: '#000',
101
+ shadowOffset: { width: 0, height: 2 },
102
+ shadowOpacity: 0.2,
103
+ shadowRadius: 4,
104
+ elevation: 2,
105
+ variants: {
106
+ size: createThumbSizeVariants(theme),
107
+ disabled: {
108
+ true: {
109
+ opacity: 0.6,
110
+ _web: {
111
+ cursor: 'not-allowed',
112
+ },
113
+ },
114
+ false: {
115
+ opacity: 1,
116
+ _web: {
117
+ cursor: 'grab',
118
+ _hover: {
119
+ transform: 'translate(-50%, -50%) scale(1.05)',
120
+ },
121
+ _active: {
122
+ cursor: 'grabbing',
123
+ transform: 'translate(-50%, -50%) scale(1.1)',
124
+ },
125
+ },
126
+ },
127
+ },
128
+ },
129
+ _web: {
130
+ boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)',
131
+ transform: 'translate(-50%, -50%)',
132
+ transition: 'transform 0.15s ease, box-shadow 0.2s ease',
133
+ },
134
+ } as const;
135
+ }
136
+ }
137
+
138
+ const createThumbIconStyles = (theme: Theme) => {
139
+ return ({ intent }: { intent: SliderIntentVariant }) => {
140
+ return {
141
+ display: 'flex',
142
+ alignItems: 'center',
143
+ justifyContent: 'center',
144
+ flexShrink: 0,
145
+ color: getThumbIconColor(theme, intent),
146
+ variants: {
147
+ size: createThumbIconSizeVariants(theme),
148
+ },
149
+ } as const;
150
+ }
151
+ }
152
+
153
+ // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel transform on native cannot resolve function calls to extract variant structures.
154
+ // @ts-ignore - TS language server needs restart to pick up theme structure changes
155
+ export const sliderStyles = StyleSheet.create((theme: Theme) => {
156
+ return {
157
+ container: {
158
+ gap: 4,
159
+ paddingVertical: 8,
160
+ },
161
+ sliderWrapper: {
162
+ position: 'relative',
163
+ paddingVertical: 4,
164
+ },
165
+ track: {
166
+ backgroundColor: theme.colors.surface.tertiary,
167
+ borderRadius: 9999,
168
+ position: 'relative',
169
+ variants: {
170
+ size: createTrackSizeVariants(theme),
171
+ disabled: {
172
+ true: {
173
+ opacity: 0.5,
174
+ _web: {
175
+ cursor: 'not-allowed',
176
+ },
177
+ },
178
+ false: {
179
+ opacity: 1,
180
+ _web: {
181
+ cursor: 'pointer',
182
+ },
183
+ },
184
+ },
185
+ } as const,
186
+ } as const,
187
+ filledTrack: createFilledTrackStyles(theme),
188
+ thumb: createThumbStyles(theme),
189
+ thumbActive: {
190
+ _web: {
191
+ transform: 'translate(-50%, -50%) scale(1.1)',
192
+ },
193
+ },
194
+ thumbIcon: createThumbIconStyles(theme),
195
+ valueLabel: {
196
+ fontSize: 12,
197
+ fontWeight: '600',
198
+ color: theme.colors.text.primary,
199
+ textAlign: 'center',
200
+ },
201
+ minMaxLabels: {
202
+ flexDirection: 'row',
203
+ justifyContent: 'space-between',
204
+ marginTop: 4,
205
+ },
206
+ minMaxLabel: {
207
+ fontSize: 12,
208
+ color: theme.colors.text.secondary,
209
+ },
210
+ mark: {
211
+ position: 'absolute',
212
+ width: 2,
213
+ backgroundColor: theme.colors.border.secondary,
214
+ top: '50%',
215
+ variants: {
216
+ size: createMarkSizeVariants(theme),
217
+ },
218
+ _web: {
219
+ transform: 'translate(-50%, -50%)',
220
+ },
221
+ },
222
+ marks: {
223
+ position: 'absolute',
224
+ width: '100%',
225
+ height: '100%',
226
+ top: 0,
227
+ left: 0,
228
+ },
229
+ markLabel: {
230
+ position: 'absolute',
231
+ fontSize: 10,
232
+ color: theme.colors.text.secondary,
233
+ top: '100%',
234
+ marginTop: 4,
235
+ _web: {
236
+ transform: 'translateX(-50%)',
237
+ whiteSpace: 'nowrap',
238
+ },
239
+ },
240
+ };
241
+ });
@@ -0,0 +1,226 @@
1
+ import React, { useState, useRef, useCallback, isValidElement, forwardRef } from 'react';
2
+ import { getWebProps } from 'react-native-unistyles/web';
3
+ import { sliderStyles } from './Slider.styles';
4
+ import type { SliderProps } 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 Slider = forwardRef<HTMLDivElement, SliderProps>(({
10
+ value: controlledValue,
11
+ defaultValue = 0,
12
+ min = 0,
13
+ max = 100,
14
+ step = 1,
15
+ disabled = false,
16
+ showValue = false,
17
+ showMinMax = false,
18
+ marks = [],
19
+ intent = 'primary',
20
+ size = 'md',
21
+ icon,
22
+ onValueChange,
23
+ onValueCommit,
24
+ style,
25
+ testID,
26
+ }, ref) => {
27
+ const [internalValue, setInternalValue] = useState(defaultValue);
28
+ const [isDragging, setIsDragging] = useState(false);
29
+ const trackRef = useRef<HTMLDivElement>(null);
30
+ const thumbRef = useRef<HTMLDivElement>(null);
31
+ const hasMoved = useRef(false);
32
+
33
+ const value = controlledValue !== undefined ? controlledValue : internalValue;
34
+
35
+ // Apply variants
36
+ sliderStyles.useVariants({
37
+ size,
38
+ disabled,
39
+ });
40
+
41
+ const containerProps = getWebProps([sliderStyles.container, style as any]);
42
+ const wrapperProps = getWebProps([sliderStyles.sliderWrapper]);
43
+ const trackProps = getWebProps([sliderStyles.track]);
44
+ const thumbIconProps = getWebProps([sliderStyles.thumbIcon({ intent })]);
45
+ const valueLabelProps = getWebProps([sliderStyles.valueLabel]);
46
+ const minMaxLabelsProps = getWebProps([sliderStyles.minMaxLabels]);
47
+ const minMaxLabelProps = getWebProps([sliderStyles.minMaxLabel]);
48
+ const marksProps = getWebProps([sliderStyles.marks]);
49
+
50
+ const clampValue = useCallback((val: number) => {
51
+ const clampedValue = Math.min(Math.max(val, min), max);
52
+ const steppedValue = Math.round(clampedValue / step) * step;
53
+ return Math.min(Math.max(steppedValue, min), max);
54
+ }, [min, max, step]);
55
+
56
+ const calculateValueFromPosition = useCallback((clientX: number) => {
57
+ if (!trackRef.current) return value;
58
+
59
+ const rect = trackRef.current.getBoundingClientRect();
60
+ const percentage = (clientX - rect.left) / rect.width;
61
+ const rawValue = min + percentage * (max - min);
62
+ return clampValue(rawValue);
63
+ }, [min, max, value, clampValue]);
64
+
65
+ const updateValue = useCallback((newValue: number) => {
66
+ const clampedValue = clampValue(newValue);
67
+
68
+ if (controlledValue === undefined) {
69
+ setInternalValue(clampedValue);
70
+ }
71
+
72
+ onValueChange?.(clampedValue);
73
+ }, [controlledValue, clampValue, onValueChange]);
74
+
75
+ const handleMouseDown = useCallback((e: React.MouseEvent) => {
76
+ if (disabled) return;
77
+
78
+ e.preventDefault();
79
+
80
+ // Check if click is on the thumb
81
+ const isThumbClick = thumbRef.current && thumbRef.current.contains(e.target as Node);
82
+
83
+ if (isThumbClick) {
84
+ // Clicking on thumb: only start dragging, don't update value yet
85
+ setIsDragging(true);
86
+ hasMoved.current = false;
87
+ } else {
88
+ // Clicking on track: immediately update value
89
+ setIsDragging(true);
90
+ hasMoved.current = true;
91
+ const newValue = calculateValueFromPosition(e.clientX);
92
+ updateValue(newValue);
93
+ }
94
+ }, [disabled, calculateValueFromPosition, updateValue]);
95
+
96
+ const handleMouseMove = useCallback((e: MouseEvent) => {
97
+ if (!isDragging || disabled) return;
98
+
99
+ hasMoved.current = true;
100
+ const newValue = calculateValueFromPosition(e.clientX);
101
+ updateValue(newValue);
102
+ }, [isDragging, disabled, calculateValueFromPosition, updateValue]);
103
+
104
+ const handleMouseUp = useCallback(() => {
105
+ if (isDragging) {
106
+ setIsDragging(false);
107
+ if (hasMoved.current) {
108
+ onValueCommit?.(value);
109
+ }
110
+ hasMoved.current = false;
111
+ }
112
+ }, [isDragging, value, onValueCommit]);
113
+
114
+ React.useEffect(() => {
115
+ if (isDragging) {
116
+ document.addEventListener('mousemove', handleMouseMove);
117
+ document.addEventListener('mouseup', handleMouseUp);
118
+
119
+ return () => {
120
+ document.removeEventListener('mousemove', handleMouseMove);
121
+ document.removeEventListener('mouseup', handleMouseUp);
122
+ };
123
+ }
124
+ }, [isDragging, handleMouseMove, handleMouseUp]);
125
+
126
+ const percentage = ((value - min) / (max - min)) * 100;
127
+
128
+ // Dynamic styles with percentage
129
+ const filledTrackProps = getWebProps([sliderStyles.filledTrack({ intent }), { width: `${percentage}%` }]);
130
+ const thumbProps = getWebProps([
131
+ sliderStyles.thumb({ intent, disabled }),
132
+ isDragging && sliderStyles.thumbActive,
133
+ { left: `${percentage}%` }
134
+ ]);
135
+
136
+ // Helper to render icon
137
+ const renderIcon = () => {
138
+ if (!icon) return null;
139
+
140
+ if (isIconName(icon)) {
141
+ // Resolve icon name to path and render with IconSvg
142
+ const iconPath = resolveIconPath(icon);
143
+ return (
144
+ <IconSvg
145
+ path={iconPath}
146
+ {...thumbIconProps}
147
+ aria-label={icon}
148
+ />
149
+ );
150
+ } else if (isValidElement(icon)) {
151
+ // Render custom component as-is
152
+ return <span {...thumbIconProps}>{icon}</span>;
153
+ }
154
+
155
+ return null;
156
+ };
157
+
158
+ const mergedRef = useMergeRefs(ref, containerProps.ref);
159
+
160
+ return (
161
+ <div {...containerProps} ref={mergedRef} data-testid={testID}>
162
+ {showValue && (
163
+ <div {...valueLabelProps}>
164
+ {value}
165
+ </div>
166
+ )}
167
+
168
+ <div {...wrapperProps}>
169
+ <div
170
+ {...trackProps}
171
+ ref={trackRef}
172
+ onMouseDown={handleMouseDown}
173
+ role="slider"
174
+ aria-valuenow={value}
175
+ aria-valuemin={min}
176
+ aria-valuemax={max}
177
+ aria-disabled={disabled}
178
+ tabIndex={disabled ? -1 : 0}
179
+ >
180
+ {/* Filled track */}
181
+ <div {...filledTrackProps} />
182
+
183
+ {/* Marks */}
184
+ {marks.length > 0 && (
185
+ <div {...marksProps}>
186
+ {marks.map((mark) => {
187
+ const markPercentage = ((mark.value - min) / (max - min)) * 100;
188
+ const markProps = getWebProps([sliderStyles.mark, { left: `${markPercentage}%` }]);
189
+ const markLabelProps = getWebProps([sliderStyles.markLabel, { left: `${markPercentage}%` }]);
190
+ return (
191
+ <div key={mark.value}>
192
+ <div {...markProps} />
193
+ {mark.label && (
194
+ <div {...markLabelProps}>
195
+ {mark.label}
196
+ </div>
197
+ )}
198
+ </div>
199
+ );
200
+ })}
201
+ </div>
202
+ )}
203
+
204
+ {/* Thumb */}
205
+ <div
206
+ ref={thumbRef}
207
+ {...thumbProps}
208
+ >
209
+ {renderIcon()}
210
+ </div>
211
+ </div>
212
+ </div>
213
+
214
+ {showMinMax && (
215
+ <div {...minMaxLabelsProps}>
216
+ <span {...minMaxLabelProps}>{min}</span>
217
+ <span {...minMaxLabelProps}>{max}</span>
218
+ </div>
219
+ )}
220
+ </div>
221
+ );
222
+ });
223
+
224
+ Slider.displayName = 'Slider';
225
+
226
+ export default Slider;
@@ -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;