@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,182 @@
1
+ import React, { useState, forwardRef, useEffect } from 'react';
2
+ import { View, TouchableOpacity, LayoutChangeEvent } from 'react-native';
3
+ import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing } from 'react-native-reanimated';
4
+ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
5
+ import { accordionStyles } from './Accordion.styles';
6
+ import Text from '../Text';
7
+ import type { AccordionProps, AccordionItem as AccordionItemType } from './types';
8
+
9
+ interface AccordionItemProps {
10
+ item: AccordionItemType;
11
+ isExpanded: boolean;
12
+ onToggle: () => void;
13
+ size: AccordionProps['size'];
14
+ type: AccordionProps['type'];
15
+ isLast: boolean;
16
+ testID?: string;
17
+ }
18
+
19
+ const AccordionItem: React.FC<AccordionItemProps> = ({
20
+ item,
21
+ isExpanded,
22
+ onToggle,
23
+ size,
24
+ type,
25
+ isLast,
26
+ testID,
27
+ }) => {
28
+ const contentHeight = useSharedValue(0);
29
+ const iconRotation = useSharedValue(0);
30
+ const [measuredHeight, setMeasuredHeight] = useState(0);
31
+
32
+ // Apply item-specific variants
33
+ accordionStyles.useVariants({
34
+ type,
35
+ isLast,
36
+ size,
37
+ expanded: isExpanded,
38
+ disabled: Boolean(item.disabled),
39
+ });
40
+
41
+ // Animate height and icon rotation when expanded state changes
42
+ useEffect(() => {
43
+ contentHeight.value = withTiming(
44
+ isExpanded ? measuredHeight : 0,
45
+ {
46
+ duration: 250,
47
+ easing: Easing.bezier(0.4, 0.0, 0.2, 1), // Material Design standard easing
48
+ }
49
+ );
50
+ iconRotation.value = withTiming(
51
+ isExpanded ? 180 : 0,
52
+ {
53
+ duration: 200,
54
+ easing: Easing.bezier(0.4, 0.0, 0.2, 1),
55
+ }
56
+ );
57
+ }, [isExpanded, measuredHeight]);
58
+
59
+ const animatedContentStyle = useAnimatedStyle(() => ({
60
+ height: contentHeight.value,
61
+ overflow: 'hidden',
62
+ }));
63
+
64
+ const animatedIconStyle = useAnimatedStyle(() => ({
65
+ transform: [{ rotate: `${iconRotation.value}deg` }],
66
+ }));
67
+
68
+ const handleContentLayout = (event: LayoutChangeEvent) => {
69
+ const { height } = event.nativeEvent.layout;
70
+ if (height > 0 && height !== measuredHeight) {
71
+ setMeasuredHeight(height);
72
+ }
73
+ };
74
+
75
+ return (
76
+ <View style={accordionStyles.item} testID={testID}>
77
+ <TouchableOpacity
78
+ style={accordionStyles.header}
79
+ onPress={onToggle}
80
+ disabled={item.disabled}
81
+ activeOpacity={0.7}
82
+ >
83
+ <View style={accordionStyles.title}>
84
+ <Text style={accordionStyles.header}>
85
+ {item.title}
86
+ </Text>
87
+ </View>
88
+ <Animated.View style={[accordionStyles.icon, animatedIconStyle]}>
89
+ <MaterialCommunityIcons
90
+ name="chevron-down"
91
+ size={20}
92
+ style={accordionStyles.icon}
93
+ />
94
+ </Animated.View>
95
+ </TouchableOpacity>
96
+
97
+ {/* Hidden view for measuring content height */}
98
+ <View
99
+ style={{ position: 'absolute', opacity: 0, zIndex: -1 }}
100
+ onLayout={handleContentLayout}
101
+ >
102
+ <View style={accordionStyles.contentInner}>
103
+ {typeof item.content === 'string' ? (
104
+ <Text style={accordionStyles.contentInner}>
105
+ {item.content}
106
+ </Text>
107
+ ) : (
108
+ item.content
109
+ )}
110
+ </View>
111
+ </View>
112
+
113
+ {/* Animated visible content */}
114
+ <Animated.View style={animatedContentStyle}>
115
+ <View style={accordionStyles.contentInner}>
116
+ {typeof item.content === 'string' ? (
117
+ <Text style={accordionStyles.contentInner}>
118
+ {item.content}
119
+ </Text>
120
+ ) : (
121
+ item.content
122
+ )}
123
+ </View>
124
+ </Animated.View>
125
+ </View>
126
+ );
127
+ };
128
+
129
+ const Accordion = forwardRef<View, AccordionProps>(({
130
+ items,
131
+ allowMultiple = false,
132
+ defaultExpanded = [],
133
+ type = 'standard',
134
+ size = 'md',
135
+ style,
136
+ testID,
137
+ }, ref) => {
138
+ const [expandedItems, setExpandedItems] = useState<string[]>(defaultExpanded);
139
+
140
+ // Apply variants
141
+ accordionStyles.useVariants({
142
+ type,
143
+ size,
144
+ });
145
+
146
+ const toggleItem = (itemId: string, disabled?: boolean) => {
147
+ if (disabled) return;
148
+
149
+ setExpandedItems((prev) => {
150
+ const isExpanded = prev.includes(itemId);
151
+
152
+ if (allowMultiple) {
153
+ return isExpanded
154
+ ? prev.filter((id) => id !== itemId)
155
+ : [...prev, itemId];
156
+ } else {
157
+ return isExpanded ? [] : [itemId];
158
+ }
159
+ });
160
+ };
161
+
162
+ return (
163
+ <View ref={ref} style={[accordionStyles.container, style]} testID={testID}>
164
+ {items.map((item, index) => (
165
+ <AccordionItem
166
+ key={item.id}
167
+ item={item}
168
+ isExpanded={expandedItems.includes(item.id)}
169
+ onToggle={() => toggleItem(item.id, item.disabled)}
170
+ size={size}
171
+ type={type}
172
+ isLast={index === items.length - 1}
173
+ testID={`${testID}-item-${item.id}`}
174
+ />
175
+ ))}
176
+ </View>
177
+ );
178
+ });
179
+
180
+ Accordion.displayName = 'Accordion';
181
+
182
+ export default Accordion;
@@ -0,0 +1,260 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+ import { Theme, StylesheetStyles, CompoundVariants, Size} from '@idealyst/theme';
3
+ import { buildSizeVariants } from '../utils/buildSizeVariants';
4
+ import { AccordionType } from './types';
5
+
6
+ type AccordionSize = Size;
7
+
8
+ type AccordionVariants = {
9
+ size: AccordionSize;
10
+ type: AccordionType;
11
+ expanded: boolean;
12
+ disabled: boolean;
13
+ isLast: boolean;
14
+ }
15
+
16
+ export type ExpandedAccordionStyles = StylesheetStyles<keyof AccordionVariants>;
17
+
18
+ /**
19
+ * Create type variants for container
20
+ */
21
+ function createContainerTypeVariants(theme: Theme) {
22
+ return {
23
+ standard: {
24
+ gap: 0,
25
+ },
26
+ separated: {
27
+ gap: 8,
28
+ },
29
+ bordered: {
30
+ gap: 0,
31
+ borderWidth: 1,
32
+ borderStyle: 'solid' as const,
33
+ borderColor: theme.colors.border.primary,
34
+ borderRadius: 8,
35
+ overflow: 'hidden' as const,
36
+ },
37
+ } as const;
38
+ }
39
+
40
+ /**
41
+ * Create type variants for items
42
+ */
43
+ function createItemTypeVariants(theme: Theme) {
44
+ return {
45
+ standard: {
46
+ borderBottomWidth: 1,
47
+ borderBottomStyle: 'solid' as const,
48
+ borderBottomColor: theme.colors.border.primary,
49
+ },
50
+ separated: {
51
+ borderWidth: 1,
52
+ borderStyle: 'solid' as const,
53
+ borderColor: theme.colors.border.primary,
54
+ borderRadius: 8,
55
+ overflow: 'hidden' as const,
56
+ },
57
+ bordered: {
58
+ borderBottomWidth: 1,
59
+ borderBottomStyle: 'solid' as const,
60
+ borderBottomColor: theme.colors.border.primary,
61
+ },
62
+ } as const;
63
+ }
64
+
65
+ /**
66
+ * Create compound variants for item (type + isLast)
67
+ */
68
+ function createItemCompoundVariants(): CompoundVariants<keyof AccordionVariants> {
69
+ return [
70
+ {
71
+ type: 'standard',
72
+ isLast: true,
73
+ styles: {
74
+ borderBottomWidth: 0,
75
+ },
76
+ },
77
+ {
78
+ type: 'bordered',
79
+ isLast: true,
80
+ styles: {
81
+ borderBottomWidth: 0,
82
+ },
83
+ },
84
+ ];
85
+ }
86
+
87
+ /**
88
+ * Create size variants for header
89
+ */
90
+ function createHeaderSizeVariants(theme: Theme) {
91
+ return buildSizeVariants(theme, 'accordion', (size) => ({
92
+ fontSize: size.headerFontSize,
93
+ padding: size.headerPadding,
94
+ }));
95
+ }
96
+
97
+ /**
98
+ * Create size variants for icon
99
+ */
100
+ function createIconSizeVariants(theme: Theme) {
101
+ return buildSizeVariants(theme, 'accordion', (size) => ({
102
+ width: size.iconSize,
103
+ height: size.iconSize,
104
+ }));
105
+ }
106
+
107
+ /**
108
+ * Create size variants for content inner
109
+ */
110
+ function createContentInnerSizeVariants(theme: Theme) {
111
+ return buildSizeVariants(theme, 'accordion', (size) => ({
112
+ fontSize: size.headerFontSize,
113
+ padding: size.contentPadding,
114
+ paddingTop: 0,
115
+ }));
116
+ }
117
+
118
+ // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel transform on native cannot resolve function calls to extract variant structures.
119
+ export const accordionStyles = StyleSheet.create((theme: Theme) => {
120
+ return {
121
+ container: {
122
+ display: 'flex' as const,
123
+ flexDirection: 'column' as const,
124
+ variants: {
125
+ size: { xs: {}, sm: {}, md: {}, lg: {}, xl: {} },
126
+ type: createContainerTypeVariants(theme),
127
+ expanded: { true: {}, false: {} },
128
+ disabled: { true: {}, false: {} },
129
+ isLast: { true: {}, false: {} },
130
+ },
131
+ },
132
+ item: {
133
+ display: 'flex' as const,
134
+ flexDirection: 'column' as const,
135
+ variants: {
136
+ size: { xs: {}, sm: {}, md: {}, lg: {}, xl: {} },
137
+ type: createItemTypeVariants(theme),
138
+ expanded: { true: {}, false: {} },
139
+ disabled: { true: {}, false: {} },
140
+ isLast: {
141
+ true: {},
142
+ false: {},
143
+ },
144
+ },
145
+ compoundVariants: createItemCompoundVariants(),
146
+ },
147
+ header: {
148
+ display: 'flex' as const,
149
+ flexDirection: 'row' as const,
150
+ alignItems: 'center' as const,
151
+ justifyContent: 'space-between' as const,
152
+ width: '100%' as const,
153
+ backgroundColor: 'transparent' as const,
154
+ color: theme.colors.text.primary,
155
+ textAlign: 'left' as const,
156
+ variants: {
157
+ size: createHeaderSizeVariants(theme),
158
+ type: { standard: {}, separated: {}, bordered: {} },
159
+ expanded: {
160
+ true: {
161
+ fontWeight: '600' as const,
162
+ },
163
+ false: {
164
+ fontWeight: '500' as const,
165
+ },
166
+ },
167
+ disabled: {
168
+ true: {
169
+ opacity: 0.5,
170
+ _web: {
171
+ cursor: 'not-allowed' as const,
172
+ },
173
+ },
174
+ false: {
175
+ _web: {
176
+ cursor: 'pointer' as const,
177
+ _hover: {
178
+ backgroundColor: theme.colors.surface.secondary,
179
+ },
180
+ },
181
+ },
182
+ },
183
+ isLast: { true: {}, false: {} },
184
+ } as const,
185
+ _web: {
186
+ border: 'none' as const,
187
+ outline: 'none' as const,
188
+ transition: 'background-color 0.2s ease' as const,
189
+ },
190
+ },
191
+ title: {
192
+ flex: 1,
193
+ variants: {
194
+ size: { xs: {}, sm: {}, md: {}, lg: {}, xl: {} },
195
+ type: { standard: {}, separated: {}, bordered: {} },
196
+ expanded: { true: {}, false: {} },
197
+ disabled: { true: {}, false: {} },
198
+ isLast: { true: {}, false: {} },
199
+ },
200
+ },
201
+ icon: {
202
+ display: 'flex' as const,
203
+ alignItems: 'center' as const,
204
+ justifyContent: 'center' as const,
205
+ marginLeft: 8,
206
+ color: theme.intents.primary.primary,
207
+ variants: {
208
+ size: createIconSizeVariants(theme),
209
+ type: { standard: {}, separated: {}, bordered: {} },
210
+ expanded: {
211
+ true: {
212
+ _web: {
213
+ transform: 'rotate(180deg)' as const,
214
+ },
215
+ },
216
+ false: {
217
+ _web: {
218
+ transform: 'rotate(0deg)' as const,
219
+ },
220
+ },
221
+ },
222
+ disabled: { true: {}, false: {} },
223
+ isLast: { true: {}, false: {} },
224
+ },
225
+ _web: {
226
+ transition: 'transform 0.2s ease' as const,
227
+ },
228
+ },
229
+ content: {
230
+ overflow: 'hidden' as const,
231
+ variants: {
232
+ size: { xs: {}, sm: {}, md: {}, lg: {}, xl: {} },
233
+ type: { standard: {}, separated: {}, bordered: {} },
234
+ expanded: {
235
+ true: {
236
+ maxHeight: 2000,
237
+ },
238
+ false: {
239
+ maxHeight: 0,
240
+ },
241
+ },
242
+ disabled: { true: {}, false: {} },
243
+ isLast: { true: {}, false: {} },
244
+ },
245
+ _web: {
246
+ transition: 'height 0.15s ease, padding 0.3s ease' as const,
247
+ },
248
+ },
249
+ contentInner: {
250
+ color: theme.colors.text.secondary,
251
+ variants: {
252
+ size: createContentInnerSizeVariants(theme),
253
+ type: { standard: {}, separated: {}, bordered: {} },
254
+ expanded: { true: {}, false: {} },
255
+ disabled: { true: {}, false: {} },
256
+ isLast: { true: {}, false: {} },
257
+ },
258
+ },
259
+ };
260
+ });
@@ -0,0 +1,147 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { getWebProps } from 'react-native-unistyles/web';
3
+ import { accordionStyles } from './Accordion.styles';
4
+ import type { AccordionProps, AccordionItem as AccordionItemType } from './types';
5
+ import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
6
+ import { resolveIconPath } from '../Icon/icon-resolver';
7
+
8
+ interface AccordionItemProps {
9
+ item: AccordionItemType;
10
+ type: AccordionProps['type'];
11
+ isExpanded: boolean;
12
+ onToggle: () => void;
13
+ size: AccordionProps['size'];
14
+ testID?: string;
15
+ }
16
+
17
+ const AccordionItem: React.FC<AccordionItemProps> = ({
18
+ item,
19
+ isExpanded,
20
+ onToggle,
21
+ type,
22
+ size,
23
+ testID,
24
+ }) => {
25
+ const contentInnerRef = useRef<HTMLDivElement>(null);
26
+ const [contentHeight, setContentHeight] = useState(0);
27
+ const chevronIconPath = resolveIconPath('chevron-down');
28
+
29
+ accordionStyles.useVariants({
30
+ size,
31
+ type,
32
+ expanded: isExpanded,
33
+ disabled: Boolean(item.disabled),
34
+ });
35
+
36
+ const itemProps = getWebProps([accordionStyles.item]);
37
+ const headerProps = getWebProps([accordionStyles.header]);
38
+ const titleProps = getWebProps([accordionStyles.title]);
39
+ const iconProps = getWebProps([accordionStyles.icon]);
40
+ const contentProps = getWebProps([
41
+ accordionStyles.content,
42
+ {
43
+ height: isExpanded ? contentHeight : 0,
44
+ overflow: 'hidden' as const,
45
+ }
46
+ ]);
47
+ const contentInnerProps = getWebProps([accordionStyles.contentInner]);
48
+
49
+ useEffect(() => {
50
+ if (isExpanded) {
51
+ setContentHeight(contentInnerRef.current.getBoundingClientRect().height);
52
+ } else {
53
+ setContentHeight(0);
54
+ }
55
+ }, [isExpanded]);
56
+
57
+ return (
58
+ <div
59
+ {...itemProps}
60
+ data-testid={testID}
61
+ >
62
+ <button
63
+ {...headerProps}
64
+ onClick={onToggle}
65
+ disabled={item.disabled}
66
+ aria-expanded={isExpanded}
67
+ aria-disabled={item.disabled}
68
+ >
69
+ <span {...titleProps}>
70
+ {item.title}
71
+ </span>
72
+ <span {...iconProps}>
73
+ <IconSvg
74
+ style={{ width: 12, height: 12 }}
75
+ path={chevronIconPath}
76
+ aria-label="chevron-down"
77
+ />
78
+ </span>
79
+ </button>
80
+
81
+ <div
82
+ {...contentProps}
83
+ aria-hidden={!isExpanded}
84
+ >
85
+ <div ref={contentInnerRef}>
86
+ <div {...contentInnerProps}>
87
+ {item.content}
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ );
93
+ };
94
+
95
+ const Accordion: React.FC<AccordionProps> = ({
96
+ items,
97
+ allowMultiple = false,
98
+ defaultExpanded = [],
99
+ type = 'standard',
100
+ size = 'md',
101
+ style,
102
+ testID,
103
+ }) => {
104
+ const [expandedItems, setExpandedItems] = useState<string[]>(defaultExpanded);
105
+
106
+ // Apply variants
107
+ accordionStyles.useVariants({
108
+ type,
109
+ size,
110
+ });
111
+
112
+ const containerProps = getWebProps([accordionStyles.container, style as any]);
113
+
114
+ const toggleItem = (itemId: string, disabled?: boolean) => {
115
+ if (disabled) return;
116
+
117
+ setExpandedItems((prev) => {
118
+ const isExpanded = prev.includes(itemId);
119
+
120
+ if (allowMultiple) {
121
+ return isExpanded
122
+ ? prev.filter((id) => id !== itemId)
123
+ : [...prev, itemId];
124
+ } else {
125
+ return isExpanded ? [] : [itemId];
126
+ }
127
+ });
128
+ };
129
+
130
+ return (
131
+ <div {...containerProps} data-testid={testID}>
132
+ {items.map((item) => (
133
+ <AccordionItem
134
+ key={item.id}
135
+ item={item}
136
+ type={type}
137
+ isExpanded={expandedItems.includes(item.id)}
138
+ onToggle={() => toggleItem(item.id, item.disabled)}
139
+ size={size}
140
+ testID={`${testID}-item-${item.id}`}
141
+ />
142
+ ))}
143
+ </div>
144
+ );
145
+ };
146
+
147
+ export default Accordion;
@@ -0,0 +1,3 @@
1
+ export { default } from './Accordion.native';
2
+ export { default as Accordion } from './Accordion.native';
3
+ export * from './types';
@@ -0,0 +1,3 @@
1
+ export { default } from './Accordion.web';
2
+ export { default as Accordion } from './Accordion.web';
3
+ export * from './types';
@@ -0,0 +1,3 @@
1
+ export { default } from './Accordion.web';
2
+ export { default as Accordion } from './Accordion.web';
3
+ export * from './types';
@@ -0,0 +1,23 @@
1
+ import { Size } from '@idealyst/theme';
2
+ import type { StyleProp, ViewStyle } from 'react-native';
3
+
4
+ // Component-specific type aliases for future extensibility
5
+ export type AccordionType = 'standard' | 'separated' | 'bordered';
6
+ export type AccordionSizeVariant = Size;
7
+
8
+ export interface AccordionItem {
9
+ id: string;
10
+ title: string;
11
+ content: React.ReactNode;
12
+ disabled?: boolean;
13
+ }
14
+
15
+ export interface AccordionProps {
16
+ items: AccordionItem[];
17
+ allowMultiple?: boolean;
18
+ defaultExpanded?: string[];
19
+ type?: AccordionType;
20
+ size?: AccordionSizeVariant;
21
+ style?: StyleProp<ViewStyle>;
22
+ testID?: string;
23
+ }
@@ -1,35 +1,37 @@
1
- import React from 'react';
1
+ import React, { forwardRef } from 'react';
2
2
  import { ActivityIndicator as RNActivityIndicator, View } from 'react-native';
