@olympusoss/canvas 3.2.1 → 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 (302) hide show
  1. package/README.md +75 -65
  2. package/package.json +11 -5
  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/styles/canvas.css +127 -74
  234. package/tsconfig.json +4 -2
  235. package/src/cn.ts +0 -3
  236. package/styles/atoms/avatar.css +0 -22
  237. package/styles/atoms/badge.css +0 -83
  238. package/styles/atoms/breadcrumb.css +0 -35
  239. package/styles/atoms/button-group.css +0 -23
  240. package/styles/atoms/button.css +0 -107
  241. package/styles/atoms/checkbox.css +0 -55
  242. package/styles/atoms/combobox.css +0 -76
  243. package/styles/atoms/dropdown.css +0 -54
  244. package/styles/atoms/icon.css +0 -8
  245. package/styles/atoms/input-group.css +0 -45
  246. package/styles/atoms/input.css +0 -56
  247. package/styles/atoms/kbd.css +0 -15
  248. package/styles/atoms/pagination.css +0 -48
  249. package/styles/atoms/popover.css +0 -14
  250. package/styles/atoms/radio.css +0 -28
  251. package/styles/atoms/select.css +0 -57
  252. package/styles/atoms/separator.css +0 -32
  253. package/styles/atoms/skeleton.css +0 -32
  254. package/styles/atoms/spinner.css +0 -26
  255. package/styles/atoms/switch.css +0 -45
  256. package/styles/atoms/textarea.css +0 -31
  257. package/styles/atoms/tooltip.css +0 -53
  258. package/styles/atoms/typography.css +0 -105
  259. package/styles/base.css +0 -17
  260. package/styles/molecules/alert.css +0 -66
  261. package/styles/molecules/card.css +0 -58
  262. package/styles/molecules/code-block.css +0 -18
  263. package/styles/molecules/empty-state.css +0 -17
  264. package/styles/molecules/field.css +0 -27
  265. package/styles/molecules/form.css +0 -27
  266. package/styles/molecules/page-header.css +0 -52
  267. package/styles/molecules/section-card.css +0 -49
  268. package/styles/molecules/stat-card.css +0 -71
  269. package/styles/molecules/toast.css +0 -95
  270. package/styles/organisms/app-shell.css +0 -46
  271. package/styles/organisms/calendar.css +0 -73
  272. package/styles/organisms/command.css +0 -95
  273. package/styles/organisms/data-table.css +0 -142
  274. package/styles/organisms/dialog.css +0 -72
  275. package/styles/organisms/filter-panel.css +0 -58
  276. package/styles/organisms/row-menu.css +0 -69
  277. package/styles/organisms/sheet.css +0 -70
  278. package/styles/organisms/sidebar.css +0 -146
  279. package/styles/organisms/stepper.css +0 -63
  280. package/styles/organisms/tabs.css +0 -40
  281. package/styles/organisms/topbar.css +0 -24
  282. package/styles/patterns/backdrops.css +0 -35
  283. package/styles/patterns/density.css +0 -66
  284. package/styles/patterns/focus.css +0 -22
  285. package/styles/patterns/glass.css +0 -85
  286. package/styles/patterns/high-contrast.css +0 -70
  287. package/styles/patterns/reduced-motion.css +0 -12
  288. package/styles/patterns/scrollbar.css +0 -10
  289. package/styles/reset.css +0 -89
  290. package/styles/tokens/colors.css +0 -108
  291. package/styles/tokens/motion.css +0 -33
  292. package/styles/tokens/radius.css +0 -10
  293. package/styles/tokens/shadows.css +0 -35
  294. package/styles/tokens/spacing.css +0 -19
  295. package/styles/tokens/typography.css +0 -6
  296. package/styles/tokens/z-index.css +0 -12
  297. package/styles/utilities/display.css +0 -66
  298. package/styles/utilities/flexbox.css +0 -240
  299. package/styles/utilities/gap.css +0 -288
  300. package/styles/utilities/grid.css +0 -138
  301. package/styles/utilities/position.css +0 -78
  302. package/styles/utilities/sizing.css +0 -138
