@olympusoss/canvas 4.0.0 → 5.0.0

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 (297) hide show
  1. package/README.md +108 -0
  2. package/package.json +14 -3
  3. package/src/atoms/avatar/avatar.md +185 -0
  4. package/src/atoms/avatar/avatar.styles.ts +48 -0
  5. package/src/atoms/avatar/avatar.tsx +99 -0
  6. package/src/atoms/badge/badge.md +237 -0
  7. package/src/atoms/badge/badge.styles.ts +79 -0
  8. package/src/atoms/badge/badge.tsx +86 -0
  9. package/src/atoms/breadcrumb/breadcrumb.md +233 -0
  10. package/src/atoms/breadcrumb/breadcrumb.styles.ts +40 -0
  11. package/src/atoms/breadcrumb/breadcrumb.tsx +130 -0
  12. package/src/atoms/button/button.android.tsx +6 -0
  13. package/src/atoms/button/button.ios.tsx +6 -0
  14. package/src/atoms/button/button.md +184 -0
  15. package/src/atoms/button/button.shared.tsx +79 -0
  16. package/src/atoms/button/button.styles.ts +152 -0
  17. package/src/atoms/button/button.tsx +6 -0
  18. package/src/atoms/button-group/button-group.android.tsx +6 -0
  19. package/src/atoms/button-group/button-group.ios.tsx +6 -0
  20. package/src/atoms/button-group/button-group.md +120 -0
  21. package/src/atoms/button-group/button-group.shared.tsx +398 -0
  22. package/src/atoms/button-group/button-group.styles.ts +483 -0
  23. package/src/atoms/button-group/button-group.tsx +6 -0
  24. package/src/atoms/checkbox/checkbox.android.tsx +6 -0
  25. package/src/atoms/checkbox/checkbox.ios.tsx +6 -0
  26. package/src/atoms/checkbox/checkbox.md +150 -0
  27. package/src/atoms/checkbox/checkbox.shared.tsx +103 -0
  28. package/src/atoms/checkbox/checkbox.styles.ts +106 -0
  29. package/src/atoms/checkbox/checkbox.tsx +6 -0
  30. package/src/atoms/combobox/combobox.android.tsx +6 -0
  31. package/src/atoms/combobox/combobox.ios.tsx +6 -0
  32. package/src/atoms/combobox/combobox.md +213 -0
  33. package/src/atoms/combobox/combobox.shared.tsx +160 -0
  34. package/src/atoms/combobox/combobox.styles.ts +270 -0
  35. package/src/atoms/combobox/combobox.tsx +6 -0
  36. package/src/atoms/divider/divider.md +140 -0
  37. package/src/atoms/divider/divider.styles.ts +35 -0
  38. package/src/atoms/divider/divider.tsx +67 -0
  39. package/src/atoms/dropdown/dropdown.android.tsx +6 -0
  40. package/src/atoms/dropdown/dropdown.ios.tsx +6 -0
  41. package/src/atoms/dropdown/dropdown.md +221 -0
  42. package/src/atoms/dropdown/dropdown.shared.tsx +190 -0
  43. package/src/atoms/dropdown/dropdown.styles.ts +233 -0
  44. package/src/atoms/dropdown/dropdown.tsx +6 -0
  45. package/src/atoms/icon/icon.md +131 -0
  46. package/src/atoms/icon/icon.styles.ts +30 -0
  47. package/src/atoms/icon/icon.tsx +328 -0
  48. package/src/atoms/index.ts +24 -0
  49. package/src/atoms/input/input.android.tsx +6 -0
  50. package/src/atoms/input/input.ios.tsx +6 -0
  51. package/src/atoms/input/input.md +118 -0
  52. package/src/atoms/input/input.shared.tsx +203 -0
  53. package/src/atoms/input/input.styles.ts +286 -0
  54. package/src/atoms/input/input.tsx +6 -0
  55. package/src/atoms/kbd/kbd.md +91 -0
  56. package/src/atoms/kbd/kbd.styles.ts +33 -0
  57. package/src/atoms/kbd/kbd.tsx +27 -0
  58. package/src/atoms/listbox/listbox.md +177 -0
  59. package/src/atoms/listbox/listbox.styles.ts +60 -0
  60. package/src/atoms/listbox/listbox.tsx +113 -0
  61. package/src/atoms/pagination/pagination.android.tsx +6 -0
  62. package/src/atoms/pagination/pagination.ios.tsx +6 -0
  63. package/src/atoms/pagination/pagination.md +133 -0
  64. package/src/atoms/pagination/pagination.shared.tsx +289 -0
  65. package/src/atoms/pagination/pagination.styles.ts +245 -0
  66. package/src/atoms/pagination/pagination.tsx +6 -0
  67. package/src/atoms/popover/popover.android.tsx +8 -0
  68. package/src/atoms/popover/popover.ios.tsx +6 -0
  69. package/src/atoms/popover/popover.md +87 -0
  70. package/src/atoms/popover/popover.shared.tsx +124 -0
  71. package/src/atoms/popover/popover.styles.ts +144 -0
  72. package/src/atoms/popover/popover.tsx +6 -0
  73. package/src/atoms/radio/radio.android.tsx +6 -0
  74. package/src/atoms/radio/radio.ios.tsx +6 -0
  75. package/src/atoms/radio/radio.md +173 -0
  76. package/src/atoms/radio/radio.shared.tsx +98 -0
  77. package/src/atoms/radio/radio.styles.ts +109 -0
  78. package/src/atoms/radio/radio.tsx +6 -0
  79. package/src/atoms/select/select.android.tsx +6 -0
  80. package/src/atoms/select/select.ios.tsx +6 -0
  81. package/src/atoms/select/select.md +156 -0
  82. package/src/atoms/select/select.shared.tsx +143 -0
  83. package/src/atoms/select/select.styles.ts +310 -0
  84. package/src/atoms/select/select.tsx +6 -0
  85. package/src/atoms/skeleton/skeleton.md +135 -0
  86. package/src/atoms/skeleton/skeleton.styles.ts +117 -0
  87. package/src/atoms/skeleton/skeleton.tsx +145 -0
  88. package/src/atoms/spinner/spinner.android.tsx +7 -0
  89. package/src/atoms/spinner/spinner.ios.tsx +7 -0
  90. package/src/atoms/spinner/spinner.md +94 -0
  91. package/src/atoms/spinner/spinner.shared.tsx +92 -0
  92. package/src/atoms/spinner/spinner.styles.tsx +115 -0
  93. package/src/atoms/spinner/spinner.tsx +7 -0
  94. package/src/atoms/switch/switch.android.tsx +6 -0
  95. package/src/atoms/switch/switch.ios.tsx +6 -0
  96. package/src/atoms/switch/switch.md +91 -0
  97. package/src/atoms/switch/switch.shared.tsx +97 -0
  98. package/src/atoms/switch/switch.styles.ts +79 -0
  99. package/src/atoms/switch/switch.tsx +6 -0
  100. package/src/atoms/textarea/textarea.android.tsx +6 -0
  101. package/src/atoms/textarea/textarea.ios.tsx +6 -0
  102. package/src/atoms/textarea/textarea.md +140 -0
  103. package/src/atoms/textarea/textarea.shared.tsx +74 -0
  104. package/src/atoms/textarea/textarea.styles.ts +116 -0
  105. package/src/atoms/textarea/textarea.tsx +6 -0
  106. package/src/atoms/tooltip/tooltip.android.tsx +6 -0
  107. package/src/atoms/tooltip/tooltip.ios.tsx +7 -0
  108. package/src/atoms/tooltip/tooltip.md +122 -0
  109. package/src/atoms/tooltip/tooltip.shared.tsx +113 -0
  110. package/src/atoms/tooltip/tooltip.styles.ts +113 -0
  111. package/src/atoms/tooltip/tooltip.tsx +6 -0
  112. package/src/atoms/typography/typography.md +330 -0
  113. package/src/atoms/typography/typography.styles.ts +95 -0
  114. package/src/atoms/typography/typography.tsx +76 -0
  115. package/src/index.ts +12 -2
  116. package/src/molecules/action-panels/action-panels.md +133 -0
  117. package/src/molecules/action-panels/action-panels.styles.ts +39 -0
  118. package/src/molecules/action-panels/action-panels.tsx +113 -0
  119. package/src/molecules/alert/alert.md +119 -0
  120. package/src/molecules/alert/alert.styles.ts +88 -0
  121. package/src/molecules/alert/alert.tsx +74 -0
  122. package/src/molecules/alert-dialog/alert-dialog.android.tsx +6 -0
  123. package/src/molecules/alert-dialog/alert-dialog.ios.tsx +6 -0
  124. package/src/molecules/alert-dialog/alert-dialog.md +177 -0
  125. package/src/molecules/alert-dialog/alert-dialog.shared.tsx +187 -0
  126. package/src/molecules/alert-dialog/alert-dialog.styles.ts +248 -0
  127. package/src/molecules/alert-dialog/alert-dialog.tsx +6 -0
  128. package/src/molecules/card/card.md +190 -0
  129. package/src/molecules/card/card.styles.ts +67 -0
  130. package/src/molecules/card/card.tsx +176 -0
  131. package/src/molecules/code-block/code-block.md +159 -0
  132. package/src/molecules/code-block/code-block.styles.ts +167 -0
  133. package/src/molecules/code-block/code-block.tsx +176 -0
  134. package/src/molecules/description-lists/description-lists.md +129 -0
  135. package/src/molecules/description-lists/description-lists.styles.ts +102 -0
  136. package/src/molecules/description-lists/description-lists.tsx +133 -0
  137. package/src/molecules/empty-state/empty-state.md +218 -0
  138. package/src/molecules/empty-state/empty-state.styles.ts +63 -0
  139. package/src/molecules/empty-state/empty-state.tsx +77 -0
  140. package/src/molecules/feeds/feeds.md +102 -0
  141. package/src/molecules/feeds/feeds.styles.ts +120 -0
  142. package/src/molecules/feeds/feeds.tsx +167 -0
  143. package/src/molecules/field/field.md +117 -0
  144. package/src/molecules/field/field.styles.ts +85 -0
  145. package/src/molecules/field/field.tsx +175 -0
  146. package/src/molecules/fieldset/fieldset.md +141 -0
  147. package/src/molecules/fieldset/fieldset.styles.ts +79 -0
  148. package/src/molecules/fieldset/fieldset.tsx +182 -0
  149. package/src/molecules/form/form.md +137 -0
  150. package/src/molecules/form/form.styles.ts +39 -0
  151. package/src/molecules/form/form.tsx +246 -0
  152. package/src/molecules/grid-lists/grid-lists.md +114 -0
  153. package/src/molecules/grid-lists/grid-lists.styles.ts +79 -0
  154. package/src/molecules/grid-lists/grid-lists.tsx +157 -0
  155. package/src/molecules/index.ts +16 -0
  156. package/src/molecules/media-objects/media-objects.md +87 -0
  157. package/src/molecules/media-objects/media-objects.styles.ts +94 -0
  158. package/src/molecules/media-objects/media-objects.tsx +128 -0
  159. package/src/molecules/stacked-lists/stacked-lists.md +116 -0
  160. package/src/molecules/stacked-lists/stacked-lists.styles.ts +111 -0
  161. package/src/molecules/stacked-lists/stacked-lists.tsx +195 -0
  162. package/src/molecules/stats/stats.md +166 -0
  163. package/src/molecules/stats/stats.styles.ts +91 -0
  164. package/src/molecules/stats/stats.tsx +88 -0
  165. package/src/organisms/calendar/calendar.android.tsx +6 -0
  166. package/src/organisms/calendar/calendar.ios.tsx +6 -0
  167. package/src/organisms/calendar/calendar.md +114 -0
  168. package/src/organisms/calendar/calendar.shared.tsx +146 -0
  169. package/src/organisms/calendar/calendar.styles.ts +315 -0
  170. package/src/organisms/calendar/calendar.tsx +6 -0
  171. package/src/organisms/charts/charts.md +326 -0
  172. package/src/organisms/charts/charts.styles.ts +135 -0
  173. package/src/organisms/charts/charts.tsx +124 -0
  174. package/src/organisms/command/command.md +117 -0
  175. package/src/organisms/command/command.styles.ts +179 -0
  176. package/src/organisms/command/command.tsx +164 -0
  177. package/src/organisms/data-table/data-table.md +182 -0
  178. package/src/organisms/data-table/data-table.styles.ts +103 -0
  179. package/src/organisms/data-table/data-table.tsx +105 -0
  180. package/src/organisms/dialog/dialog.android.tsx +6 -0
  181. package/src/organisms/dialog/dialog.ios.tsx +6 -0
  182. package/src/organisms/dialog/dialog.md +271 -0
  183. package/src/organisms/dialog/dialog.shared.tsx +230 -0
  184. package/src/organisms/dialog/dialog.styles.ts +272 -0
  185. package/src/organisms/dialog/dialog.tsx +6 -0
  186. package/src/organisms/filter-panel/filter-panel.md +116 -0
  187. package/src/organisms/filter-panel/filter-panel.styles.ts +83 -0
  188. package/src/organisms/filter-panel/filter-panel.tsx +91 -0
  189. package/src/organisms/index.ts +13 -0
  190. package/src/organisms/navbars/navbars.android.tsx +6 -0
  191. package/src/organisms/navbars/navbars.ios.tsx +6 -0
  192. package/src/organisms/navbars/navbars.md +144 -0
  193. package/src/organisms/navbars/navbars.shared.tsx +137 -0
  194. package/src/organisms/navbars/navbars.styles.ts +251 -0
  195. package/src/organisms/navbars/navbars.tsx +6 -0
  196. package/src/organisms/overlays/overlays.android.tsx +6 -0
  197. package/src/organisms/overlays/overlays.ios.tsx +6 -0
  198. package/src/organisms/overlays/overlays.md +123 -0
  199. package/src/organisms/overlays/overlays.shared.tsx +175 -0
  200. package/src/organisms/overlays/overlays.styles.ts +309 -0
  201. package/src/organisms/overlays/overlays.tsx +6 -0
  202. package/src/organisms/row-menu/row-menu.android.tsx +6 -0
  203. package/src/organisms/row-menu/row-menu.ios.tsx +6 -0
  204. package/src/organisms/row-menu/row-menu.md +102 -0
  205. package/src/organisms/row-menu/row-menu.shared.tsx +105 -0
  206. package/src/organisms/row-menu/row-menu.styles.ts +262 -0
  207. package/src/organisms/row-menu/row-menu.tsx +6 -0
  208. package/src/organisms/sidebar/sidebar.android.tsx +6 -0
  209. package/src/organisms/sidebar/sidebar.ios.tsx +6 -0
  210. package/src/organisms/sidebar/sidebar.md +188 -0
  211. package/src/organisms/sidebar/sidebar.shared.tsx +167 -0
  212. package/src/organisms/sidebar/sidebar.styles.ts +262 -0
  213. package/src/organisms/sidebar/sidebar.tsx +6 -0
  214. package/src/organisms/stepper/stepper.android.tsx +6 -0
  215. package/src/organisms/stepper/stepper.ios.tsx +6 -0
  216. package/src/organisms/stepper/stepper.md +150 -0
  217. package/src/organisms/stepper/stepper.shared.tsx +158 -0
  218. package/src/organisms/stepper/stepper.styles.ts +280 -0
  219. package/src/organisms/stepper/stepper.tsx +6 -0
  220. package/src/organisms/tabs/tabs.android.tsx +6 -0
  221. package/src/organisms/tabs/tabs.ios.tsx +6 -0
  222. package/src/organisms/tabs/tabs.md +127 -0
  223. package/src/organisms/tabs/tabs.shared.tsx +281 -0
  224. package/src/organisms/tabs/tabs.styles.ts +398 -0
  225. package/src/organisms/tabs/tabs.tsx +6 -0
  226. package/src/style/color.ts +17 -0
  227. package/src/style/index.ts +14 -0
  228. package/src/style/primitives.ts +26 -0
  229. package/src/style/responsive.ts +45 -0
  230. package/src/style/shadow.ts +21 -0
  231. package/src/style/theme.tsx +56 -0
  232. package/src/style/tokens.ts +487 -0
  233. package/src/theme.ts +21 -0
  234. package/styles/canvas.css +128 -67
  235. package/tsconfig.json +4 -2
  236. package/src/cn.ts +0 -3
  237. package/styles/base.css +0 -17
  238. package/styles/components/alert.css +0 -66
  239. package/styles/components/app-shell.css +0 -46
  240. package/styles/components/avatar.css +0 -15
  241. package/styles/components/badge.css +0 -83
  242. package/styles/components/breadcrumb.css +0 -35
  243. package/styles/components/button-group.css +0 -23
  244. package/styles/components/button.css +0 -107
  245. package/styles/components/calendar.css +0 -73
  246. package/styles/components/card.css +0 -58
  247. package/styles/components/checkbox.css +0 -55
  248. package/styles/components/code-block.css +0 -18
  249. package/styles/components/combobox.css +0 -75
  250. package/styles/components/command.css +0 -94
  251. package/styles/components/data-table.css +0 -142
  252. package/styles/components/dialog.css +0 -72
  253. package/styles/components/dropdown.css +0 -54
  254. package/styles/components/empty-state.css +0 -17
  255. package/styles/components/field.css +0 -27
  256. package/styles/components/filter-panel.css +0 -58
  257. package/styles/components/form.css +0 -27
  258. package/styles/components/icon.css +0 -8
  259. package/styles/components/input-group.css +0 -45
  260. package/styles/components/input.css +0 -56
  261. package/styles/components/kbd.css +0 -15
  262. package/styles/components/page-header.css +0 -52
  263. package/styles/components/pagination.css +0 -48
  264. package/styles/components/popover.css +0 -14
  265. package/styles/components/radio.css +0 -28
  266. package/styles/components/row-menu.css +0 -69
  267. package/styles/components/section-card.css +0 -49
  268. package/styles/components/select.css +0 -57
  269. package/styles/components/separator.css +0 -32
  270. package/styles/components/sheet.css +0 -70
  271. package/styles/components/sidebar.css +0 -146
  272. package/styles/components/skeleton.css +0 -32
  273. package/styles/components/spinner.css +0 -26
  274. package/styles/components/stat-card.css +0 -71
  275. package/styles/components/stepper.css +0 -63
  276. package/styles/components/switch.css +0 -45
  277. package/styles/components/tabs.css +0 -40
  278. package/styles/components/textarea.css +0 -31
  279. package/styles/components/toast.css +0 -95
  280. package/styles/components/tooltip.css +0 -53
  281. package/styles/components/topbar.css +0 -24
  282. package/styles/components/typography.css +0 -105
  283. package/styles/patterns/backdrops.css +0 -35
  284. package/styles/patterns/density.css +0 -66
  285. package/styles/patterns/focus.css +0 -38
  286. package/styles/patterns/glass.css +0 -85
  287. package/styles/patterns/high-contrast.css +0 -70
  288. package/styles/patterns/reduced-motion.css +0 -12
  289. package/styles/patterns/scrollbar.css +0 -10
  290. package/styles/reset.css +0 -89
  291. package/styles/tokens/colors.css +0 -106
  292. package/styles/tokens/motion.css +0 -33
  293. package/styles/tokens/radius.css +0 -10
  294. package/styles/tokens/shadows.css +0 -35
  295. package/styles/tokens/spacing.css +0 -19
  296. package/styles/tokens/typography.css +0 -6
  297. package/styles/tokens/z-index.css +0 -12