3
3
  import { ActivityIndicatorProps } from './types';
4
4
  import { activityIndicatorStyles } from './ActivityIndicator.styles';
5
5
 
6
- const ActivityIndicator: React.FC<ActivityIndicatorProps> = ({
6
+ const ActivityIndicator = forwardRef<View, ActivityIndicatorProps>(({
7
7
  animating = true,
8
- size = 'medium',
8
+ size = 'md',
9
9
  intent = 'primary',
10
10
  color,
11
11
  style,
12
12
  testID,
13
13
  hidesWhenStopped = true,
14
- }) => {
14
+ }, ref) => {
15
15
  // Handle numeric size
16
- const sizeVariant = typeof size === 'number' ? 'medium' : size;
16
+ const sizeVariant = typeof size === 'number' ? 'md' : size;
17
17
  const customSize = typeof size === 'number' ? size : undefined;
18
-
18
+
19
19
  // Map our size variants to React Native's size prop
20
- const rnSize = sizeVariant === 'small' ? 'small' : 'large';
21
-
20
+ const rnSize = sizeVariant === 'sm' ? 'small' : 'large';
21
+
22
22
  activityIndicatorStyles.useVariants({
23
23
  size: sizeVariant,
24
- intent,
25
24
  animating,
26
25
  });
27
26
 
27
+ // Call dynamic style with intent variant
28
+ const spinnerStyle = activityIndicatorStyles.spinner({ intent });
29
+
28
30
  // Get the color from styles or use custom color
29
- const indicatorColor = color || activityIndicatorStyles.spinner.color;
31
+ const indicatorColor = color || spinnerStyle.color;
30
32
 
31
33
  return (
32
- <View
34
+ <View
33
35
  style={[
34
36
  activityIndicatorStyles.container,
35
37
  customSize && {
@@ -38,6 +40,7 @@ const ActivityIndicator: React.FC<ActivityIndicatorProps> = ({
38
40
  },
39
41
  style
40
42
  ]}
43
+ ref={ref}
41
44
  testID={testID}
42
45
  >
43
46
  <RNActivityIndicator
@@ -48,6 +51,8 @@ const ActivityIndicator: React.FC<ActivityIndicatorProps> = ({
48
51
  />
49
52
  </View>
50
53
  );
51
- };
54
+ });
55
+
56
+ ActivityIndicator.displayName = 'ActivityIndicator';
52
57
 
53
58
  export default ActivityIndicator;