@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
@@ -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
  */
@@ -1,4 +1,4 @@
1
- import React, { useState, useRef } from 'react';
1
+ import React, { useState, useRef, forwardRef } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
@@ -10,10 +10,14 @@ import {
10
10
  Platform,
11
11
  Animated,
12
12
  } from 'react-native';
13
+ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
13
14
  import { SelectProps, SelectOption } from './types';
14
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';
15
19
 
16
- const Select: React.FC<SelectProps> = ({
20
+ const Select = forwardRef<View, SelectProps>(({
17
21
  options,
18
22
  value,
19
23
  onValueChange,
@@ -22,9 +26,9 @@ const Select: React.FC<SelectProps> = ({
22
26
  error = false,
23
27
  helperText,
24
28
  label,
25
- variant = 'outlined',
29
+ type = 'outlined',
26
30
  intent = 'neutral',
27
- size = 'medium',
31
+ size = 'md',
28
32
  searchable = false,
29
33
  filterOption,
30
34
  presentationMode = 'dropdown',
@@ -32,11 +36,27 @@ const Select: React.FC<SelectProps> = ({
32
36
  style,
33
37
  testID,
34
38
  accessibilityLabel,
35
- }) => {
39
+ }, ref) => {
36
40
  const [isOpen, setIsOpen] = useState(false);
37
41
  const [searchTerm, setSearchTerm] = useState('');
38
42
  const scaleAnim = useRef(new Animated.Value(0)).current;
39
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);
40
60
  const selectedOption = options.find(option => option.value === value);
41
61
 
42
62
  // Filter options based on search term
@@ -51,9 +71,8 @@ const Select: React.FC<SelectProps> = ({
51
71
 
52
72
  // Apply styles with variants
53
73
  selectStyles.useVariants({
54
- variant: variant as any,
74
+ type,
55
75
  size,
56
- intent,
57
76
  disabled,
58
77
  error,
59
78
  focused: isOpen,
@@ -65,8 +84,13 @@ const Select: React.FC<SelectProps> = ({
65
84
  if (Platform.OS === 'ios' && presentationMode === 'actionSheet') {
66
85
  showIOSActionSheet();
67
86
  } else {
87
+ // Measure and position dropdown
88
+ measureAndPosition();
89
+
90
+ // Open the modal (it will be invisible with opacity 0 until positioned)
68
91
  setIsOpen(true);
69
92
  setSearchTerm('');
93
+
70
94
  // Animate dropdown appearance
71
95
  Animated.spring(scaleAnim, {
72
96
  toValue: 1,
@@ -89,7 +113,7 @@ const Select: React.FC<SelectProps> = ({
89
113
  title: label || 'Select an option',
90
114
  message: helperText,
91
115
  },
92
- (buttonIndex) => {
116
+ (buttonIndex: number) => {
93
117
  if (buttonIndex !== cancelButtonIndex && buttonIndex < options.length) {
94
118
  const selectedOption = options[buttonIndex];
95
119
  if (!selectedOption.disabled) {
@@ -115,6 +139,7 @@ const Select: React.FC<SelectProps> = ({
115
139
  friction: 20,
116
140
  }).start(() => {
117
141
  setIsOpen(false);
142
+ resetPosition();
118
143
  setSearchTerm('');
119
144
  });
120
145
  };
@@ -123,32 +148,95 @@ const Select: React.FC<SelectProps> = ({
123
148
  setSearchTerm(text);
124
149
  };
125
150
 
126
- const renderChevron = () => (
127
- <View style={selectStyles.chevron}>
128
- <Text style={{ color: 'currentColor' }}>▼</Text>
129
- </View>
130
- );
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
+ };
131
161
 
132
- const renderDropdown = () => (
133
- <Modal
134
- visible={isOpen}
135
- transparent
136
- animationType="none"
137
- onRequestClose={closeDropdown}
138
- >
139
- <Pressable style={selectStyles.overlay} onPress={closeDropdown}>
140
- <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
141
- <Animated.View
142
- style={[
143
- selectStyles.dropdown,
144
- {
145
- maxHeight,
146
- transform: [{ scale: scaleAnim }],
147
- minWidth: 280,
148
- maxWidth: '90%',
149
- },
150
- ]}
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}
151
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
+ >
152
240
  {searchable && (
153
241
  <View style={selectStyles.searchContainer}>
154
242
  <TextInput
@@ -161,16 +249,11 @@ const Select: React.FC<SelectProps> = ({
161
249
  </View>
162
250
  )}
163
251
 
164
- <ScrollView style={selectStyles.optionsList} showsVerticalScrollIndicator={false}>
165
- {filteredOptions.map((option) => {
166
- const isSelected = option.value === value;
167
-
168
- selectStyles.useVariants({
169
- selected: isSelected,
170
- disabled: option.disabled,
171
- });
172
-
173
- return (
252
+ <ScrollView
253
+ style={selectStyles.optionsList}
254
+ showsVerticalScrollIndicator={true}
255
+ >
256
+ {filteredOptions.map((option) => (
174
257
  <Pressable
175
258
  key={option.value}
176
259
  style={selectStyles.option}
@@ -179,11 +262,7 @@ const Select: React.FC<SelectProps> = ({
179
262
  android_ripple={{ color: 'rgba(0, 0, 0, 0.1)' }}
180
263
  >
181
264
  <View style={selectStyles.optionContent}>
182
- {option.icon && (
183
- <View style={selectStyles.optionIcon}>
184
- {option.icon}
185
- </View>
186
- )}
265
+ {renderOptionIcon(option.icon)}
187
266
  <Text
188
267
  style={[
189
268
  selectStyles.optionText,
@@ -194,8 +273,7 @@ const Select: React.FC<SelectProps> = ({
194
273
  </Text>
195
274
  </View>
196
275
  </Pressable>
197
- );
198
- })}
276
+ ))}
199
277
 
200
278
  {filteredOptions.length === 0 && (
201
279
  <View style={selectStyles.option}>
@@ -205,14 +283,15 @@ const Select: React.FC<SelectProps> = ({
205
283
  </View>
206
284
  )}
207
285
  </ScrollView>
208
- </Animated.View>
209
- </View>
210
- </Pressable>
211
- </Modal>
212
- );
286
+ </Animated.View>
287
+ </BoundedModalContent>
288
+ </Pressable>
289
+ </Modal>
290
+ );
291
+ };
213
292
 
214
293
  return (
215
- <View style={[selectStyles.container, style]} testID={testID}>
294
+ <View ref={ref} style={[selectStyles.container, style]} testID={testID}>
216
295
  {label && (
217
296
  <Text style={selectStyles.label}>
218
297
  {label}
@@ -220,7 +299,8 @@ const Select: React.FC<SelectProps> = ({
220
299
  )}
221
300
 
222
301
  <Pressable
223
- style={selectStyles.trigger}
302
+ ref={mergedTriggerRef}
303
+ style={selectStyles.trigger({ type, intent })}
224
304
  onPress={handleTriggerPress}
225
305
  disabled={disabled}
226
306
  accessibilityLabel={accessibilityLabel || label}
@@ -232,11 +312,7 @@ const Select: React.FC<SelectProps> = ({
232
312
  android_ripple={{ color: 'rgba(0, 0, 0, 0.1)' }}
233
313
  >
234
314
  <View style={selectStyles.triggerContent}>
235
- {selectedOption?.icon && (
236
- <View style={selectStyles.icon}>
237
- {selectedOption.icon}
238
- </View>
239
- )}
315
+ {renderTriggerIcon(selectedOption?.icon)}
240
316
  <Text
241
317
  style={[
242
318
  selectedOption ? selectStyles.triggerText : selectStyles.placeholder,
@@ -257,7 +333,6 @@ const Select: React.FC<SelectProps> = ({
257
333
  <Text
258
334
  style={[
259
335
  selectStyles.helperText,
260
- error && selectStyles.helperText.variants?.error?.true,
261
336
  ]}
262
337
  >
263
338
  {helperText}
@@ -265,6 +340,8 @@ const Select: React.FC<SelectProps> = ({
265
340
  )}
266
341
  </View>
267
342
  );
268
- };
343
+ });
344
+
345
+ Select.displayName = 'Select';
269
346
 
270
347
  export default Select;