@@ -0,0 +1,131 @@
1
+ # Icons
2
+
3
+ Lucide-style outline. 1.75 stroke width, rounded caps. Inherits currentColor, so the same icon adapts to any context: set the color on the parent.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ <Icon shield size={24} />
9
+ ```
10
+
11
+ ## Variants
12
+
13
+ ### View - set
14
+
15
+ ```tsx
16
+ <Icon set />
17
+ ```
18
+
19
+ ### Color - primary
20
+
21
+ ```tsx
22
+ <Icon shield size={24} primary />
23
+ ```
24
+
25
+ ### Color - destructive
26
+
27
+ ```tsx
28
+ <Icon shield size={24} destructive />
29
+ ```
30
+
31
+ ### Color - muted
32
+
33
+ ```tsx
34
+ <Icon shield size={24} muted />
35
+ ```
36
+
37
+ ## Do & Don't
38
+
39
+ ### Stroke coherence
40
+
41
+ **Do** — One outline style at 1.75 stroke across the whole set.
42
+
43
+ ```tsx
44
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 20 }}>
45
+ <Icon home size={28} />
46
+ <Icon search size={28} />
47
+ <Icon bell size={28} />
48
+ </View>
49
+ ```
50
+
51
+ **Don't** — Mixed stroke weights and a stray filled glyph make a set look incoherent.
52
+
53
+ ```tsx
54
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 20 }}>
55
+ <Icon home muted size={20} />
56
+ <Icon search destructive size={34} />
57
+ <Icon bell primary size={28} />
58
+ </View>
59
+ ```
60
+
61
+ ### foreground
62
+
63
+ **Do** — Leave stroke as currentColor and set text-foreground on the parent so it follows light and dark.
64
+
65
+ ```tsx
66
+ <Icon mail size={28} />
67
+ ```
68
+
69
+ **Don't** — Hard-coding a hex stroke pins the icon to one theme; it stays black on a dark surface and disappears.
70
+
71
+ ```tsx
72
+ <View style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", borderRadius: 6, backgroundColor: tokens.foreground, padding: 12 }}>
73
+ <Icon mail size={28} />
74
+ </View>
75
+ ```
76
+
77
+ ### primary
78
+
79
+ **Do** — Reserve text-primary for the one active or selected icon; keep the rest muted.
80
+
81
+ ```tsx
82
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 20 }}>
83
+ <Icon home muted size={22} />
84
+ <Icon star primary size={22} />
85
+ <Icon settings muted size={22} />
86
+ </View>
87
+ ```
88
+
89
+ **Don't** — Painting a whole toolbar primary spends the accent on everything, so nothing reads as emphasized.
90
+
91
+ ```tsx
92
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 20 }}>
93
+ <Icon home primary size={22} />
94
+ <Icon search primary size={22} />
95
+ <Icon settings primary size={22} />
96
+ </View>
97
+ ```
98
+
99
+ ### destructive
100
+
101
+ **Do** — Keep text-destructive for genuinely destructive actions like delete, so red always means consequence.
102
+
103
+ ```tsx
104
+ <Icon trash destructive size={28} />
105
+ ```
106
+
107
+ **Don't** — A red download icon implies danger on a perfectly safe action and trains users to ignore the warning color.
108
+
109
+ ```tsx
110
+ <Icon download destructive size={28} />
111
+ ```
112
+
113
+ ### muted
114
+
115
+ **Do** — Use text-muted-foreground for secondary, inline hint icons where its color matches the helper text.
116
+
117
+ ```tsx
118
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
119
+ <Icon info muted size={16} />
120
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Optional, used only for recovery</Text>
121
+ </View>
122
+ ```
123
+
124
+ **Don't** — A muted icon inside a solid primary button reads as disabled and clashes with the high-contrast label.
125
+
126
+ ```tsx
127
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 8, borderRadius: 6, backgroundColor: tokens.primary, paddingHorizontal: 16, paddingVertical: 8 }}>
128
+ <Icon plus muted size={16} />
129
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens["primary-foreground"] }}>New project</Text>
130
+ </Pressable>
131
+ ```
@@ -0,0 +1,30 @@
1
+ import { type ViewStyle, type TextStyle } from "react-native";
2
+ import { type ColorTokens } from "../../style/index.js";
3
+
4
+ // Co-located Icon styles. A single glyph is a bare SVG with no surrounding box,
5
+ // so it has no fragments here; these cover the `set` gallery only. Layout-only
6
+ // parts are static objects; the label color reads the active tokens (so it
7
+ // follows light/dark).
8
+
9
+ // The gallery grid: full width, glyphs flowing left-to-right and wrapping.
10
+ // (w-full flex-row flex-wrap)
11
+ export const setGrid: ViewStyle = {
12
+ width: "100%",
13
+ flexDirection: "row",
14
+ flexWrap: "wrap",
15
+ };
16
+
17
+ // One gallery cell: a fixed-width column centering the glyph over its label.
18
+ // (items-center gap-1.5 rounded-lg px-1 py-2.5; the 80px width stays inline.)
19
+ export const setCell: ViewStyle = {
20
+ alignItems: "center",
21
+ gap: 6,
22
+ borderRadius: 8,
23
+ paddingHorizontal: 4,
24
+ paddingVertical: 10,
25
+ };
26
+
27
+ // The cell label color. (text-muted-foreground; the 10px size stays inline.)
28
+ export function setLabel(tokens: ColorTokens): TextStyle {
29
+ return { color: tokens["muted-foreground"] };
30
+ }
@@ -0,0 +1,328 @@
1
+ import Svg, { Circle, Ellipse, Line, Path, Polygon, Polyline, Rect } from "react-native-svg";
2
+ import { View, Text, useTheme, type ColorTokens, type StyleProp, type ViewStyle } from "../../style/index.js";
3
+ import * as s from "./icon.styles.js";
4
+
5
+ // Icon: a Lucide-style outline glyph rendered with react-native-svg, so it draws
6
+ // crisply on native and web and inherits color the same way everywhere. Stroke is
7
+ // 1.75 with rounded caps/joins; the glyph paints in a single theme color that the
8
+ // caller picks via a boolean color prop (foreground by default).
9
+ //
10
+ // Boolean-prop API (the prop name is the value):
11
+ //
12
+ // <Icon shield /> the shield glyph, foreground, 24px
13
+ // <Icon search primary /> the search glyph, primary color
14
+ // <Icon trash destructive /> the trash glyph, destructive color
15
+ // <Icon set /> the whole gallery, each glyph labeled by name
16
+ //
17
+ // Axes (pass at most one per axis; first match wins):
18
+ // - Name: one boolean per glyph (activity, bell, search, shield, …). Default shield.
19
+ // - Color: primary, primaryForeground, destructive, muted. Default foreground.
20
+ // (primaryForeground is the contrast color for a glyph on a primary surface.)
21
+ // Dimensions/layout (orthogonal): `size` (px, single glyph) and `set` (gallery).
22
+
23
+ type Shape =
24
+ | { t: "path"; d: string }
25
+ | { t: "circle"; cx: number; cy: number; r: number }
26
+ | { t: "line"; x1: number; y1: number; x2: number; y2: number }
27
+ | { t: "polyline"; points: string }
28
+ | { t: "polygon"; points: string }
29
+ | { t: "rect"; x: number; y: number; width: number; height: number; rx?: number; ry?: number }
30
+ | { t: "ellipse"; cx: number; cy: number; rx: number; ry: number };
31
+
32
+ // The curated set, in gallery order. Each glyph is an array of SVG primitives on
33
+ // the 0 0 24 24 viewBox, transcribed from the Lucide outline source.
34
+ const ICONS: Record<string, Shape[]> = {
35
+ activity: [{ t: "polyline", points: "22 12 18 12 15 21 9 3 6 12 2 12" }],
36
+ alertTriangle: [
37
+ { t: "path", d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" },
38
+ { t: "line", x1: 12, y1: 9, x2: 12, y2: 13 },
39
+ { t: "line", x1: 12, y1: 17, x2: 12.01, y2: 17 },
40
+ ],
41
+ archive: [
42
+ { t: "rect", x: 2, y: 7, width: 20, height: 14, rx: 2, ry: 2 },
43
+ { t: "path", d: "M16 3v4M8 3v4" },
44
+ ],
45
+ bell: [
46
+ { t: "path", d: "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" },
47
+ { t: "path", d: "M13.73 21a2 2 0 0 1-3.46 0" },
48
+ ],
49
+ calendar: [
50
+ { t: "rect", x: 3, y: 4, width: 18, height: 18, rx: 2, ry: 2 },
51
+ { t: "line", x1: 16, y1: 2, x2: 16, y2: 6 },
52
+ { t: "line", x1: 8, y1: 2, x2: 8, y2: 6 },
53
+ { t: "line", x1: 3, y1: 10, x2: 21, y2: 10 },
54
+ ],
55
+ check: [{ t: "polyline", points: "20 6 9 17 4 12" }],
56
+ chevronDown: [{ t: "path", d: "m6 9 6 6 6-6" }],
57
+ chevronLeft: [{ t: "path", d: "m15 18-6-6 6-6" }],
58
+ chevronRight: [{ t: "path", d: "m9 18 6-6-6-6" }],
59
+ code: [
60
+ { t: "polyline", points: "16 18 22 12 16 6" },
61
+ { t: "polyline", points: "8 6 2 12 8 18" },
62
+ ],
63
+ copy: [
64
+ { t: "rect", x: 9, y: 9, width: 13, height: 13, rx: 2, ry: 2 },
65
+ { t: "path", d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" },
66
+ ],
67
+ database: [
68
+ { t: "ellipse", cx: 12, cy: 5, rx: 9, ry: 3 },
69
+ { t: "path", d: "M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" },
70
+ { t: "path", d: "M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" },
71
+ ],
72
+ download: [
73
+ { t: "path", d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" },
74
+ { t: "polyline", points: "7 10 12 15 17 10" },
75
+ { t: "line", x1: 12, y1: 15, x2: 12, y2: 3 },
76
+ ],
77
+ eye: [
78
+ { t: "path", d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" },
79
+ { t: "circle", cx: 12, cy: 12, r: 3 },
80
+ ],
81
+ file: [
82
+ { t: "path", d: "M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z" },
83
+ { t: "polyline", points: "13 2 13 9 20 9" },
84
+ ],
85
+ filter: [{ t: "polygon", points: "22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" }],
86
+ globe: [
87
+ { t: "circle", cx: 12, cy: 12, r: 10 },
88
+ { t: "line", x1: 2, y1: 12, x2: 22, y2: 12 },
89
+ { t: "path", d: "M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" },
90
+ ],
91
+ home: [
92
+ { t: "path", d: "m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" },
93
+ { t: "polyline", points: "9 22 9 12 15 12 15 22" },
94
+ ],
95
+ info: [
96
+ { t: "circle", cx: 12, cy: 12, r: 10 },
97
+ { t: "line", x1: 12, y1: 16, x2: 12, y2: 12 },
98
+ { t: "line", x1: 12, y1: 8, x2: 12.01, y2: 8 },
99
+ ],
100
+ key: [{ t: "path", d: "m21 2-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0 3 3L22 7l-3-3m-3.5 3.5L19 4" }],
101
+ lock: [
102
+ { t: "rect", x: 3, y: 11, width: 18, height: 11, rx: 2, ry: 2 },
103
+ { t: "path", d: "M7 11V7a5 5 0 0 1 10 0v4" },
104
+ ],
105
+ mail: [
106
+ { t: "path", d: "M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" },
107
+ { t: "polyline", points: "22,6 12,13 2,6" },
108
+ ],
109
+ plus: [
110
+ { t: "line", x1: 12, y1: 5, x2: 12, y2: 19 },
111
+ { t: "line", x1: 5, y1: 12, x2: 19, y2: 12 },
112
+ ],
113
+ search: [
114
+ { t: "circle", cx: 11, cy: 11, r: 8 },
115
+ { t: "line", x1: 21, y1: 21, x2: 16.65, y2: 16.65 },
116
+ ],
117
+ settings: [
118
+ { t: "circle", cx: 12, cy: 12, r: 3 },
119
+ {
120
+ t: "path",
121
+ d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z",
122
+ },
123
+ ],
124
+ shield: [{ t: "path", d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }],
125
+ star: [{ t: "polygon", points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" }],
126
+ trash: [
127
+ { t: "polyline", points: "3 6 5 6 21 6" },
128
+ { t: "path", d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" },
129
+ ],
130
+ upload: [
131
+ { t: "path", d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" },
132
+ { t: "polyline", points: "17 8 12 3 7 8" },
133
+ { t: "line", x1: 12, y1: 3, x2: 12, y2: 15 },
134
+ ],
135
+ user: [
136
+ { t: "path", d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" },
137
+ { t: "circle", cx: 12, cy: 7, r: 4 },
138
+ ],
139
+ users: [
140
+ { t: "path", d: "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" },
141
+ { t: "circle", cx: 9, cy: 7, r: 4 },
142
+ { t: "path", d: "M23 21v-2a4 4 0 0 0-3-3.87" },
143
+ { t: "path", d: "M16 3.13a4 4 0 0 1 0 7.75" },
144
+ ],
145
+ x: [
146
+ { t: "line", x1: 18, y1: 6, x2: 6, y2: 18 },
147
+ { t: "line", x1: 6, y1: 6, x2: 18, y2: 18 },
148
+ ],
149
+ zap: [{ t: "polygon", points: "13 2 3 14 12 14 11 22 21 10 12 10 13 2" }],
150
+ };
151
+
152
+ // Gallery order + the short label shown under each glyph in the set view.
153
+ const NAMES: { key: string; label: string }[] = [
154
+ { key: "activity", label: "activity" },
155
+ { key: "alertTriangle", label: "alert-tri" },
156
+ { key: "archive", label: "archive" },
157
+ { key: "bell", label: "bell" },
158
+ { key: "calendar", label: "calendar" },
159
+ { key: "check", label: "check" },
160
+ { key: "chevronDown", label: "chevron-down" },
161
+ { key: "chevronLeft", label: "chevron-left" },
162
+ { key: "chevronRight", label: "chevron-right" },
163
+ { key: "code", label: "code" },
164
+ { key: "copy", label: "copy" },
165
+ { key: "database", label: "database" },
166
+ { key: "download", label: "download" },
167
+ { key: "eye", label: "eye" },
168
+ { key: "file", label: "file" },
169
+ { key: "filter", label: "filter" },
170
+ { key: "globe", label: "globe" },
171
+ { key: "home", label: "home" },
172
+ { key: "info", label: "info" },
173
+ { key: "key", label: "key" },
174
+ { key: "lock", label: "lock" },
175
+ { key: "mail", label: "mail" },
176
+ { key: "plus", label: "plus" },
177
+ { key: "search", label: "search" },
178
+ { key: "settings", label: "settings" },
179
+ { key: "shield", label: "shield" },
180
+ { key: "star", label: "star" },
181
+ { key: "trash", label: "trash" },
182
+ { key: "upload", label: "upload" },
183
+ { key: "user", label: "user" },
184
+ { key: "users", label: "users" },
185
+ { key: "x", label: "x" },
186
+ { key: "zap", label: "zap" },
187
+ ];
188
+
189
+ export interface IconProps {
190
+ // Name axis: one boolean per glyph (pass one; first match wins, default shield).
191
+ activity?: boolean;
192
+ alertTriangle?: boolean;
193
+ archive?: boolean;
194
+ bell?: boolean;
195
+ calendar?: boolean;
196
+ check?: boolean;
197
+ chevronDown?: boolean;
198
+ chevronLeft?: boolean;
199
+ chevronRight?: boolean;
200
+ code?: boolean;
201
+ copy?: boolean;
202
+ database?: boolean;
203
+ download?: boolean;
204
+ eye?: boolean;
205
+ file?: boolean;
206
+ filter?: boolean;
207
+ globe?: boolean;
208
+ home?: boolean;
209
+ info?: boolean;
210
+ key?: boolean;
211
+ lock?: boolean;
212
+ mail?: boolean;
213
+ plus?: boolean;
214
+ search?: boolean;
215
+ settings?: boolean;
216
+ shield?: boolean;
217
+ star?: boolean;
218
+ trash?: boolean;
219
+ upload?: boolean;
220
+ user?: boolean;
221
+ users?: boolean;
222
+ x?: boolean;
223
+ zap?: boolean;
224
+ // Color axis: pass one (default foreground). First match wins.
225
+ primary?: boolean;
226
+ /** Contrast color for a glyph on a primary surface (e.g. a primary button). */
227
+ primaryForeground?: boolean;
228
+ destructive?: boolean;
229
+ muted?: boolean;
230
+ // Single-glyph size in px (default 24).
231
+ size?: number;
232
+ // Render the whole gallery instead of a single glyph.
233
+ set?: boolean;
234
+ /** Escape hatch for layout/positioning composition (margins, alignment). */
235
+ style?: StyleProp<ViewStyle>;
236
+ }
237
+
238
+ // First-match name precedence; defaults to shield (the demo glyph).
239
+ function nameOf(p: IconProps): string {
240
+ for (const { key } of NAMES) {
241
+ if ((p as Record<string, unknown>)[key]) return key;
242
+ }
243
+ return "shield";
244
+ }
245
+
246
+ // First-match color precedence; defaults to foreground.
247
+ function strokeOf(p: IconProps, tokens: ColorTokens): string {
248
+ if (p.primary) return tokens.primary;
249
+ if (p.primaryForeground) return tokens["primary-foreground"];
250
+ if (p.destructive) return tokens.destructive;
251
+ if (p.muted) return tokens["muted-foreground"];
252
+ return tokens.foreground;
253
+ }
254
+
255
+ function renderShape(sh: Shape, k: number) {
256
+ switch (sh.t) {
257
+ case "path":
258
+ return <Path key={k} d={sh.d} />;
259
+ case "circle":
260
+ return <Circle key={k} cx={sh.cx} cy={sh.cy} r={sh.r} />;
261
+ case "line":
262
+ return <Line key={k} x1={sh.x1} y1={sh.y1} x2={sh.x2} y2={sh.y2} />;
263
+ case "polyline":
264
+ return <Polyline key={k} points={sh.points} />;
265
+ case "polygon":
266
+ return <Polygon key={k} points={sh.points} />;
267
+ case "rect":
268
+ return <Rect key={k} x={sh.x} y={sh.y} width={sh.width} height={sh.height} rx={sh.rx} ry={sh.ry} />;
269
+ case "ellipse":
270
+ return <Ellipse key={k} cx={sh.cx} cy={sh.cy} rx={sh.rx} ry={sh.ry} />;
271
+ }
272
+ }
273
+
274
+ // One glyph: an SVG that sets the shared presentation attributes (stroke, weight,
275
+ // caps) on the root; the primitive children inherit them.
276
+ function Glyph({
277
+ shapes,
278
+ size,
279
+ stroke,
280
+ style,
281
+ }: {
282
+ shapes: Shape[];
283
+ size: number;
284
+ stroke: string;
285
+ style?: StyleProp<ViewStyle>;
286
+ }) {
287
+ return (
288
+ <Svg
289
+ width={size}
290
+ height={size}
291
+ viewBox="0 0 24 24"
292
+ fill="none"
293
+ stroke={stroke}
294
+ strokeWidth={1.75}
295
+ strokeLinecap="round"
296
+ strokeLinejoin="round"
297
+ style={style}
298
+ >
299
+ {shapes.map((sh, i) => renderShape(sh, i))}
300
+ </Svg>
301
+ );
302
+ }
303
+
304
+ export function Icon(props: IconProps) {
305
+ const { tokens } = useTheme();
306
+
307
+ if (props.set) {
308
+ return (
309
+ <View style={[s.setGrid, props.style]}>
310
+ {NAMES.map(({ key, label }) => (
311
+ <View key={key} style={[s.setCell, { width: 80 }]}>
312
+ <Glyph shapes={ICONS[key]} size={20} stroke={tokens.foreground} />
313
+ <Text style={[s.setLabel(tokens), { fontSize: 10 }]}>{label}</Text>
314
+ </View>
315
+ ))}
316
+ </View>
317
+ );
318
+ }
319
+
320
+ return (
321
+ <Glyph
322
+ shapes={ICONS[nameOf(props)]}
323
+ size={props.size ?? 24}
324
+ stroke={strokeOf(props, tokens)}
325
+ style={props.style}
326
+ />
327
+ );
328
+ }
@@ -0,0 +1,24 @@
1
+ // Atoms: the React Native UI kit components at the atoms atomic level.
2
+ export * from "./avatar/avatar.js";
3
+ export * from "./badge/badge.js";
4
+ export * from "./breadcrumb/breadcrumb.js";
5
+ export * from "./button/button.js";
6
+ export * from "./button-group/button-group.js";
7
+ export * from "./checkbox/checkbox.js";
8
+ export * from "./combobox/combobox.js";
9
+ export * from "./divider/divider.js";
10
+ export * from "./dropdown/dropdown.js";
11
+ export * from "./icon/icon.js";
12
+ export * from "./input/input.js";
13
+ export * from "./kbd/kbd.js";
14
+ export * from "./listbox/listbox.js";
15
+ export * from "./pagination/pagination.js";
16
+ export * from "./popover/popover.js";
17
+ export * from "./radio/radio.js";
18
+ export * from "./select/select.js";
19
+ export * from "./skeleton/skeleton.js";
20
+ export * from "./spinner/spinner.js";
21
+ export * from "./switch/switch.js";
22
+ export * from "./textarea/textarea.js";
23
+ export * from "./tooltip/tooltip.js";
24
+ export * from "./typography/typography.js";
@@ -0,0 +1,6 @@
1
+ import { createInput } from "./input.shared.js";
2
+ import { androidSkin } from "./input.styles.js";
3
+
4
+ // Material 3 Input. Metro resolves this file on Android; the docs import it for preview.
5
+ export const Input = createInput(androidSkin);
6
+ export type { InputProps } from "./input.shared.js";
@@ -0,0 +1,6 @@
1
+ import { createInput } from "./input.shared.js";
2
+ import { iosSkin } from "./input.styles.js";
3
+
4
+ // iOS (HIG) Input. Metro resolves this file on iOS; the docs import it for preview.
5
+ export const Input = createInput(iosSkin);
6
+ export type { InputProps } from "./input.shared.js";
@@ -0,0 +1,118 @@
1
+ # Inputs & Forms
2
+
3
+ The Input component is a React Native text field with semantic boolean props (`error`, `small`, `large`, `block`, `disabled`), plus prefix/suffix addons and overlaid icons; `multiline` turns it into a textarea. Select and the search field share its look, and Field and Form compose a label, the control, and helper text.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ <Input placeholder="rachel.chen@example.com" style={{ maxWidth: 320 }} />
9
+ ```
10
+
11
+ ## Variants
12
+
13
+ ### Control - number
14
+
15
+ ```tsx
16
+ <Input placeholder="1024" style={{ maxWidth: 320 }} />
17
+ ```
18
+
19
+ ### Control - select
20
+
21
+ ```tsx
22
+ <View style={{ maxWidth: 320, flexDirection: "column", gap: 6 }}>
23
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Status</Text>
24
+ <Select value="Active" options={["Active", "Inactive", "Pending"]} />
25
+ <Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>We'll use this for account recovery.</Text>
26
+ </View>
27
+ ```
28
+
29
+ ### Control - textarea
30
+
31
+ ```tsx
32
+ <Input multiline placeholder="Describe the change" style={{ maxWidth: 320 }} />
33
+ ```
34
+
35
+ ### State - error
36
+
37
+ ```tsx
38
+ <Input error placeholder="rachel.chen@example.com" style={{ maxWidth: 320 }} />
39
+ ```
40
+
41
+ ### State - disabled
42
+
43
+ ```tsx
44
+ <Input disabled placeholder="rachel.chen@example.com" style={{ maxWidth: 320 }} />
45
+ ```
46
+
47
+ ### State - readonly
48
+
49
+ ```tsx
50
+ <Input readOnly placeholder="rachel.chen@example.com" style={{ maxWidth: 320 }} />
51
+ ```
52
+
53
+ ## Do & Don't
54
+
55
+ ### text
56
+
57
+ **Do** — Pair every field with a persistent .label above the control.
58
+
59
+ ```tsx
60
+ <Field label="Email" placeholder="ada@acme.dev" style={{ maxWidth: 320 }} />
61
+ ```
62
+
63
+ **Don't** — A placeholder is not a label; it vanishes the moment the user types and screen readers may skip it.
64
+
65
+ ```tsx
66
+ <Input placeholder="Email" style={{ maxWidth: 320 }} />
67
+ ```
68
+
69
+ ### number
70
+
71
+ **Do** — Use type="number" with inputmode and park the unit in a .input-addon so the value stays purely numeric.
72
+
73
+ ```tsx
74
+ <View style={{ maxWidth: 320 }}>
75
+ <Text style={{ marginBottom: 6, fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Storage</Text>
76
+ <Input value="1024" suffix="GB" />
77
+ </View>
78
+ ```
79
+
80
+ **Don't** — A plain text field lets users type the unit into the value, breaking parsing and validation.
81
+
82
+ ```tsx
83
+ <Field label="Storage" value="1024 GB" style={{ maxWidth: 320 }} />
84
+ ```
85
+
86
+ ### select
87
+
88
+ **Do** — Reserve a select for picking one of several mutually exclusive options; use a switch or radios for two.
89
+
90
+ ```tsx
91
+ <Select label="Status" options={["Active", "Inactive", "Pending", "Archived"]} value="Active" style={{ maxWidth: 320 }} />
92
+ ```
93
+
94
+ **Don't** — A select for a single on/off choice buries a one-tap decision behind a dropdown.
95
+
96
+ ```tsx
97
+ <Select label="Email notifications" options={["On", "Off"]} value="On" style={{ maxWidth: 320 }} />
98
+ ```
99
+
100
+ ### textarea
101
+
102
+ **Do** — Give a textarea a min-height for several lines and resize-y so it can grow with the content.
103
+
104
+ ```tsx
105
+ <View style={{ maxWidth: 320 }}>
106
+ <Text style={{ marginBottom: 6, fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Notes</Text>
107
+ <Textarea rows={4} value="Describe the change in enough detail that a teammate could follow it…" />
108
+ </View>
109
+ ```
110
+
111
+ **Don't** — A one-line, resize-none textarea clips multi-line input so users cannot review what they wrote.
112
+
113
+ ```tsx
114
+ <View style={{ maxWidth: 320 }}>
115
+ <Text style={{ marginBottom: 6, fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Notes</Text>
116
+ <TextInput multiline value="Describe the change in enough detail that a teammate could follow it…" style={{ height: 36, width: "100%", borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12, paddingVertical: 4, fontSize: 14, lineHeight: 20, color: tokens.foreground, overflow: "hidden" }} />
117
+ </View>
118
+ ```