@@ -0,0 +1,270 @@
1
+ import { StyleSheet, type ViewStyle, type TextStyle } from "react-native";
2
+ import { type ColorTokens, shadow } from "../../style/index.js";
3
+
4
+ // Co-located Combobox skins, one per platform. A Combobox is a searchable
5
+ // single-select: an editable field that filters an open option list. The BRAND
6
+ // survives on every platform (the indigo `primary`/`accent` tokens stay; the
7
+ // focus accent is the `ring`, never a platform default) and only the native
8
+ // SHAPE, sizing, fill, border/underline treatment, popover elevation, and press
9
+ // feedback change per OS. The treatment mirrors Input/Select:
10
+ // iOS 27 (iOS 26+/Liquid Glass): a plain field, transparent, no capsule, just
11
+ // a bottom hairline (`border` at rest -> brand `primary` when open); the
12
+ // trailing chevron is `muted-foreground`; the open list is a large
13
+ // continuous-corner `popover` card (~27 radius) with a soft shadow and roomy
14
+ // rows. Press = opacity dim (~0.8).
15
+ // Android (Material 3 filled): a subtle `muted` fill, TOP corners ~4 radius and
16
+ // a flat bottom, a bottom active-indicator underline (1dp `border` at rest ->
17
+ // 2dp `ring` brand when open); the menu surface is a flat-cornered (~4)
18
+ // elevated `popover` sheet (M3 elevation, no soft drop shadow), full-width
19
+ // rows ~48dp tall; press = android_ripple.
20
+ // Web: the established Canvas look (the current combobox, lifted verbatim) —
21
+ // full 1px `input` border, 6 radius, `background` fill, h-8/9/10; the popover
22
+ // is a 6-radius bordered `popover` card with `shadow-lg`, 4px padding, 2-radius
23
+ // accent rows. Press dims nothing (the active accent fill is the feedback).
24
+
25
+ export type Size = "small" | "default" | "large";
26
+
27
+ // The contract a platform skin fulfills. The shell resolves the size and the
28
+ // open/selected/pressed/muted state and asks the skin to map them to RN style
29
+ // objects. The skin owns shape, fill, border/underline, popover elevation, the
30
+ // row layout, and the press-feedback channel (iOS/web opacity vs Android ripple).
31
+ export interface ComboboxSkin {
32
+ /** Type scale per size; the field text and the option rows share it. */
33
+ text: (size: Size) => TextStyle;
34
+ /** Stacked field label above the field. */
35
+ label: (t: ColorTokens, size: Size) => TextStyle;
36
+ /** The editable field surface: shape, fill, border/underline for the open state. */
37
+ field: (t: ColorTokens, size: Size, open: boolean) => ViewStyle;
38
+ /** The field's value text (foreground), or muted for the placeholder. */
39
+ fieldText: (t: ColorTokens, size: Size, muted: boolean) => TextStyle;
40
+ /** The trailing disclosure chevron. */
41
+ chevron: (t: ColorTokens, size: Size) => TextStyle;
42
+ /** The open option list surface (radius, elevation/shadow, padding). */
43
+ popover: (t: ColorTokens) => ViewStyle;
44
+ /** The "No results" row box. */
45
+ emptyRow: ViewStyle;
46
+ emptyText: (t: ColorTokens, size: Size) => TextStyle;
47
+ /** A single option row's layout (gutter, radius, padding). */
48
+ row: ViewStyle;
49
+ /** The accent surface for the selected row and the pressed/active row. */
50
+ rowAccent: (t: ColorTokens) => ViewStyle;
51
+ /** The leading check column. */
52
+ check: (t: ColorTokens, size: Size) => TextStyle;
53
+ /** The option label. */
54
+ optionText: (t: ColorTokens, size: Size) => TextStyle;
55
+ /** Helper line below the option list. */
56
+ helper: (t: ColorTokens) => TextStyle;
57
+ /** Opacity applied to the whole control when disabled. */
58
+ disabledOpacity: number;
59
+ /** iOS/web dim the field on press; Android uses a ripple instead (null). */
60
+ pressedOpacity: number | null;
61
+ /** Android ripple over the pressable surfaces; null on iOS/web. */
62
+ ripple: ((t: ColorTokens) => { color: string; borderless: boolean }) | null;
63
+ }
64
+
65
+ // `relative w-full`: the positioning context for the absolutely-placed popover.
66
+ export const wrapper: ViewStyle = { position: "relative", width: "100%" };
67
+
68
+ // When the list is open, the wrapper is lifted into its own stacking context
69
+ // above sibling content. react-native-web gives every positioned View an
70
+ // implicit stacking context, so the popover's own `zIndex` is scoped INSIDE the
71
+ // `relative` wrapper and cannot rise above a later sibling. Raising the wrapper's
72
+ // zIndex while open lifts the whole control — field and popover together — above
73
+ // everything painted after it.
74
+ export const wrapperLifted: ViewStyle = { zIndex: 50 };
75
+
76
+ // --- shared type scale (identical across platforms; brand type, not a face) --
77
+ const TEXT_SIZE: Record<Size, TextStyle> = {
78
+ small: { fontSize: 12, lineHeight: 16 },
79
+ default: { fontSize: 14, lineHeight: 20 },
80
+ large: { fontSize: 16, lineHeight: 24 },
81
+ };
82
+ function webText(size: Size): TextStyle {
83
+ return TEXT_SIZE[size];
84
+ }
85
+
86
+ // Field height per size; mirrors Input's footprint per platform.
87
+ const WEB_FIELD_BOX: Record<Size, number> = { small: 32, default: 36, large: 40 };
88
+
89
+ // ---------- Web: the established Canvas look (lifted verbatim) ----------
90
+ export const webSkin: ComboboxSkin = {
91
+ text: webText,
92
+ label: (t, size) => ({ marginBottom: 6, fontWeight: "500", color: t.foreground, ...TEXT_SIZE[size] }),
93
+ field: (t, size) => ({
94
+ flexDirection: "row",
95
+ alignItems: "center",
96
+ justifyContent: "space-between",
97
+ borderRadius: 6,
98
+ borderWidth: 1,
99
+ borderColor: t.input,
100
+ backgroundColor: t.background,
101
+ paddingHorizontal: 12,
102
+ height: WEB_FIELD_BOX[size],
103
+ }),
104
+ fieldText: (t, size, muted) => ({ color: muted ? t["muted-foreground"] : t.foreground, ...TEXT_SIZE[size] }),
105
+ chevron: (t, size) => ({ color: t["muted-foreground"], ...TEXT_SIZE[size] }),
106
+ popover: (t) => ({
107
+ position: "absolute",
108
+ top: "100%",
109
+ left: 0,
110
+ right: 0,
111
+ zIndex: 50,
112
+ marginTop: 4,
113
+ maxHeight: 240,
114
+ borderRadius: 6,
115
+ borderWidth: 1,
116
+ borderColor: t.border,
117
+ backgroundColor: t.popover,
118
+ padding: 4,
119
+ ...shadow("lg"),
120
+ }),
121
+ emptyRow: { paddingHorizontal: 8, paddingVertical: 6 },
122
+ emptyText: (t, size) => ({ color: t["muted-foreground"], ...TEXT_SIZE[size] }),
123
+ row: {
124
+ flexDirection: "row",
125
+ alignItems: "center",
126
+ gap: 8,
127
+ borderRadius: 2,
128
+ paddingHorizontal: 8,
129
+ paddingVertical: 6,
130
+ },
131
+ rowAccent: (t) => ({ backgroundColor: t.accent }),
132
+ check: (t, size) => ({ width: 14, color: t["popover-foreground"], ...TEXT_SIZE[size] }),
133
+ optionText: (t, size) => ({ color: t["popover-foreground"], ...TEXT_SIZE[size] }),
134
+ helper: (t) => ({ marginTop: 6, fontSize: 12, lineHeight: 16, color: t["muted-foreground"] }),
135
+ disabledOpacity: 0.5,
136
+ pressedOpacity: null, // web shows press via the active accent fill, not opacity
137
+ ripple: null,
138
+ };
139
+
140
+ // ---------- iOS 27 (iOS 26+/Liquid Glass): plain hairline field, large-radius glass menu ----------
141
+ // The iOS 27 combo box reads like the new iOS text field: NO filled capsule and
142
+ // no box, just the value text on a transparent surface above a thin bottom
143
+ // hairline (`border` at rest, the brand indigo `primary` when the list is open,
144
+ // echoing the field's blue caret in the reference). The open list is the iOS 26+
145
+ // menu surface: a large continuous-corner `popover` card (~27 radius, up from the
146
+ // old ~12) floating on a soft shadow, with roomy rows. Press dims the surface
147
+ // (~0.8); no ripple. The brand survives: the open hairline, the leading check,
148
+ // and the focus accent are all the indigo `primary`, never iOS system blue.
149
+ const IOS_MENU_RADIUS = 27;
150
+ const IOS_FIELD_BOX: Record<Size, number> = { small: 36, default: 44, large: 50 };
151
+ export const iosSkin: ComboboxSkin = {
152
+ text: webText,
153
+ label: (t, size) => ({ marginBottom: 6, fontWeight: "600", color: t.foreground, ...TEXT_SIZE[size] }),
154
+ // Plain field: transparent, square (no capsule), bottom hairline only. The
155
+ // hairline thickens and tints to the brand `primary` when the list is open.
156
+ field: (t, size, open) => ({
157
+ flexDirection: "row",
158
+ alignItems: "center",
159
+ justifyContent: "space-between",
160
+ borderRadius: 0,
161
+ backgroundColor: "transparent",
162
+ borderBottomWidth: open ? 1.5 : StyleSheet.hairlineWidth,
163
+ borderBottomColor: open ? t.primary : t.border,
164
+ paddingHorizontal: 0,
165
+ height: IOS_FIELD_BOX[size],
166
+ }),
167
+ fieldText: (t, size, muted) => ({ color: muted ? t["muted-foreground"] : t.foreground, ...TEXT_SIZE[size] }),
168
+ chevron: (t, size) => ({ color: t["muted-foreground"], ...TEXT_SIZE[size] }),
169
+ popover: (t) => ({
170
+ position: "absolute",
171
+ top: "100%",
172
+ left: 0,
173
+ right: 0,
174
+ zIndex: 50,
175
+ marginTop: 6,
176
+ maxHeight: 260,
177
+ borderRadius: IOS_MENU_RADIUS,
178
+ backgroundColor: t.popover,
179
+ padding: 6,
180
+ ...shadow("lg"),
181
+ }),
182
+ emptyRow: { paddingHorizontal: 14, paddingVertical: 10 },
183
+ emptyText: (t, size) => ({ color: t["muted-foreground"], ...TEXT_SIZE[size] }),
184
+ row: {
185
+ flexDirection: "row",
186
+ alignItems: "center",
187
+ gap: 8,
188
+ borderRadius: 18,
189
+ paddingHorizontal: 14,
190
+ paddingVertical: 10,
191
+ },
192
+ rowAccent: (t) => ({ backgroundColor: t.accent }),
193
+ check: (t, size) => ({ width: 16, color: t.primary, ...TEXT_SIZE[size] }),
194
+ optionText: (t, size) => ({ color: t["popover-foreground"], ...TEXT_SIZE[size] }),
195
+ helper: (t) => ({ marginTop: 6, fontSize: 12, lineHeight: 16, color: t["muted-foreground"] }),
196
+ disabledOpacity: 0.5,
197
+ pressedOpacity: 0.8,
198
+ ripple: null,
199
+ };
200
+
201
+ // ---------- Android (Material 3 filled): subtle fill, top radius, bottom indicator, elevated menu ----------
202
+ // M3 exposed dropdown: the anchor is a filled field (`muted`) with the TOP
203
+ // corners rounded ~4dp and a flat bottom, plus a bottom active-indicator
204
+ // underline — 1dp `border` at rest, 2dp `ring` (brand) when open. The menu is a
205
+ // flat-cornered (~4) elevated `popover` sheet (M3 elevation via `elevation`, no
206
+ // soft iOS drop shadow), full-width rows ~48dp tall whose active/selected state
207
+ // is the `accent` state layer. The action feedback is android_ripple.
208
+ const ANDROID_TOP_RADIUS = 4;
209
+ const ANDROID_FIELD_BOX: Record<Size, number> = { small: 48, default: 56, large: 60 };
210
+ export const androidSkin: ComboboxSkin = {
211
+ // M3 body text is 16sp; nudge base/large up, keep small readable.
212
+ text: (size) => {
213
+ if (size === "large") return { fontSize: 18, lineHeight: 26 };
214
+ if (size === "small") return { fontSize: 14, lineHeight: 20 };
215
+ return { fontSize: 16, lineHeight: 24 };
216
+ },
217
+ label: (t, size) => ({ marginBottom: 6, fontWeight: "500", color: t.foreground, ...TEXT_SIZE[size] }),
218
+ field: (t, size, open) => ({
219
+ flexDirection: "row",
220
+ alignItems: "center",
221
+ justifyContent: "space-between",
222
+ borderTopLeftRadius: ANDROID_TOP_RADIUS,
223
+ borderTopRightRadius: ANDROID_TOP_RADIUS,
224
+ borderBottomLeftRadius: 0,
225
+ borderBottomRightRadius: 0,
226
+ borderBottomWidth: open ? 2 : 1,
227
+ // Rest baseline reads clearly (on-surface-variant ~ muted-foreground) so the M3
228
+ // filled field is distinct from the iOS lineless capsule.
229
+ borderBottomColor: open ? t.ring : t["muted-foreground"],
230
+ backgroundColor: t.muted,
231
+ paddingHorizontal: 16,
232
+ height: ANDROID_FIELD_BOX[size],
233
+ }),
234
+ fieldText: (t, size, muted) => ({ color: muted ? t["muted-foreground"] : t.foreground, ...TEXT_SIZE[size] }),
235
+ chevron: (t, size) => ({ color: t["muted-foreground"], ...TEXT_SIZE[size] }),
236
+ // M3 menu surface: flat 4dp corners, elevated (no soft drop shadow), zero
237
+ // padding so the full-bleed rows reach the edges.
238
+ popover: (t) => ({
239
+ position: "absolute",
240
+ top: "100%",
241
+ left: 0,
242
+ right: 0,
243
+ zIndex: 50,
244
+ marginTop: 4,
245
+ maxHeight: 280,
246
+ borderRadius: 4,
247
+ backgroundColor: t.popover,
248
+ paddingVertical: 8,
249
+ elevation: 8,
250
+ }),
251
+ emptyRow: { paddingHorizontal: 16, paddingVertical: 12 },
252
+ emptyText: (t, size) => ({ color: t["muted-foreground"], ...TEXT_SIZE[size] }),
253
+ // Full-bleed M3 list rows: square corners, ~48dp tall, 16dp gutter.
254
+ row: {
255
+ flexDirection: "row",
256
+ alignItems: "center",
257
+ gap: 12,
258
+ borderRadius: 0,
259
+ minHeight: 48,
260
+ paddingHorizontal: 16,
261
+ paddingVertical: 12,
262
+ },
263
+ rowAccent: (t) => ({ backgroundColor: t.accent }),
264
+ check: (t, size) => ({ width: 16, color: t.primary, ...TEXT_SIZE[size] }),
265
+ optionText: (t, size) => ({ color: t["popover-foreground"], ...TEXT_SIZE[size] }),
266
+ helper: (t) => ({ marginTop: 6, fontSize: 12, lineHeight: 16, color: t["muted-foreground"] }),
267
+ disabledOpacity: 0.38, // M3 disabled opacity
268
+ pressedOpacity: null, // Android uses a ripple instead
269
+ ripple: (t) => ({ color: t.accent, borderless: false }),
270
+ };
@@ -0,0 +1,6 @@
1
+ import { createCombobox } from "./combobox.shared.js";
2
+ import { webSkin } from "./combobox.styles.js";
3
+
4
+ // Web Combobox (the base; Metro falls back to it on native, web bundlers resolve it).
5
+ export const Combobox = createCombobox(webSkin);
6
+ export type { ComboboxProps } from "./combobox.shared.js";
@@ -0,0 +1,140 @@
1
+ # Dividers
2
+
3
+ Horizontal, vertical, with label, with action.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ <Divider />
9
+ ```
10
+
11
+ ## Variants
12
+
13
+ ### Orientation - vertical
14
+
15
+ ```tsx
16
+ <Divider vertical />
17
+ ```
18
+
19
+ ### Variant - label
20
+
21
+ ```tsx
22
+ <Divider>Or continue with</Divider>
23
+ ```
24
+
25
+ ### Variant - action
26
+
27
+ ```tsx
28
+ <View style={{ width: 320 }}>
29
+ <View style={{ gap: 8 }}>
30
+ <View style={{ borderRadius: 6, borderWidth: 1, borderColor: tokens.border, paddingHorizontal: 12, paddingVertical: 8 }}>
31
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens.foreground }}>Ada commented on the draft</Text>
32
+ </View>
33
+ <View style={{ borderRadius: 6, borderWidth: 1, borderColor: tokens.border, paddingHorizontal: 12, paddingVertical: 8 }}>
34
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens.foreground }}>Grace approved the request</Text>
35
+ </View>
36
+ </View>
37
+ <Divider style={{ marginTop: 12 }} children={<Button ghost small>Show more</Button>} />
38
+ </View>
39
+ ```
40
+
41
+ ## Do & Don't
42
+
43
+ ### Plain
44
+
45
+ **Do** — Click a row: group with spacing and reserve a divider for a real break like Sign out.
46
+
47
+ ```tsx
48
+ <View style={{ maxWidth: 280 }}>
49
+ <Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Profile</Text>
50
+ <Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Account</Text>
51
+ <Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Notifications</Text>
52
+ <Divider style={{ marginVertical: 4 }} />
53
+ <Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Sign out</Text>
54
+ </View>
55
+ ```
56
+
57
+ **Don't** — Click a row: a divider between every one is noise that competes with the content.
58
+
59
+ ```tsx
60
+ <View style={{ maxWidth: 280 }}>
61
+ <Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Profile</Text>
62
+ <Divider />
63
+ <Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Account</Text>
64
+ <Divider />
65
+ <Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Notifications</Text>
66
+ <Divider />
67
+ <Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Billing</Text>
68
+ </View>
69
+ ```
70
+
71
+ ### With label
72
+
73
+ **Do** — Click a provider: keep the label to a few words and let the buttons carry the options.
74
+
75
+ ```tsx
76
+ <View style={{ width: 320, flexDirection: "column", gap: 8 }}>
77
+ <Button primary block>Sign in</Button>
78
+ <Divider>or continue with</Divider>
79
+ <View style={{ flexDirection: "row", gap: 8 }}>
80
+ <Button outline block style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%" }}>Google</Button>
81
+ <Button outline block style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%" }}>GitHub</Button>
82
+ </View>
83
+ </View>
84
+ ```
85
+
86
+ **Don't** — Click Sign in: a full sentence in the label divider buries the choice.
87
+
88
+ ```tsx
89
+ <View style={{ width: 320, flexDirection: "column", gap: 8 }}>
90
+ <Button primary block>Sign in</Button>
91
+ <Divider>or continue with one of your previously linked third-party accounts</Divider>
92
+ </View>
93
+ ```
94
+
95
+ ### With action
96
+
97
+ **Do** — Click Show more: the button toggles its label and reveals the rest.
98
+
99
+ ```tsx
100
+ <View style={{ width: 320, gap: 6 }}>
101
+ <Text style={{ paddingVertical: 6, fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Logged in from 2 new devices · 3 more entries</Text>
102
+ <Divider>
103
+ <Button ghost small>Show less</Button>
104
+ </Divider>
105
+ </View>
106
+ ```
107
+
108
+ **Don't** — Click the button: an action divider that does nothing is just decoration.
109
+
110
+ ```tsx
111
+ <View style={{ width: 320 }}>
112
+ <Divider>
113
+ <Button ghost small>Show more</Button>
114
+ </Divider>
115
+ </View>
116
+ ```
117
+
118
+ ### Vertical
119
+
120
+ **Do** — Click an action: the vertical rule separates inline actions in a row.
121
+
122
+ ```tsx
123
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
124
+ <Text style={{ fontSize: 14, lineHeight: 20 }}>Edit</Text>
125
+ <Divider vertical style={{ height: 16 }} />
126
+ <Text style={{ fontSize: 14, lineHeight: 20 }}>Delete</Text>
127
+ <Divider vertical style={{ height: 16 }} />
128
+ <Text style={{ fontSize: 14, lineHeight: 20 }}>Share</Text>
129
+ </View>
130
+ ```
131
+
132
+ **Don't** — Click an action: a vertical rule between stacked items reads as a glitch.
133
+
134
+ ```tsx
135
+ <View style={{ flexDirection: "column", alignItems: "flex-start", gap: 8 }}>
136
+ <Text style={{ fontSize: 14, lineHeight: 20 }}>Edit</Text>
137
+ <Divider vertical style={{ height: 16 }} />
138
+ <Text style={{ fontSize: 14, lineHeight: 20 }}>Delete</Text>
139
+ </View>
140
+ ```
@@ -0,0 +1,35 @@
1
+ import { type ViewStyle, type TextStyle } from "react-native";
2
+ import { type ColorTokens } from "../../style/index.js";
3
+
4
+ // Co-located Divider styles. The hairline rule is layout-only except for its
5
+ // fill, which reads a token (so it follows light/dark and the glass surface):
6
+ // `strong` tracks the standard border token, `soft` steps down to muted for a
7
+ // quieter rule. Layout fragments are static objects; the color is a function of
8
+ // the active tokens.
9
+
10
+ export type Orientation = "horizontal" | "vertical";
11
+ export type Emphasis = "soft" | "strong";
12
+
13
+ // The hairline fill color, token-backed. `strong` is the standard border;
14
+ // `soft` steps down to the muted token for a quieter rule (was bg-border / bg-muted).
15
+ export function ruleFill(tokens: ColorTokens, emphasis: Emphasis): ViewStyle {
16
+ return { backgroundColor: emphasis === "strong" ? tokens.border : tokens.muted };
17
+ }
18
+
19
+ // Vertical rule: 1px wide, stretching to the row height it sits in (w-px self-stretch).
20
+ export const verticalRule: ViewStyle = { width: 1, alignSelf: "stretch" };
21
+
22
+ // Plain horizontal hairline spanning the full width (h-px w-full).
23
+ export const horizontalRule: ViewStyle = { height: 1, width: "100%" };
24
+
25
+ // The labeled/action row: a centered node flanked by two hairlines
26
+ // (flex-row items-center gap-3).
27
+ export const labelRow: ViewStyle = { flexDirection: "row", alignItems: "center", gap: 12 };
28
+
29
+ // A flanking hairline inside the labeled row: 1px tall, growing to fill (h-px flex-1).
30
+ export const flankRule: ViewStyle = { height: 1, flexGrow: 1, flexShrink: 1, flexBasis: "0%" };
31
+
32
+ // The centered label text: xs muted (text-xs text-muted-foreground).
33
+ export function labelText(tokens: ColorTokens): TextStyle {
34
+ return { fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] };
35
+ }
@@ -0,0 +1,67 @@
1
+ import { type ReactNode } from "react";
2
+ import { View, Text, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
3
+ import * as s from "./divider.styles.js";
4
+ import { type Orientation, type Emphasis } from "./divider.styles.js";
5
+
6
+ export interface DividerProps {
7
+ /**
8
+ * Optional middle content. With children, a horizontal divider renders a
9
+ * flanking-line + centered-label row (the "with label" / "with action"
10
+ * patterns). A string renders as muted label text; arbitrary nodes (e.g. a
11
+ * button) render as-is for the action pattern. Ignored when `vertical`.
12
+ */
13
+ children?: ReactNode;
14
+ // Orientation (pick one; default is horizontal).
15
+ horizontal?: boolean;
16
+ vertical?: boolean;
17
+ // Emphasis (pick one; default tracks the border token).
18
+ soft?: boolean;
19
+ strong?: boolean;
20
+ /** Escape hatch for layout/positioning composition (width, margins, alignment). */
21
+ style?: StyleProp<ViewStyle>;
22
+ }
23
+
24
+ // First match wins when more than one orientation flag is passed.
25
+ function orientationOf(p: DividerProps): Orientation {
26
+ if (p.vertical) return "vertical";
27
+ return "horizontal";
28
+ }
29
+
30
+ // First match wins when more than one emphasis flag is passed.
31
+ function emphasisOf(p: DividerProps): Emphasis {
32
+ if (p.soft) return "soft";
33
+ return "strong";
34
+ }
35
+
36
+ export function Divider(props: DividerProps) {
37
+ const { children, style } = props;
38
+ const orientation = orientationOf(props);
39
+ const emphasis = emphasisOf(props);
40
+ const { tokens } = useTheme();
41
+ const ruleFill = s.ruleFill(tokens, emphasis);
42
+
43
+ if (orientation === "vertical") {
44
+ // A thin vertical rule that adapts to the row height it sits in.
45
+ return <View style={[s.verticalRule, ruleFill, style]} />;
46
+ }
47
+
48
+ // Horizontal with a label/action in the middle: a centered node flanked by
49
+ // two hairlines (the sepLabel pattern: gap-3, xs muted text).
50
+ if (children != null) {
51
+ const isText = typeof children === "string" || typeof children === "number";
52
+ return (
53
+ <View style={[s.labelRow, style]}>
54
+ <View style={[s.flankRule, ruleFill]} />
55
+ {isText ? (
56
+ <Text style={s.labelText(tokens)}>{children}</Text>
57
+ ) : (
58
+ children
59
+ )}
60
+ <View style={[s.flankRule, ruleFill]} />
61
+ </View>
62
+ );
63
+ }
64
+
65
+ // Plain horizontal hairline spanning the full width.
66
+ return <View style={[s.horizontalRule, ruleFill, style]} />;
67
+ }
@@ -0,0 +1,6 @@
1
+ import { createDropdown } from "./dropdown.shared.js";
2
+ import { androidSkin } from "./dropdown.styles.js";
3
+
4
+ // Material 3 (menu) Dropdown. Metro resolves this file on Android; the docs import it for preview.
5
+ export const Dropdown = createDropdown(androidSkin);
6
+ export type { DropdownProps, DropdownItem } from "./dropdown.shared.js";
@@ -0,0 +1,6 @@
1
+ import { createDropdown } from "./dropdown.shared.js";
2
+ import { iosSkin } from "./dropdown.styles.js";
3
+
4
+ // iOS (HIG pull-down menu) Dropdown. Metro resolves this file on iOS; the docs import it for preview.
5
+ export const Dropdown = createDropdown(iosSkin);
6
+ export type { DropdownProps, DropdownItem } from "./dropdown.shared.js";