@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
@@ -1,7 +1,11 @@
1
+ import { Intent } from '@idealyst/theme';
1
2
  import React from 'react';
2
3
  import { ViewProps } from 'react-native';
3
4
  import { SvgProps } from 'react-native-svg';
4
- import { IntentNames } from '../theme';
5
+
6
+ // Component-specific type aliases for future extensibility
7
+ export type SVGImageIntentVariant = Intent;
8
+ export type SVGImageResizeMode = 'contain' | 'cover' | 'stretch';
5
9
 
6
10
  export interface SVGImageProps extends Omit<ViewProps, 'children'> {
7
11
  source: string | { uri: string } | React.FC<SvgProps>;
@@ -9,6 +13,6 @@ export interface SVGImageProps extends Omit<ViewProps, 'children'> {
9
13
  height?: number | string;
10
14
  size?: number | string;
11
15
  color?: string;
12
- intent?: IntentNames;
13
- resizeMode?: 'contain' | 'cover' | 'stretch';
16
+ intent?: SVGImageIntentVariant;
17
+ resizeMode?: SVGImageResizeMode;
14
18
  }
@@ -1,41 +1,67 @@
1
- import React from 'react';
2
- import { View as RNView, ScrollView as RNScrollView } from 'react-native';
1
+ import React, { forwardRef } from 'react';
2
+ import { View as RNView, ScrollView as RNScrollView, SafeAreaView } from 'react-native';
3
3
  import { ScreenProps } from './types';
4
4
  import { screenStyles } from './Screen.styles';
5
5
 
6
- const Screen: React.FC<ScreenProps> = ({
6
+ const Screen = forwardRef<RNView | RNScrollView, ScreenProps>(({
7
7
  children,
8
8
  background = 'primary',
9
9
  padding = 'md',
10
- safeArea = false,
10
+ safeArea = true,
11
11
  scrollable = true,
12
+ contentInset,
12
13
  style,
13
14
  testID,
14
- }) => {
15
+ }, ref) => {
16
+
15
17
  screenStyles.useVariants({
16
18
  background,
17
19
  padding,
18
20
  safeArea,
19
21
  });
20
22
 
21
- const screenStyleArray = [
22
- screenStyles.screen,
23
- style,
24
- ];
25
-
26
23
  if (scrollable) {
27
- return (
28
- <RNScrollView style={screenStyleArray} testID={testID}>
29
- {children}
30
- </RNScrollView>
24
+ // For ScrollView, flex: 1 goes on the ScrollView style
25
+ // Background and padding go on contentContainerStyle (without flex: 1)
26
+ const scrollViewStyle = [{ flex: 1 }, style];
27
+
28
+ const contentContainerStyleArray = [
29
+ screenStyles.screenContent,
30
+ contentInset ? {
31
+ paddingTop: contentInset.top,
32
+ paddingBottom: contentInset.bottom,
33
+ paddingLeft: contentInset.left,
34
+ paddingRight: contentInset.right,
35
+ } : undefined,
36
+ ];
37
+
38
+ return (
39
+ <RNScrollView
40
+ ref={ref as any}
41
+ style={scrollViewStyle}
42
+ contentContainerStyle={contentContainerStyleArray}
43
+ testID={testID}
44
+ >
45
+ {children}
46
+ </RNScrollView>
31
47
  );
32
48
  }
33
49
 
34
- return (
35
- <RNView style={screenStyleArray} testID={testID}>
50
+ const containerStyle = [screenStyles.screen, style];
51
+
52
+ const view = (
53
+ <RNView ref={ref as any} style={containerStyle} testID={testID}>
36
54
  {children}
37
55
  </RNView>
38
56
  );
39
- };
57
+
58
+ if (safeArea) {
59
+ return <SafeAreaView style={{ flex: 1 }}>{view}</SafeAreaView>;
60
+ }
61
+
62
+ return view;
63
+ });
64
+
65
+ Screen.displayName = 'Screen';
40
66
 
41
67
  export default Screen;
@@ -1,60 +1,64 @@
1
1
  import { StyleSheet } from 'react-native-unistyles';
2
+ import { Theme } from '@idealyst/theme';
2
3
 
3
- export const screenStyles = StyleSheet.create((theme) => ({
4
- screen: {
5
- flex: 1,
6
- // Default background color as fallback
7
- backgroundColor: theme.colors.surface.primary,
8
- variants: {
9
- background: {
10
- primary: {
11
- backgroundColor: theme.colors.surface.primary,
12
- },
13
- secondary: {
14
- backgroundColor: theme.colors.surface.secondary,
15
- },
16
- tertiary: {
17
- backgroundColor: theme.colors.surface.tertiary,
18
- },
19
- inverse: {
20
- backgroundColor: theme.colors.surface.inverse,
21
- },
22
- },
23
- padding: {
24
- none: {
25
- padding: 0,
26
- },
27
- sm: {
28
- padding: theme.spacing.sm,
29
- },
30
- md: {
31
- padding: theme.spacing.md,
32
- },
33
- lg: {
34
- padding: theme.spacing.lg,
35
- },
36
- xl: {
37
- padding: theme.spacing.xl,
38
- },
4
+ function generateBackgroundVariants(theme: Theme) {
5
+ return {
6
+ primary: { backgroundColor: theme.colors.surface.primary },
7
+ secondary: { backgroundColor: theme.colors.surface.secondary },
8
+ tertiary: { backgroundColor: theme.colors.surface.tertiary },
9
+ inverse: { backgroundColor: theme.colors.surface.inverse },
10
+ 'inverse-secondary': { backgroundColor: theme.colors.surface['inverse-secondary'] },
11
+ 'inverse-tertiary': { backgroundColor: theme.colors.surface['inverse-tertiary'] },
12
+ transparent: { backgroundColor: 'transparent' },
13
+ }
14
+ }
15
+
16
+ function generatePaddingVariants() {
17
+ return {
18
+ none: { padding: 0 },
19
+ xs: { padding: 4 },
20
+ sm: { padding: 8 },
21
+ md: { padding: 16 },
22
+ lg: { padding: 24 },
23
+ xl: { padding: 32 },
24
+ }
25
+ }
26
+
27
+ // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel
28
+ // transform on native cannot resolve function calls to extract variant structures.
29
+ // @ts-ignore - TS language server needs restart to pick up theme structure changes
30
+ export const screenStyles = StyleSheet.create((theme: Theme) => {
31
+ return {
32
+ screen: {
33
+ flex: 1,
34
+ backgroundColor: theme.colors.surface.primary,
35
+ variants: {
36
+ background: generateBackgroundVariants(theme),
37
+ padding: generatePaddingVariants(),
38
+ safeArea: {
39
+ true: {},
40
+ false: {},
41
+ }
39
42
  },
40
- safeArea: {
41
- true: {
42
- paddingTop: theme.spacing.lg, // Safe area top
43
- paddingBottom: theme.spacing.lg, // Safe area bottom
44
- },
45
- false: {
46
- paddingTop: 0,
47
- paddingBottom: 0,
48
- },
43
+ _web: {
44
+ overflow: 'auto',
45
+ display: 'flex',
46
+ flexDirection: 'column',
47
+ minHeight: '100%',
48
+ boxSizing: 'border-box',
49
49
  },
50
50
  },
51
- // Web-specific styles
52
- _web: {
53
- overflow: 'auto',
54
- display: 'flex',
55
- flexDirection: 'column',
56
- minHeight: '100%',
57
- boxSizing: 'border-box',
51
+ // Content style for ScrollView - no flex: 1 so content can grow
52
+ screenContent: {
53
+ backgroundColor: theme.colors.surface.primary,
54
+ variants: {
55
+ background: generateBackgroundVariants(theme),
56
+ padding: generatePaddingVariants(),
57
+ safeArea: {
58
+ true: {},
59
+ false: {},
60
+ }
61
+ },
58
62
  },
59
- },
60
- }));
63
+ };
64
+ });
@@ -1,16 +1,17 @@
1
- import React from 'react';
1
+ import React, { forwardRef } from 'react';
2
2
  import { getWebProps } from 'react-native-unistyles/web';
3
3
  import { ScreenProps } from './types';
4
4
  import { screenStyles } from './Screen.styles';
5
+ import useMergeRefs from '../hooks/useMergeRefs';
5
6
 
6
- const Screen: React.FC<ScreenProps> = ({
7
+ const Screen = forwardRef<HTMLDivElement, ScreenProps>(({
7
8
  children,
8
9
  background = 'primary',
9
10
  padding = 'md',
10
11
  safeArea = false,
11
12
  style,
12
13
  testID,
13
- }) => {
14
+ }, ref) => {
14
15
  screenStyles.useVariants({
15
16
  background,
16
17
  padding,
@@ -18,16 +19,21 @@ const Screen: React.FC<ScreenProps> = ({
18
19
  });
19
20
 
20
21
  // Use getWebProps to generate className and ref for web
21
- const webProps = getWebProps([screenStyles.screen, style]);
22
+ const webProps = getWebProps([screenStyles.screen, style as any]);
23
+
24
+ const mergedRef = useMergeRefs(ref, webProps.ref);
22
25
 
23
26
  return (
24
27
  <div
25
28
  {...webProps}
29
+ ref={mergedRef}
26
30
  data-testid={testID}
27
31
  >
28
32
  {children}
29
33
  </div>
30
34
  );
31
- };
35
+ });
36
+
37
+ Screen.displayName = 'Screen';
32
38
 
33
39
  export default Screen;
@@ -1,2 +1,5 @@
1
- export { default } from './Screen.web';
2
- export * from './types';
1
+ import ScreenComponent from './Screen.web';
2
+
3
+ export default ScreenComponent;
4
+ export { ScreenComponent as Screen };
5
+ export * from './types';
@@ -1,2 +1,5 @@
1
- export { default } from './Screen.web';
2
- export * from './types';
1
+ import ScreenComponent from './Screen.web';
2
+
3
+ export default ScreenComponent;
4
+ export { ScreenComponent as Screen };
5
+ export * from './types';
@@ -1,31 +1,45 @@
1
- import { ReactNode } from 'react';
1
+ import type { ReactNode } from 'react';
2
+ import type { StyleProp, ViewStyle } from 'react-native';
3
+ import { Size, Surface } from '@idealyst/theme';
2
4
 
3
5
  export interface ScreenProps {
4
6
  /**
5
7
  * The content to display inside the screen
6
8
  */
7
9
  children?: ReactNode;
8
-
10
+
9
11
  /**
10
12
  * Background variant - controls the background color
11
13
  */
12
- background?: 'primary' | 'secondary' | 'tertiary' | 'inverse';
13
-
14
+ background?: Surface | 'transparent';
15
+
14
16
  /**
15
17
  * Screen padding variant
16
18
  */
17
- padding?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
18
-
19
+ padding?: Size | 'none';
20
+
19
21
  /**
20
22
  * Safe area padding for mobile devices
21
23
  */
22
24
  safeArea?: boolean;
23
-
25
+
26
+ /**
27
+ * Content inset padding for scrollable content (mobile only)
28
+ * Adds padding to the scroll view's content container
29
+ * Useful for adding safe area insets or additional spacing
30
+ */
31
+ contentInset?: {
32
+ top?: number;
33
+ bottom?: number;
34
+ left?: number;
35
+ right?: number;
36
+ };
37
+
24
38
  /**
25
39
  * Additional styles (platform-specific)
26
40
  */
27
- style?: any;
28
-
41
+ style?: StyleProp<ViewStyle>;
42
+
29
43
  /**
30
44
  * Test ID for testing
31
45
  */
@@ -0,0 +1,347 @@
1
+ import React, { useState, useRef, forwardRef } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ Pressable,
6
+ Modal,
7
+ ScrollView,
8
+ TextInput,
9
+ ActionSheetIOS,
10
+ Platform,
11
+ Animated,
12
+ } from 'react-native';
13
+ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
14
+ import { SelectProps, SelectOption } from './types';
15
+ import { selectStyles } from './Select.styles';
16
+ import { BoundedModalContent } from '../internal/BoundedModalContent.native';
17
+ import { useSmartPosition } from '../hooks/useSmartPosition.native';
18
+ import useMergeRefs from '../hooks/useMergeRefs';
19
+
20
+ const Select = forwardRef<View, SelectProps>(({
21
+ options,
22
+ value,
23
+ onValueChange,
24
+ placeholder = 'Select an option',
25
+ disabled = false,
26
+ error = false,
27
+ helperText,
28
+ label,
29
+ type = 'outlined',
30
+ intent = 'neutral',
31
+ size = 'md',
32
+ searchable = false,
33
+ filterOption,
34
+ presentationMode = 'dropdown',
35
+ maxHeight = 240,
36
+ style,
37
+ testID,
38
+ accessibilityLabel,
39
+ }, ref) => {
40
+ const [isOpen, setIsOpen] = useState(false);
41
+ const [searchTerm, setSearchTerm] = useState('');
42
+ const scaleAnim = useRef(new Animated.Value(0)).current;
43
+
44
+ const {
45
+ position: dropdownPosition,
46
+ size: dropdownSize,
47
+ isPositioned,
48
+ anchorRef: triggerRef,
49
+ measureAndPosition,
50
+ handleLayout: handleDropdownLayout,
51
+ reset: resetPosition,
52
+ } = useSmartPosition({
53
+ placement: 'bottom-start',
54
+ offset: 4,
55
+ maxHeight,
56
+ matchWidth: true,
57
+ });
58
+
59
+ const mergedTriggerRef = useMergeRefs(ref, triggerRef);
60
+ const selectedOption = options.find(option => option.value === value);
61
+
62
+ // Filter options based on search term
63
+ const filteredOptions = searchable && searchTerm
64
+ ? options.filter(option => {
65
+ if (filterOption) {
66
+ return filterOption(option, searchTerm);
67
+ }
68
+ return option.label.toLowerCase().includes(searchTerm.toLowerCase());
69
+ })
70
+ : options;
71
+
72
+ // Apply styles with variants
73
+ selectStyles.useVariants({
74
+ type,
75
+ size,
76
+ disabled,
77
+ error,
78
+ focused: isOpen,
79
+ });
80
+
81
+ const handleTriggerPress = () => {
82
+ if (disabled) return;
83
+
84
+ if (Platform.OS === 'ios' && presentationMode === 'actionSheet') {
85
+ showIOSActionSheet();
86
+ } else {
87
+ // Measure and position dropdown
88
+ measureAndPosition();
89
+
90
+ // Open the modal (it will be invisible with opacity 0 until positioned)
91
+ setIsOpen(true);
92
+ setSearchTerm('');
93
+
94
+ // Animate dropdown appearance
95
+ Animated.spring(scaleAnim, {
96
+ toValue: 1,
97
+ useNativeDriver: true,
98
+ tension: 300,
99
+ friction: 20,
100
+ }).start();
101
+ }
102
+ };
103
+
104
+ const showIOSActionSheet = () => {
105
+ const actionOptions = options.map(option => option.label);
106
+ const cancelButtonIndex = actionOptions.length;
107
+ actionOptions.push('Cancel');
108
+
109
+ ActionSheetIOS.showActionSheetWithOptions(
110
+ {
111
+ options: actionOptions,
112
+ cancelButtonIndex,
113
+ title: label || 'Select an option',
114
+ message: helperText,
115
+ },
116
+ (buttonIndex: number) => {
117
+ if (buttonIndex !== cancelButtonIndex && buttonIndex < options.length) {
118
+ const selectedOption = options[buttonIndex];
119
+ if (!selectedOption.disabled) {
120
+ onValueChange?.(selectedOption.value);
121
+ }
122
+ }
123
+ }
124
+ );
125
+ };
126
+
127
+ const handleOptionSelect = (option: SelectOption) => {
128
+ if (!option.disabled) {
129
+ onValueChange?.(option.value);
130
+ closeDropdown();
131
+ }
132
+ };
133
+
134
+ const closeDropdown = () => {
135
+ Animated.spring(scaleAnim, {
136
+ toValue: 0,
137
+ useNativeDriver: true,
138
+ tension: 300,
139
+ friction: 20,
140
+ }).start(() => {
141
+ setIsOpen(false);
142
+ resetPosition();
143
+ setSearchTerm('');
144
+ });
145
+ };
146
+
147
+ const handleSearchChange = (text: string) => {
148
+ setSearchTerm(text);
149
+ };
150
+
151
+ const renderChevron = () => {
152
+ return (
153
+ <View style={selectStyles.chevron}>
154
+ <MaterialCommunityIcons
155
+ name="chevron-down"
156
+ style={selectStyles.chevron}
157
+ />
158
+ </View>
159
+ );
160
+ };
161
+
162
+ const renderTriggerIcon = (icon: any) => {
163
+ if (!icon) return null;
164
+
165
+ // If it's a string, render as MaterialCommunityIcons
166
+ if (typeof icon === 'string') {
167
+ const iconStyle = selectStyles.icon;
168
+ return (
169
+ <View style={iconStyle}>
170
+ <MaterialCommunityIcons
171
+ style={iconStyle}
172
+ name={icon}
173
+ />
174
+ </View>
175
+ );
176
+ }
177
+
178
+ // Otherwise render the React element directly
179
+ return <View style={selectStyles.icon}>{icon}</View>;
180
+ };
181
+
182
+ const renderOptionIcon = (icon: any) => {
183
+ if (!icon) return null;
184
+
185
+ // If it's a string, render as MaterialCommunityIcons
186
+ if (typeof icon === 'string') {
187
+ const iconStyle = selectStyles.optionIcon;
188
+ return (
189
+ <View style={iconStyle}>
190
+ <MaterialCommunityIcons
191
+ style={iconStyle}
192
+ name={icon}
193
+ />
194
+ </View>
195
+ );
196
+ }
197
+
198
+ // Otherwise render the React element directly
199
+ return <View style={selectStyles.optionIcon}>{icon}</View>;
200
+ };
201
+
202
+ const renderDropdown = () => {
203
+ if (!isOpen) return null;
204
+
205
+ // Show dropdown only after it has been measured AND positioned
206
+ const isMeasured = dropdownSize.height > 0;
207
+ const shouldShow = isMeasured && isPositioned;
208
+
209
+ return (
210
+ <Modal
211
+ visible={true}
212
+ transparent
213
+ animationType="none"
214
+ onRequestClose={closeDropdown}
215
+ >
216
+ <Pressable
217
+ style={selectStyles.overlay}
218
+ onPress={closeDropdown}
219
+ >
220
+ <BoundedModalContent
221
+ top={dropdownPosition.top}
222
+ left={dropdownPosition.left}
223
+ width={dropdownPosition.width}
224
+ maxHeight={maxHeight}
225
+ >
226
+ <Animated.View
227
+ style={[
228
+ selectStyles.dropdown,
229
+ {
230
+ position: 'relative', // Override absolute positioning from styles
231
+ top: 0, // Override top: '100%' from styles
232
+ left: 0, // Override left from styles
233
+ right: undefined, // Remove right constraint
234
+ transform: [{ scale: scaleAnim }],
235
+ opacity: shouldShow ? 1 : 0, // Hide until measured AND positioned
236
+ }
237
+ ]}
238
+ onLayout={handleDropdownLayout}
239
+ >
240
+ {searchable && (
241
+ <View style={selectStyles.searchContainer}>
242
+ <TextInput
243
+ style={selectStyles.searchInput}
244
+ placeholder="Search options..."
245
+ value={searchTerm}
246
+ onChangeText={handleSearchChange}
247
+ autoFocus
248
+ />
249
+ </View>
250
+ )}
251
+
252
+ <ScrollView
253
+ style={selectStyles.optionsList}
254
+ showsVerticalScrollIndicator={true}
255
+ >
256
+ {filteredOptions.map((option) => (
257
+ <Pressable
258
+ key={option.value}
259
+ style={selectStyles.option}
260
+ onPress={() => handleOptionSelect(option)}
261
+ disabled={option.disabled}
262
+ android_ripple={{ color: 'rgba(0, 0, 0, 0.1)' }}
263
+ >
264
+ <View style={selectStyles.optionContent}>
265
+ {renderOptionIcon(option.icon)}
266
+ <Text
267
+ style={[
268
+ selectStyles.optionText,
269
+ option.disabled && selectStyles.optionTextDisabled,
270
+ ]}
271
+ >
272
+ {option.label}
273
+ </Text>
274
+ </View>
275
+ </Pressable>
276
+ ))}
277
+
278
+ {filteredOptions.length === 0 && (
279
+ <View style={selectStyles.option}>
280
+ <Text style={selectStyles.optionText}>
281
+ No options found
282
+ </Text>
283
+ </View>
284
+ )}
285
+ </ScrollView>
286
+ </Animated.View>
287
+ </BoundedModalContent>
288
+ </Pressable>
289
+ </Modal>
290
+ );
291
+ };
292
+
293
+ return (
294
+ <View ref={ref} style={[selectStyles.container, style]} testID={testID}>
295
+ {label && (
296
+ <Text style={selectStyles.label}>
297
+ {label}
298
+ </Text>
299
+ )}
300
+
301
+ <Pressable
302
+ ref={mergedTriggerRef}
303
+ style={selectStyles.trigger({ type, intent })}
304
+ onPress={handleTriggerPress}
305
+ disabled={disabled}
306
+ accessibilityLabel={accessibilityLabel || label}
307
+ accessibilityRole="button"
308
+ accessibilityState={{
309
+ expanded: isOpen,
310
+ disabled,
311
+ }}
312
+ android_ripple={{ color: 'rgba(0, 0, 0, 0.1)' }}
313
+ >
314
+ <View style={selectStyles.triggerContent}>
315
+ {renderTriggerIcon(selectedOption?.icon)}
316
+ <Text
317
+ style={[
318
+ selectedOption ? selectStyles.triggerText : selectStyles.placeholder,
319
+ ]}
320
+ numberOfLines={1}
321
+ >
322
+ {selectedOption ? selectedOption.label : placeholder}
323
+ </Text>
324
+ </View>
325
+
326
+ {renderChevron()}
327
+ </Pressable>
328
+
329
+ {/* Only render dropdown modal if not using iOS ActionSheet */}
330
+ {!(Platform.OS === 'ios' && presentationMode === 'actionSheet') && renderDropdown()}
331
+
332
+ {helperText && (
333
+ <Text
334
+ style={[
335
+ selectStyles.helperText,
336
+ ]}
337
+ >
338
+ {helperText}
339
+ </Text>
340
+ )}
341
+ </View>
342
+ );
343
+ });
344
+
345
+ Select.displayName = 'Select';
346
+
347
+ export default Select;