@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,139 @@
1
+ import React, { useEffect, forwardRef } from 'react';
2
+ import { View } from 'react-native';
3
+ import Animated, {
4
+ useSharedValue,
5
+ useAnimatedStyle,
6
+ withRepeat,
7
+ withSequence,
8
+ withTiming,
9
+ Easing,
10
+ } from 'react-native-reanimated';
11
+ import { skeletonStyles } from './Skeleton.styles';
12
+ import type { SkeletonProps, SkeletonGroupProps } from './types';
13
+
14
+ const Skeleton = forwardRef<View, SkeletonProps>(({
15
+ width = '100%',
16
+ height = 20,
17
+ shape = 'rectangle',
18
+ borderRadius,
19
+ animation = 'pulse',
20
+ style,
21
+ testID,
22
+ }, ref) => {
23
+ skeletonStyles.useVariants({
24
+ shape,
25
+ animation,
26
+ });
27
+
28
+ const animatedValue = useSharedValue(0);
29
+
30
+ useEffect(() => {
31
+ if (animation === 'pulse') {
32
+ // Pulse animation: opacity from 1 -> 0.5 -> 1
33
+ animatedValue.value = withRepeat(
34
+ withSequence(
35
+ withTiming(1, { duration: 750, easing: Easing.inOut(Easing.ease) }),
36
+ withTiming(0, { duration: 750, easing: Easing.inOut(Easing.ease) })
37
+ ),
38
+ -1, // infinite
39
+ false
40
+ );
41
+ } else if (animation === 'wave') {
42
+ // Wave animation: translateX from -100% -> 100%
43
+ animatedValue.value = withRepeat(
44
+ withTiming(1, { duration: 1500, easing: Easing.inOut(Easing.ease) }),
45
+ -1, // infinite
46
+ false
47
+ );
48
+ }
49
+ }, [animation]);
50
+
51
+ const pulseAnimatedStyle = useAnimatedStyle(() => {
52
+ if (animation === 'pulse') {
53
+ return {
54
+ opacity: animatedValue.value === 0 ? 1 : 1 - animatedValue.value * 0.5,
55
+ };
56
+ }
57
+ return {};
58
+ });
59
+
60
+ const waveAnimatedStyle = useAnimatedStyle(() => {
61
+ if (animation === 'wave') {
62
+ return {
63
+ transform: [
64
+ {
65
+ translateX: (animatedValue.value - 0.5) * 400, // -200 to 200
66
+ },
67
+ ],
68
+ };
69
+ }
70
+ return {};
71
+ });
72
+
73
+ const customStyles = {
74
+ ...(shape === 'rounded' && borderRadius ? { borderRadius } : {}),
75
+ ...(shape === 'circle' ? { aspectRatio: 1 } : {}),
76
+ };
77
+
78
+ return (
79
+ <Animated.View
80
+ ref={ref as any}
81
+ style={[
82
+ skeletonStyles.skeleton,
83
+ customStyles,
84
+ style,
85
+ pulseAnimatedStyle,
86
+ ]}
87
+ testID={testID}
88
+ >
89
+ {animation === 'wave' && (
90
+ <Animated.View
91
+ style={[
92
+ {
93
+ position: 'absolute',
94
+ top: 0,
95
+ left: 0,
96
+ right: 0,
97
+ bottom: 0,
98
+ backgroundColor: 'rgba(255, 255, 255, 0.3)',
99
+ },
100
+ waveAnimatedStyle,
101
+ ]}
102
+ />
103
+ )}
104
+ </Animated.View>
105
+ );
106
+ });
107
+
108
+ Skeleton.displayName = 'Skeleton';
109
+
110
+ export const SkeletonGroup: React.FC<SkeletonGroupProps> = ({
111
+ count = 3,
112
+ spacing = 12,
113
+ skeletonProps,
114
+ style,
115
+ testID,
116
+ }) => {
117
+ skeletonStyles.useVariants({});
118
+
119
+ return (
120
+ <View
121
+ style={[
122
+ skeletonStyles.group,
123
+ { gap: spacing },
124
+ style,
125
+ ]}
126
+ testID={testID}
127
+ >
128
+ {Array.from({ length: count }).map((_, index) => (
129
+ <Skeleton
130
+ key={index}
131
+ {...skeletonProps}
132
+ testID={testID ? `${testID}-item-${index}` : undefined}
133
+ />
134
+ ))}
135
+ </View>
136
+ );
137
+ };
138
+
139
+ export default Skeleton;
@@ -0,0 +1,59 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+ import { Theme, StylesheetStyles} from '@idealyst/theme';
3
+
4
+ type SkeletonShape = 'rectangle' | 'rounded' | 'circle';
5
+ type SkeletonAnimation = 'pulse' | 'wave' | 'none';
6
+
7
+ type SkeletonVariants = {
8
+ shape: SkeletonShape;
9
+ animation: SkeletonAnimation;
10
+ }
11
+
12
+ export type ExpandedSkeletonStyles = StylesheetStyles<keyof SkeletonVariants>;
13
+ export type ExpandedSkeletonGroupStyles = StylesheetStyles<never>;
14
+
15
+ export type SkeletonStylesheet = {
16
+ skeleton: ExpandedSkeletonStyles;
17
+ group: ExpandedSkeletonGroupStyles;
18
+ }
19
+
20
+ /**
21
+ * Create shape variants for skeleton
22
+ */
23
+ function createShapeVariants(theme: Theme) {
24
+ return {
25
+ rectangle: {
26
+ borderRadius: 0,
27
+ },
28
+ rounded: {
29
+ borderRadius: 8,
30
+ },
31
+ circle: {
32
+ borderRadius: 9999,
33
+ },
34
+ } as const;
35
+ }
36
+
37
+ // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel
38
+ // transform on native cannot resolve function calls to extract variant structures.
39
+ // @ts-ignore - TS language server needs restart to pick up theme structure changes
40
+ export const skeletonStyles = StyleSheet.create((theme: Theme) => {
41
+ return {
42
+ skeleton: {
43
+ backgroundColor: theme.colors.surface.tertiary,
44
+ overflow: 'hidden',
45
+ variants: {
46
+ shape: createShapeVariants(theme),
47
+ animation: {
48
+ pulse: {},
49
+ wave: {},
50
+ none: {},
51
+ },
52
+ },
53
+ },
54
+ group: {
55
+ display: 'flex',
56
+ flexDirection: 'column',
57
+ },
58
+ };
59
+ });
@@ -0,0 +1,112 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { getWebProps } from 'react-native-unistyles/web';
3
+ import { skeletonStyles } from './Skeleton.styles';
4
+ import type { SkeletonProps, SkeletonGroupProps } from './types';
5
+
6
+ const Skeleton: React.FC<SkeletonProps> = ({
7
+ width = '100%',
8
+ height = 20,
9
+ shape = 'rectangle',
10
+ borderRadius,
11
+ animation = 'pulse',
12
+ style,
13
+ testID,
14
+ }) => {
15
+ skeletonStyles.useVariants({
16
+ shape,
17
+ animation,
18
+ });
19
+
20
+ const skeletonProps = getWebProps([skeletonStyles.skeleton, style as any]);
21
+
22
+ // Apply custom border radius if provided and shape is 'rounded'
23
+ const customStyles: React.CSSProperties = {
24
+ width: typeof width === 'number' ? `${width}px` : width,
25
+ height: typeof height === 'number' ? `${height}px` : height,
26
+ ...(shape === 'rounded' && borderRadius ? { borderRadius: `${borderRadius}px` } : {}),
27
+ ...(shape === 'circle' ? { aspectRatio: '1' } : {}),
28
+ };
29
+
30
+ // Animation styles
31
+ const animationStyles: React.CSSProperties = {};
32
+ if (animation === 'pulse') {
33
+ animationStyles.animation = 'skeleton-pulse 1.5s ease-in-out infinite';
34
+ } else if (animation === 'wave') {
35
+ animationStyles.position = 'relative';
36
+ animationStyles.overflow = 'hidden';
37
+ }
38
+
39
+ return (
40
+ <>
41
+ {animation === 'pulse' && (
42
+ <style>{`
43
+ @keyframes skeleton-pulse {
44
+ 0%, 100% { opacity: 1; }
45
+ 50% { opacity: 0.5; }
46
+ }
47
+ `}</style>
48
+ )}
49
+ {animation === 'wave' && (
50
+ <style>{`
51
+ @keyframes skeleton-wave {
52
+ 0% { transform: translateX(-100%); }
53
+ 100% { transform: translateX(100%); }
54
+ }
55
+ `}</style>
56
+ )}
57
+ <div
58
+ {...skeletonProps}
59
+ style={{
60
+ ...customStyles,
61
+ ...animationStyles,
62
+ }}
63
+ data-testid={testID}
64
+ >
65
+ {animation === 'wave' && (
66
+ <div
67
+ style={{
68
+ position: 'absolute',
69
+ top: 0,
70
+ left: 0,
71
+ right: 0,
72
+ bottom: 0,
73
+ background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent)',
74
+ animation: 'skeleton-wave 1.5s ease-in-out infinite',
75
+ }}
76
+ />
77
+ )}
78
+ </div>
79
+ </>
80
+ );
81
+ };
82
+
83
+ export const SkeletonGroup: React.FC<SkeletonGroupProps> = ({
84
+ count = 3,
85
+ spacing = 12,
86
+ skeletonProps,
87
+ style,
88
+ testID,
89
+ }) => {
90
+ skeletonStyles.useVariants({});
91
+ const groupProps = getWebProps([skeletonStyles.group, style as any]);
92
+
93
+ return (
94
+ <div
95
+ {...groupProps}
96
+ style={{
97
+ gap: `${spacing}px`,
98
+ }}
99
+ data-testid={testID}
100
+ >
101
+ {Array.from({ length: count }).map((_, index) => (
102
+ <Skeleton
103
+ key={index}
104
+ {...skeletonProps}
105
+ testID={testID ? `${testID}-item-${index}` : undefined}
106
+ />
107
+ ))}
108
+ </div>
109
+ );
110
+ };
111
+
112
+ export default Skeleton;
@@ -0,0 +1,4 @@
1
+ import Skeleton from './Skeleton.native';
2
+ export { SkeletonGroup } from './Skeleton.native';
3
+ export default Skeleton;
4
+ export type { SkeletonProps, SkeletonGroupProps, SkeletonShape, SkeletonAnimation } from './types';
@@ -0,0 +1,5 @@
1
+ import SkeletonComponent from './Skeleton.web';
2
+ export { SkeletonGroup } from './Skeleton.web';
3
+ export default SkeletonComponent;
4
+ export { SkeletonComponent as Skeleton };
5
+ export type { SkeletonProps, SkeletonGroupProps, SkeletonShape, SkeletonAnimation } from './types';
@@ -0,0 +1,5 @@
1
+ import SkeletonComponent from './Skeleton.web';
2
+ export { SkeletonGroup } from './Skeleton.web';
3
+ export default SkeletonComponent;
4
+ export { SkeletonComponent as Skeleton };
5
+ export type { SkeletonProps, SkeletonGroupProps, SkeletonShape, SkeletonAnimation } from './types';
@@ -0,0 +1,75 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+
3
+ export type SkeletonShape = 'rectangle' | 'circle' | 'rounded';
4
+ export type SkeletonAnimation = 'pulse' | 'wave' | 'none';
5
+
6
+ export interface SkeletonProps {
7
+ /**
8
+ * Width of the skeleton (number in pixels or string with units)
9
+ * @default '100%'
10
+ */
11
+ width?: number | string;
12
+
13
+ /**
14
+ * Height of the skeleton (number in pixels or string with units)
15
+ * @default 20
16
+ */
17
+ height?: number | string;
18
+
19
+ /**
20
+ * Shape of the skeleton
21
+ * @default 'rectangle'
22
+ */
23
+ shape?: SkeletonShape;
24
+
25
+ /**
26
+ * Border radius for 'rounded' shape (in pixels)
27
+ * @default 8
28
+ */
29
+ borderRadius?: number;
30
+
31
+ /**
32
+ * Animation type
33
+ * @default 'pulse'
34
+ */
35
+ animation?: SkeletonAnimation;
36
+
37
+ /**
38
+ * Additional custom styles
39
+ */
40
+ style?: StyleProp<ViewStyle>;
41
+
42
+ /**
43
+ * Test ID for testing
44
+ */
45
+ testID?: string;
46
+ }
47
+
48
+ export interface SkeletonGroupProps {
49
+ /**
50
+ * Number of skeleton items to render
51
+ * @default 3
52
+ */
53
+ count?: number;
54
+
55
+ /**
56
+ * Spacing between skeleton items (in pixels)
57
+ * @default 12
58
+ */
59
+ spacing?: number;
60
+
61
+ /**
62
+ * Props to pass to each skeleton item
63
+ */
64
+ skeletonProps?: Omit<SkeletonProps, 'testID'>;
65
+
66
+ /**
67
+ * Additional custom styles for the container
68
+ */
69
+ style?: StyleProp<ViewStyle>;
70
+
71
+ /**
72
+ * Test ID for testing
73
+ */
74
+ testID?: string;
75
+ }
@@ -0,0 +1,248 @@
1
+ import React, { useState, useCallback, forwardRef } from 'react';
2
+ import { View } from 'react-native';
3
+ import { GestureDetector, Gesture } from 'react-native-gesture-handler';
4
+ import Animated, { useSharedValue, useAnimatedStyle, runOnJS, withSpring } from 'react-native-reanimated';
5
+ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
6
+ import { sliderStyles } from './Slider.styles';
7
+ import Text from '../Text';
8
+ import type { SliderProps } from './types';
9
+ import { isIconName } from '../Icon/icon-resolver';
10
+
11
+ const Slider = forwardRef<View, SliderProps>(({
12
+ value: controlledValue,
13
+ defaultValue = 0,
14
+ min = 0,
15
+ max = 100,
16
+ step = 1,
17
+ disabled = false,
18
+ showValue = false,
19
+ showMinMax = false,
20
+ marks = [],
21
+ intent = 'primary',
22
+ size = 'md',
23
+ icon,
24
+ onValueChange,
25
+ onValueCommit,
26
+ style,
27
+ testID,
28
+ }, ref) => {
29
+ const [internalValue, setInternalValue] = useState(defaultValue);
30
+ const [trackWidthState, setTrackWidthState] = useState(0);
31
+ const trackWidth = useSharedValue(0);
32
+ const translateX = useSharedValue(0);
33
+ const isDragging = useSharedValue(false);
34
+ const scale = useSharedValue(1);
35
+
36
+ const value = controlledValue !== undefined ? controlledValue : internalValue;
37
+
38
+ sliderStyles.useVariants({
39
+ size,
40
+ disabled,
41
+ });
42
+
43
+ const clampValue = (val: number) => {
44
+ 'worklet';
45
+ const clampedValue = Math.min(Math.max(val, min), max);
46
+ const steppedValue = Math.round(clampedValue / step) * step;
47
+ return Math.min(Math.max(steppedValue, min), max);
48
+ };
49
+
50
+ const calculateValueFromPosition = (position: number) => {
51
+ 'worklet';
52
+ if (trackWidth.value === 0) return value;
53
+ const percentage = Math.max(0, Math.min(1, position / trackWidth.value));
54
+ const rawValue = min + percentage * (max - min);
55
+ return clampValue(rawValue);
56
+ };
57
+
58
+ const updateValue = useCallback((newValue: number) => {
59
+ if (controlledValue === undefined) {
60
+ setInternalValue(newValue);
61
+ }
62
+
63
+ onValueChange?.(newValue);
64
+ }, [controlledValue, onValueChange]);
65
+
66
+ const commitValue = useCallback((finalValue: number) => {
67
+ onValueCommit?.(finalValue);
68
+ }, [onValueCommit]);
69
+
70
+ // Update translateX when value changes externally
71
+ React.useEffect(() => {
72
+ if (!isDragging.value && trackWidth.value > 0) {
73
+ const percentage = (value - min) / (max - min);
74
+ translateX.value = percentage * trackWidth.value;
75
+ }
76
+ }, [value, min, max, trackWidth, translateX, isDragging]);
77
+
78
+ const panGesture = Gesture.Pan()
79
+ .enabled(!disabled)
80
+ .activeOffsetX([-5, 5])
81
+ .failOffsetY([-5, 5])
82
+ .onStart(() => {
83
+ isDragging.value = true;
84
+ scale.value = withSpring(1.15, { damping: 15, stiffness: 200 });
85
+ })
86
+ .onUpdate((event) => {
87
+ const newPosition = Math.max(0, Math.min(trackWidth.value, event.x));
88
+ translateX.value = newPosition;
89
+ const newValue = calculateValueFromPosition(newPosition);
90
+ runOnJS(updateValue)(newValue);
91
+ })
92
+ .onEnd(() => {
93
+ isDragging.value = false;
94
+ scale.value = withSpring(1, { damping: 15, stiffness: 200 });
95
+ const newPosition = Math.max(0, Math.min(trackWidth.value, translateX.value));
96
+ const finalValue = calculateValueFromPosition(newPosition);
97
+ runOnJS(commitValue)(finalValue);
98
+ });
99
+
100
+ const tapGesture = Gesture.Tap()
101
+ .enabled(!disabled)
102
+ .onBegin(() => {
103
+ scale.value = withSpring(1.15, { damping: 15, stiffness: 200 });
104
+ })
105
+ .onEnd((event) => {
106
+ const newPosition = Math.max(0, Math.min(trackWidth.value, event.x));
107
+ translateX.value = newPosition;
108
+ const newValue = calculateValueFromPosition(newPosition);
109
+ runOnJS(updateValue)(newValue);
110
+ runOnJS(commitValue)(newValue);
111
+ scale.value = withSpring(1, { damping: 15, stiffness: 200 });
112
+ })
113
+ .onFinalize(() => {
114
+ scale.value = withSpring(1, { damping: 15, stiffness: 200 });
115
+ });
116
+
117
+ const composedGesture = Gesture.Race(panGesture, tapGesture);
118
+
119
+ const getThumbSize = () => {
120
+ if (size === 'sm') return 16;
121
+ if (size === 'lg') return 24;
122
+ return 20;
123
+ };
124
+
125
+ const thumbSize = getThumbSize();
126
+
127
+ const thumbAnimatedStyle = useAnimatedStyle(() => {
128
+ return {
129
+ transform: [
130
+ { translateX: translateX.value - thumbSize / 2 },
131
+ { scale: scale.value },
132
+ ],
133
+ } as any;
134
+ });
135
+
136
+ const filledTrackAnimatedStyle = useAnimatedStyle(() => {
137
+ return {
138
+ width: translateX.value,
139
+ };
140
+ });
141
+
142
+ const percentage = ((value - min) / (max - min)) * 100;
143
+
144
+ const renderIcon = () => {
145
+ if (!icon) return null;
146
+
147
+ if (typeof icon === 'string' && isIconName(icon)) {
148
+ const iconStyle = sliderStyles.thumbIcon({ intent });
149
+ return (
150
+ <MaterialCommunityIcons
151
+ name={icon}
152
+ size={iconStyle.width || 16}
153
+ color={iconStyle.color}
154
+ />
155
+ );
156
+ }
157
+
158
+ return icon;
159
+ };
160
+
161
+ return (
162
+ <View ref={ref} style={[sliderStyles.container, style]} testID={testID}>
163
+ {showValue && (
164
+ <View style={sliderStyles.valueLabel as any}>
165
+ <Text>{value}</Text>
166
+ </View>
167
+ )}
168
+
169
+ <View style={sliderStyles.sliderWrapper}>
170
+ <GestureDetector gesture={composedGesture}>
171
+ <View
172
+ style={sliderStyles.track}
173
+ onLayout={(e) => {
174
+ const width = e.nativeEvent.layout.width;
175
+ trackWidth.value = width;
176
+ setTrackWidthState(width);
177
+ }}
178
+ >
179
+ {/* Filled track */}
180
+ <Animated.View
181
+ style={[sliderStyles.filledTrack({ intent }), filledTrackAnimatedStyle]}
182
+ />
183
+
184
+ {/* Marks */}
185
+ {marks.length > 0 && trackWidthState > 0 && (
186
+ <View style={sliderStyles.marks}>
187
+ {marks.map((mark) => {
188
+ const markPercentage = ((mark.value - min) / (max - min)) * 100;
189
+ const markPosition = (markPercentage / 100) * trackWidthState;
190
+ return (
191
+ <View key={mark.value}>
192
+ <View
193
+ style={[
194
+ sliderStyles.mark,
195
+ { left: markPosition },
196
+ ]}
197
+ />
198
+ {mark.label && (
199
+ <View
200
+ style={[
201
+ sliderStyles.markLabel,
202
+ { left: markPosition },
203
+ ]}
204
+ >
205
+ <Text size="sm">{mark.label}</Text>
206
+ </View>
207
+ )}
208
+ </View>
209
+ );
210
+ })}
211
+ </View>
212
+ )}
213
+
214
+ {/* Thumb */}
215
+ <Animated.View
216
+ style={[
217
+ sliderStyles.thumb({ intent, disabled }),
218
+ {
219
+ // Manual positioning/sizing for native layout
220
+ position: 'absolute',
221
+ top: '50%',
222
+ marginTop: -thumbSize / 2,
223
+ width: thumbSize,
224
+ height: thumbSize,
225
+ borderRadius: thumbSize / 2,
226
+ },
227
+ thumbAnimatedStyle,
228
+ ]}
229
+ >
230
+ {renderIcon()}
231
+ </Animated.View>
232
+ </View>
233
+ </GestureDetector>
234
+ </View>
235
+
236
+ {showMinMax && (
237
+ <View style={sliderStyles.minMaxLabels}>
238
+ <Text style={sliderStyles.minMaxLabel} size="sm">{min}</Text>
239
+ <Text style={sliderStyles.minMaxLabel} size="sm">{max}</Text>
240
+ </View>
241
+ )}
242
+ </View>
243
+ );
244
+ });
245
+
246
+ Slider.displayName = 'Slider';
247
+
248
+ export default Slider;