@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,124 @@
1
+ import { Intent, Size } from '@idealyst/theme';
2
+ import type { ReactNode } from 'react';
3
+ import type { StyleProp, ViewStyle } from 'react-native';
4
+
5
+ // Component-specific type aliases for future extensibility
6
+ export type SelectIntentVariant = Intent;
7
+ export type SelectSizeVariant = Size;
8
+ export type SelectType = 'outlined' | 'filled';
9
+
10
+ export interface SelectOption {
11
+ /**
12
+ * The unique value for this option
13
+ */
14
+ value: string;
15
+
16
+ /**
17
+ * The display label for this option
18
+ */
19
+ label: string;
20
+
21
+ /**
22
+ * Whether this option is disabled
23
+ */
24
+ disabled?: boolean;
25
+
26
+ /**
27
+ * Optional icon or custom content to display before the label
28
+ */
29
+ icon?: ReactNode;
30
+ }
31
+
32
+ export interface SelectProps {
33
+ /**
34
+ * Array of options to display in the select
35
+ */
36
+ options: SelectOption[];
37
+
38
+ /**
39
+ * The currently selected value
40
+ */
41
+ value?: string;
42
+
43
+ /**
44
+ * Called when the selected value changes
45
+ */
46
+ onValueChange?: (value: string) => void;
47
+
48
+ /**
49
+ * Placeholder text when no value is selected
50
+ */
51
+ placeholder?: string;
52
+
53
+ /**
54
+ * Whether the select is disabled
55
+ */
56
+ disabled?: boolean;
57
+
58
+ /**
59
+ * Whether the select shows an error state
60
+ */
61
+ error?: boolean;
62
+
63
+ /**
64
+ * Helper text to display below the select
65
+ */
66
+ helperText?: string;
67
+
68
+ /**
69
+ * Label text to display above the select
70
+ */
71
+ label?: string;
72
+
73
+ /**
74
+ * The visual type of the select
75
+ */
76
+ type?: SelectType,
77
+
78
+ /**
79
+ * The intent/color scheme of the select
80
+ */
81
+ intent?: SelectIntentVariant;
82
+
83
+ /**
84
+ * The size of the select
85
+ */
86
+ size?: SelectSizeVariant;
87
+
88
+ /**
89
+ * Whether to show a search/filter input (web only)
90
+ */
91
+ searchable?: boolean;
92
+
93
+ /**
94
+ * Custom search filter function (used with searchable)
95
+ */
96
+ filterOption?: (option: SelectOption, searchTerm: string) => boolean;
97
+
98
+ /**
99
+ * Native iOS presentation mode (native only)
100
+ * 'dropdown' uses a standard dropdown overlay
101
+ * 'actionSheet' uses iOS ActionSheet for selection
102
+ */
103
+ presentationMode?: 'dropdown' | 'actionSheet';
104
+
105
+ /**
106
+ * Maximum height for the dropdown content
107
+ */
108
+ maxHeight?: number;
109
+
110
+ /**
111
+ * Additional styles (platform-specific)
112
+ */
113
+ style?: StyleProp<ViewStyle>;
114
+
115
+ /**
116
+ * Test ID for testing
117
+ */
118
+ testID?: string;
119
+
120
+ /**
121
+ * Accessibility label
122
+ */
123
+ accessibilityLabel?: string;
124
+ }
@@ -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
+ }