@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,272 @@
1
+ import { type ViewStyle, type TextStyle } from "react-native";
2
+ import { type ColorTokens, shadow, alpha } from "../../style/index.js";
3
+
4
+ // Co-located Dialog skins, one per platform, all driven by the brand tokens
5
+ // (passed in from useTheme so they follow light/dark and read as glass when
6
+ // tokens.popover is swapped translucent at the theming level). The BRAND
7
+ // survives on every platform (the indigo `primary` confirm action, the
8
+ // `destructive` red for an irreversible confirm); only the native SHAPE, sizing,
9
+ // title alignment, body type, footer layout, and backdrop dimming change per OS:
10
+ // iOS (iOS 27 / Liquid Glass alert): a centered card (28 radius, `popover`
11
+ // fill, NO border, soft lg shadow) over a ~0.30 black backdrop; a LEFT-aligned
12
+ // ~20pt/700 title, a ~15pt muted LEFT body, and a side-by-side row of CAPSULE
13
+ // action buttons (a gray `secondary` Cancel capsule + a `primary` indigo
14
+ // Confirm capsule; a destructive confirm is a gray capsule with `destructive`
15
+ // red text), NO hairline dividers; press = opacity dim (~0.8).
16
+ // Android (Material 3 basic dialog): a card (28 radius, `popover` elevated
17
+ // surface, shadow lg) over a ~0.32 black scrim; a LEFT-aligned ~22pt title,
18
+ // a 14sp body, and TEXT-button actions (no fill) bottom-RIGHT in a row
19
+ // (Cancel then Confirm) in brand-indigo text, an android_ripple on each, and
20
+ // NO dividers.
21
+ // Web: the established Canvas look (the current dialog, lifted verbatim) — a
22
+ // bordered card (8 radius, `border`, `popover` fill, xl shadow) over a 0.50
23
+ // black backdrop; a 16pt/600 left title, a 14pt muted body, and a
24
+ // right-aligned action row of an outline Cancel + a primary/destructive
25
+ // Confirm Button.
26
+
27
+ export type Size = "xs" | "small" | "medium" | "default" | "large" | "wide";
28
+
29
+ // The dialog card's max width per size, narrowest to widest. The default sits
30
+ // one step wider than `medium`, roomy enough for a short form; `xs`/`small`
31
+ // tighten the panel for a terse message, `large`/`wide` open it up for a longer
32
+ // form. Pixel widths mirror Tailwind's max-w-xs..2xl scale.
33
+ export const PANEL_MAX_WIDTH: Record<Size, number> = {
34
+ xs: 320,
35
+ small: 384,
36
+ medium: 448,
37
+ default: 512,
38
+ large: 576,
39
+ wide: 672,
40
+ };
41
+
42
+ // How the footer renders its actions. The web/Android footer is a horizontal
43
+ // row of Buttons (web) / text buttons (Android); iOS renders a side-by-side row
44
+ // of CAPSULE buttons (the iOS 27 alert: a gray Cancel capsule + a primary
45
+ // Confirm capsule, no dividers). The shell reads this to pick the footer
46
+ // structure.
47
+ export type FooterKind = "buttons" | "capsules";
48
+
49
+ // The contract a platform skin fulfills. The shell renders the backdrop, the
50
+ // centered card (shape/fill/border/shadow from the skin), the title, the body,
51
+ // the optional data-driven form, and the footer; the skin maps each piece onto
52
+ // the active platform's look. The footer kind picks between a Button row
53
+ // (web/Android) and iOS's side-by-side capsule row.
54
+ export interface DialogSkin {
55
+ /** The dim backdrop behind the card (full black at the per-platform opacity). */
56
+ backdrop: (t: ColorTokens) => ViewStyle;
57
+ /** The card layout box: shape, fill, border (or lack of), shadow, padding. The
58
+ * shell supplies the per-size maxWidth inline. */
59
+ card: (t: ColorTokens) => ViewStyle;
60
+ /** The title type: size, weight, alignment, color. */
61
+ title: (t: ColorTokens) => TextStyle;
62
+ /** The body/description type: size, color, alignment. */
63
+ body: (t: ColorTokens) => TextStyle;
64
+ /** Whether the footer is a Button row or iOS's side-by-side capsule row. */
65
+ footerKind: FooterKind;
66
+ /** The footer container (the action row). */
67
+ footer: (t: ColorTokens) => ViewStyle;
68
+ // --- capsule (iOS) footer pieces; null on the Button-row platforms ---------
69
+ /** A single capsule action button's box (shape/padding); each capsule grows to
70
+ * share the row evenly. `confirm` true for the primary/confirm capsule. */
71
+ capsule: ((t: ColorTokens, confirm: boolean, destructive: boolean) => ViewStyle) | null;
72
+ /** The label inside a capsule; `confirm` true for the primary confirm action
73
+ * (drawn on the indigo fill), `destructive` true for an irreversible confirm
74
+ * (red text on a gray capsule). */
75
+ capsuleLabel: ((t: ColorTokens, confirm: boolean, destructive: boolean) => TextStyle) | null;
76
+ /** Opacity applied to a pressed capsule (iOS dims; null elsewhere). */
77
+ capsulePressedOpacity: number | null;
78
+ // --- text-button (Android) footer pieces; null elsewhere -------------------
79
+ /** An Android text-button touch target (no fill, brand-indigo label). */
80
+ textButton: ViewStyle | null;
81
+ /** The Android text-button label; `destructive` reds an irreversible confirm. */
82
+ textButtonLabel: ((t: ColorTokens, destructive: boolean) => TextStyle) | null;
83
+ /** The Android ripple over a text button; null on the other platforms. */
84
+ textButtonRipple: ((t: ColorTokens) => { color: string; borderless: boolean }) | null;
85
+ // --- data-driven body form (Amount + Reason) -------------------------------
86
+ /** Wrapper above the form fields. */
87
+ formBody: ViewStyle;
88
+ /** A field label above an input. */
89
+ fieldLabel: (t: ColorTokens) => TextStyle;
90
+ /** Extra top inset for the second field label. */
91
+ fieldLabelGap: ViewStyle;
92
+ /** The Amount row (currency glyph + input). */
93
+ amountRow: ViewStyle;
94
+ /** The leading currency glyph. */
95
+ currency: (t: ColorTokens) => TextStyle;
96
+ /** The Amount input grows to fill the row. */
97
+ amountInput: ViewStyle;
98
+ }
99
+
100
+ // --- outer shell (identical across platforms) -------------------------------
101
+
102
+ // The outermost wrapper shrinks to its content so the inline trigger sits flush.
103
+ export const root: ViewStyle = { alignSelf: "flex-start" };
104
+
105
+ // Inset between the trigger button and the backdrop when a trigger is rendered.
106
+ export const backdropTriggerGap: ViewStyle = { marginTop: 12 };
107
+
108
+ // The contained backdrop sizing (centered, with presence in the docs preview via
109
+ // an explicit minHeight). The per-platform fill/radius come from the skin.
110
+ export const backdropLayout: ViewStyle = {
111
+ alignItems: "center",
112
+ justifyContent: "center",
113
+ padding: 32,
114
+ minHeight: 220,
115
+ };
116
+
117
+ // The card layout box (width up to its per-size cap). Shape/fill/shadow come
118
+ // from the skin; this owns the box-model that every platform shares.
119
+ export const cardLayout: ViewStyle = { width: "100%", padding: 24 };
120
+
121
+ // Per-size max width cap.
122
+ export function cardWidth(size: Size): ViewStyle {
123
+ return { maxWidth: PANEL_MAX_WIDTH[size] };
124
+ }
125
+
126
+ // ---------- Web: the established Canvas look (lifted verbatim) ----------
127
+ // The current dialog: a card (rounded-lg border bg-popover p-6 shadow-xl) over a
128
+ // bg-black/50 backdrop; a 16/24 600 title, a 14/20 muted-foreground body, and a
129
+ // right-aligned action row (gap-2, mt-6) of an outline Cancel + a primary/
130
+ // destructive Confirm Button.
131
+ export const webSkin: DialogSkin = {
132
+ backdrop: () => ({ borderRadius: 8, backgroundColor: alpha("#000000", 0.5) }),
133
+ card: (t) => ({
134
+ borderRadius: 8,
135
+ borderWidth: 1,
136
+ borderColor: t.border,
137
+ backgroundColor: t.popover,
138
+ ...shadow("xl"),
139
+ }),
140
+ title: (t) => ({ fontSize: 16, lineHeight: 24, fontWeight: "600", color: t["popover-foreground"] }),
141
+ body: (t) => ({ fontSize: 14, lineHeight: 20, color: t["muted-foreground"], marginTop: 8 }),
142
+ footerKind: "buttons",
143
+ footer: () => ({ flexDirection: "row", justifyContent: "flex-end", gap: 8, marginTop: 24 }),
144
+ capsule: null,
145
+ capsuleLabel: null,
146
+ capsulePressedOpacity: null,
147
+ textButton: null,
148
+ textButtonLabel: null,
149
+ textButtonRipple: null,
150
+ formBody: { marginTop: 20 },
151
+ fieldLabel: (t) => ({ fontSize: 14, lineHeight: 20, fontWeight: "500", color: t.foreground, marginBottom: 6 }),
152
+ fieldLabelGap: { marginTop: 16 },
153
+ amountRow: { flexDirection: "row", alignItems: "center" },
154
+ currency: (t) => ({ fontSize: 14, lineHeight: 20, color: t["muted-foreground"], marginRight: 8 }),
155
+ amountInput: { flexGrow: 1, flexShrink: 1, flexBasis: "0%" },
156
+ };
157
+
158
+ // ---------- iOS (iOS 27 alert): rounded card, no border, side-by-side capsules ----------
159
+ // iOS 27 (iOS 26+ / Liquid Glass) alert: a centered card (28pt radius) over the
160
+ // `popover` fill with NO border and a soft shadow, on a ~0.30 black backdrop; a
161
+ // LEFT-aligned 20pt/700 title, a 15pt muted LEFT body, and a side-by-side row of
162
+ // CAPSULE action buttons — a gray `secondary` Cancel capsule + a `primary`
163
+ // indigo Confirm capsule, with NO hairline dividers. A destructive confirm keeps
164
+ // the gray `secondary` capsule but draws its label in the `destructive` red. Each
165
+ // capsule shares the row evenly; a pressed capsule dims (no ripple) at 0.8.
166
+ const IOS_RADIUS = 28;
167
+ const IOS_CAPSULE_RADIUS = 22;
168
+ export const iosSkin: DialogSkin = {
169
+ backdrop: () => ({ borderRadius: 8, backgroundColor: alpha("#000000", 0.3) }),
170
+ card: (t) => ({
171
+ borderRadius: IOS_RADIUS,
172
+ backgroundColor: t.popover,
173
+ ...shadow("lg"),
174
+ }),
175
+ title: (t) => ({
176
+ fontSize: 20,
177
+ lineHeight: 25,
178
+ fontWeight: "700",
179
+ color: t["popover-foreground"],
180
+ textAlign: "left",
181
+ }),
182
+ body: (t) => ({
183
+ fontSize: 15,
184
+ lineHeight: 20,
185
+ color: t["muted-foreground"],
186
+ textAlign: "left",
187
+ marginTop: 8,
188
+ }),
189
+ footerKind: "capsules",
190
+ footer: () => ({ flexDirection: "row", gap: 12, marginTop: 20 }),
191
+ // A capsule sized to share the row evenly. The Confirm capsule fills with the
192
+ // indigo `primary`; Cancel and a destructive Confirm both fill with the gray
193
+ // `secondary` surface (the destructive variant only reds its label).
194
+ capsule: (t, confirm, destructive) => ({
195
+ flexGrow: 1,
196
+ flexShrink: 1,
197
+ flexBasis: "0%",
198
+ alignItems: "center",
199
+ justifyContent: "center",
200
+ paddingVertical: 14,
201
+ paddingHorizontal: 16,
202
+ minHeight: 50,
203
+ borderRadius: IOS_CAPSULE_RADIUS,
204
+ backgroundColor: confirm && !destructive ? t.primary : t.secondary,
205
+ }),
206
+ capsuleLabel: (t, confirm, destructive) => ({
207
+ fontSize: 17,
208
+ lineHeight: 22,
209
+ fontWeight: "600",
210
+ color: destructive
211
+ ? t.destructive
212
+ : confirm
213
+ ? t["primary-foreground"]
214
+ : t["secondary-foreground"],
215
+ textAlign: "center",
216
+ }),
217
+ capsulePressedOpacity: 0.8,
218
+ textButton: null,
219
+ textButtonLabel: null,
220
+ textButtonRipple: null,
221
+ formBody: { marginTop: 16 },
222
+ fieldLabel: (t) => ({ fontSize: 13, lineHeight: 18, fontWeight: "500", color: t.foreground, marginBottom: 6 }),
223
+ fieldLabelGap: { marginTop: 14 },
224
+ amountRow: { flexDirection: "row", alignItems: "center" },
225
+ currency: (t) => ({ fontSize: 15, lineHeight: 20, color: t["muted-foreground"], marginRight: 8 }),
226
+ amountInput: { flexGrow: 1, flexShrink: 1, flexBasis: "0%" },
227
+ };
228
+
229
+ // ---------- Android (Material 3 basic dialog): 28 radius, left title, text-button row ----------
230
+ // M3 basic dialog: a card (28dp radius) over the `popover` ELEVATED surface (soft
231
+ // shadow) on a ~0.32 black scrim; a LEFT-aligned ~22sp title, a 14sp body, and
232
+ // TEXT-button actions (no fill) bottom-RIGHT in a row — Cancel then Confirm — in
233
+ // brand-indigo text, an android_ripple on each, and NO dividers.
234
+ const ANDROID_RADIUS = 28;
235
+ export const androidSkin: DialogSkin = {
236
+ backdrop: () => ({ borderRadius: 8, backgroundColor: alpha("#000000", 0.32) }),
237
+ card: (t) => ({
238
+ borderRadius: ANDROID_RADIUS,
239
+ backgroundColor: t.popover,
240
+ ...shadow("lg"),
241
+ }),
242
+ title: (t) => ({ fontSize: 22, lineHeight: 28, fontWeight: "500", color: t["popover-foreground"] }),
243
+ body: (t) => ({ fontSize: 14, lineHeight: 20, color: t["muted-foreground"], marginTop: 12 }),
244
+ footerKind: "buttons",
245
+ footer: () => ({ flexDirection: "row", justifyContent: "flex-end", alignItems: "center", gap: 8, marginTop: 24 }),
246
+ capsule: null,
247
+ capsuleLabel: null,
248
+ capsulePressedOpacity: null,
249
+ // A flat text-button: no fill, comfortable touch target, rounded for the ripple.
250
+ textButton: {
251
+ alignItems: "center",
252
+ justifyContent: "center",
253
+ paddingHorizontal: 12,
254
+ paddingVertical: 10,
255
+ borderRadius: 20,
256
+ minHeight: 40,
257
+ },
258
+ textButtonLabel: (t, destructive) => ({
259
+ fontSize: 14,
260
+ lineHeight: 20,
261
+ fontWeight: "500",
262
+ letterSpacing: 0.1,
263
+ color: destructive ? t.destructive : t.primary,
264
+ }),
265
+ textButtonRipple: (t) => ({ color: alpha(t.primary, 0.12), borderless: false }),
266
+ formBody: { marginTop: 20 },
267
+ fieldLabel: (t) => ({ fontSize: 14, lineHeight: 20, fontWeight: "500", color: t.foreground, marginBottom: 6 }),
268
+ fieldLabelGap: { marginTop: 16 },
269
+ amountRow: { flexDirection: "row", alignItems: "center" },
270
+ currency: (t) => ({ fontSize: 14, lineHeight: 20, color: t["muted-foreground"], marginRight: 8 }),
271
+ amountInput: { flexGrow: 1, flexShrink: 1, flexBasis: "0%" },
272
+ };
@@ -0,0 +1,6 @@
1
+ import { createDialog } from "./dialog.shared.js";
2
+ import { webSkin } from "./dialog.styles.js";
3
+
4
+ // Web Dialog (the base; Metro falls back to it on native, web bundlers resolve it).
5
+ export const Dialog = createDialog(webSkin);
6
+ export type { DialogProps } from "./dialog.shared.js";
@@ -0,0 +1,116 @@
1
+ # Filter Panels
2
+
3
+ Sidebar filter rail with chip pills for active filters.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ <FilterPanel
9
+ bordered
10
+ activeCount={2}
11
+ groups={[
12
+ { title: "Status", options: [
13
+ { label: "Active", checked: true, count: "128" },
14
+ { label: "Pending", count: "12", checked: false },
15
+ { label: "Archived", count: "2", checked: false }
16
+ ] },
17
+ { title: "Schema", options: [
18
+ { label: "Default", checked: true, count: "96" },
19
+ { label: "Custom", count: "46", checked: false }
20
+ ] },
21
+ { title: "MFA", options: [
22
+ { label: "Enabled", count: "84", checked: false },
23
+ { label: "Disabled", count: "58", checked: false }
24
+ ] }
25
+ ]}
26
+ />
27
+ ```
28
+
29
+ ## Do & Don't
30
+
31
+ ### Sidebar
32
+
33
+ **Do** — Give each chip a × so a single filter can be removed in place, and keep it in sync with the sidebar checkbox.
34
+
35
+ ```tsx
36
+ <View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
37
+ <Pressable style={{ flexDirection: "row", alignItems: "center", alignSelf: "flex-start", gap: 4, borderRadius: 9999, backgroundColor: tokens.primary, paddingHorizontal: 10, paddingVertical: 4 }}>
38
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens["primary-foreground"] }}>Active</Text>
39
+ <Icon x primaryForeground size={12} />
40
+ </Pressable>
41
+ <Pressable style={{ flexDirection: "row", alignItems: "center", alignSelf: "flex-start", gap: 4, borderRadius: 9999, backgroundColor: tokens.primary, paddingHorizontal: 10, paddingVertical: 4 }}>
42
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens["primary-foreground"] }}>Default</Text>
43
+ <Icon x primaryForeground size={12} />
44
+ </Pressable>
45
+ </View>
46
+ ```
47
+
48
+ **Don't** — Active-filter chips with no remove affordance leave no way to clear one filter without hunting back through the sidebar.
49
+
50
+ ```tsx
51
+ <View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
52
+ <View style={{ flexDirection: "row", alignItems: "center", alignSelf: "flex-start", borderRadius: 9999, backgroundColor: tokens.primary, paddingHorizontal: 10, paddingVertical: 4 }}>
53
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens["primary-foreground"] }}>Active</Text>
54
+ </View>
55
+ <View style={{ flexDirection: "row", alignItems: "center", alignSelf: "flex-start", borderRadius: 9999, backgroundColor: tokens.primary, paddingHorizontal: 10, paddingVertical: 4 }}>
56
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens["primary-foreground"] }}>Default</Text>
57
+ </View>
58
+ </View>
59
+ ```
60
+
61
+ ### Inline
62
+
63
+ **Do** — Surface two or three primary filters and tuck the rest behind "+ Add filter" so the bar stays one scannable row.
64
+
65
+ ```tsx
66
+ <View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 8 }}>
67
+ <Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, backgroundColor: tokens.primary, paddingHorizontal: 12 }}>
68
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens["primary-foreground"] }}>Status</Text>
69
+ <Icon chevronDown primaryForeground size={12} />
70
+ </Pressable>
71
+ <Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
72
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Role</Text>
73
+ <Icon chevronDown size={12} />
74
+ </Pressable>
75
+ <Button ghost small style={{ color: tokens.primary }}>+ Add filter</Button>
76
+ </View>
77
+ ```
78
+
79
+ **Don't** — Eight inline dropdowns wrap into a wall of buttons, which defeats the compact bar; that volume of filtering belongs in the sidebar rail.
80
+
81
+ ```tsx
82
+ <View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 8 }}>
83
+ <Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
84
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Status</Text>
85
+ <Icon chevronDown size={12} />
86
+ </Pressable>
87
+ <Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
88
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Role</Text>
89
+ <Icon chevronDown size={12} />
90
+ </Pressable>
91
+ <Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
92
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Schema</Text>
93
+ <Icon chevronDown size={12} />
94
+ </Pressable>
95
+ <Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
96
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>MFA</Text>
97
+ <Icon chevronDown size={12} />
98
+ </Pressable>
99
+ <Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
100
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Region</Text>
101
+ <Icon chevronDown size={12} />
102
+ </Pressable>
103
+ <Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
104
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Created</Text>
105
+ <Icon chevronDown size={12} />
106
+ </Pressable>
107
+ <Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
108
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Last seen</Text>
109
+ <Icon chevronDown size={12} />
110
+ </Pressable>
111
+ <Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
112
+ <Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Team</Text>
113
+ <Icon chevronDown size={12} />
114
+ </Pressable>
115
+ </View>
116
+ ```
@@ -0,0 +1,83 @@
1
+ import { type ViewStyle, type TextStyle } from "react-native";
2
+ import { type ColorTokens } from "../../style/index.js";
3
+
4
+ // Co-located FilterPanel styles. Layout-only fragments (the fixed panel width,
5
+ // the header/group/row rows, the density gaps and paddings) are static objects;
6
+ // the surface fill + border reads the active tokens (so `bordered` follows
7
+ // light/dark and goes translucent under glass). Density (compact vs. base) is
8
+ // resolved by the component and selects the matching padding/gap fragment.
9
+
10
+ export type Density = "compact" | "base";
11
+
12
+ // --- panel surface ----------------------------------------------------------
13
+
14
+ // Fixed panel width (w-[280px]) and column layout, shared by both surfaces.
15
+ export const panelBase: ViewStyle = { width: 280, flexDirection: "column" };
16
+
17
+ // `bordered` wraps the panel as a rounded card: a border on the card fill. The
18
+ // card token goes translucent under glass, so don't hardcode its hex.
19
+ export function borderedSurface(tokens: ColorTokens): ViewStyle {
20
+ return { borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card };
21
+ }
22
+
23
+ // Panel inset per density (p-3 compact / p-4 base).
24
+ export const panelPad: Record<Density, ViewStyle> = {
25
+ compact: { padding: 12 },
26
+ base: { padding: 16 },
27
+ };
28
+
29
+ // Vertical rhythm between the header and groups, and between groups
30
+ // (gap-3 compact / gap-5 base).
31
+ export const panelStack: Record<Density, ViewStyle> = {
32
+ compact: { gap: 12 },
33
+ base: { gap: 20 },
34
+ };
35
+
36
+ // Row spacing inside a group, and between a group's title and its options
37
+ // (gap-1.5 compact / gap-2 base). Shared by the group column and its option list.
38
+ export const groupGap: Record<Density, ViewStyle> = {
39
+ compact: { gap: 6 },
40
+ base: { gap: 8 },
41
+ };
42
+
43
+ // --- header -----------------------------------------------------------------
44
+
45
+ // Header row: title cluster on the left, the Clear action on the right.
46
+ export const headerRow: ViewStyle = {
47
+ flexDirection: "row",
48
+ alignItems: "center",
49
+ justifyContent: "space-between",
50
+ };
51
+
52
+ // Title + active-count cluster (a row with a small gap).
53
+ export const titleCluster: ViewStyle = { flexDirection: "row", alignItems: "center", gap: 8 };
54
+
55
+ // "Filters" heading: small, semibold, on the foreground token.
56
+ export function titleText(tokens: ColorTokens): TextStyle {
57
+ return { fontSize: 14, lineHeight: 20, fontWeight: "600", color: tokens.foreground };
58
+ }
59
+
60
+ // --- groups -----------------------------------------------------------------
61
+
62
+ // A group column (the title above its option list).
63
+ export const groupColumn: ViewStyle = { flexDirection: "column" };
64
+
65
+ // Group heading: extra-small, medium, uppercase, wide tracking, muted.
66
+ export function groupTitle(tokens: ColorTokens): TextStyle {
67
+ return {
68
+ fontSize: 12,
69
+ lineHeight: 16,
70
+ fontWeight: "500",
71
+ textTransform: "uppercase",
72
+ letterSpacing: 0.4,
73
+ color: tokens["muted-foreground"],
74
+ };
75
+ }
76
+
77
+ // One option row: checkbox on the left, optional count badge on the right.
78
+ export const optionRow: ViewStyle = {
79
+ flexDirection: "row",
80
+ alignItems: "center",
81
+ justifyContent: "space-between",
82
+ gap: 8,
83
+ };
@@ -0,0 +1,91 @@
1
+ import { View, Text, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
2
+ import { Badge } from "../../atoms/badge/badge.js";
3
+ import { Button } from "../../atoms/button/button.js";
4
+ import { Checkbox } from "../../atoms/checkbox/checkbox.js";
5
+ import * as s from "./filter-panel.styles.js";
6
+
7
+ export interface FilterOption {
8
+ /** Row label, shown beside the checkbox. */
9
+ label: string;
10
+ /** Controlled checked state for this option. */
11
+ checked?: boolean;
12
+ /** Optional trailing count, rendered as a secondary badge. */
13
+ count?: string;
14
+ }
15
+
16
+ export interface FilterGroup {
17
+ /** Group heading, rendered uppercase and muted. */
18
+ title: string;
19
+ /** The checkbox options under this group. */
20
+ options: FilterOption[];
21
+ }
22
+
23
+ export interface FilterPanelProps {
24
+ /** Filter groups, each a heading plus its checkbox options. */
25
+ groups: FilterGroup[];
26
+ /** Active-filter count shown next to the "Filters" title in the header. */
27
+ activeCount?: number;
28
+ /** Fired when the header "Clear" action is pressed. */
29
+ onClear?: () => void;
30
+ /** Fired when an option row toggles, with its group/option indexes and next value. */
31
+ onChange?: (groupIndex: number, optionIndex: number, next: boolean) => void;
32
+ // Surface (pick one path): a rounded, bordered card vs. a bare panel.
33
+ bordered?: boolean;
34
+ // Density (pick one): tighten the panel's padding and row spacing.
35
+ compact?: boolean;
36
+ /** Escape hatch for layout/positioning composition (mainly width, margins). */
37
+ style?: StyleProp<ViewStyle>;
38
+ }
39
+
40
+ // Density precedence when more than one is passed: first match wins. There is a
41
+ // single density flag today, so this collapses to compact vs. the default.
42
+ function densityOf(p: FilterPanelProps): s.Density {
43
+ if (p.compact) return "compact";
44
+ return "base";
45
+ }
46
+
47
+ export function FilterPanel(props: FilterPanelProps) {
48
+ const { groups, activeCount, onClear, onChange, bordered, style } = props;
49
+ const { tokens } = useTheme();
50
+ const density = densityOf(props);
51
+
52
+ return (
53
+ <View
54
+ style={[
55
+ s.panelBase,
56
+ // `bordered` wraps it as a rounded card with a border and a card fill;
57
+ // the bare panel keeps the same width but drops the chrome.
58
+ bordered ? s.borderedSurface(tokens) : null,
59
+ s.panelPad[density],
60
+ s.panelStack[density],
61
+ style,
62
+ ]}
63
+ >
64
+ <View style={s.headerRow}>
65
+ <View style={s.titleCluster}>
66
+ <Text style={s.titleText(tokens)}>Filters</Text>
67
+ {activeCount != null ? <Badge secondary>{String(activeCount)}</Badge> : null}
68
+ </View>
69
+ <Button ghost small onPress={onClear}>
70
+ Clear
71
+ </Button>
72
+ </View>
73
+
74
+ {groups.map((group, gi) => (
75
+ <View key={gi} style={[s.groupColumn, s.groupGap[density]]}>
76
+ <Text style={s.groupTitle(tokens)}>{group.title}</Text>
77
+ <View style={[s.groupColumn, s.groupGap[density]]}>
78
+ {group.options.map((option, oi) => (
79
+ <View key={oi} style={s.optionRow}>
80
+ <Checkbox checked={option.checked} onChange={(next) => onChange?.(gi, oi, next)}>
81
+ {option.label}
82
+ </Checkbox>
83
+ {option.count != null ? <Badge secondary>{option.count}</Badge> : null}
84
+ </View>
85
+ ))}
86
+ </View>
87
+ </View>
88
+ ))}
89
+ </View>
90
+ );
91
+ }
@@ -0,0 +1,13 @@
1
+ // Organisms: the React Native UI kit components at the organisms atomic level.
2
+ export * from "./calendar/calendar.js";
3
+ export * from "./charts/charts.js";
4
+ export * from "./command/command.js";
5
+ export * from "./data-table/data-table.js";
6
+ export * from "./dialog/dialog.js";
7
+ export * from "./filter-panel/filter-panel.js";
8
+ export * from "./navbars/navbars.js";
9
+ export * from "./overlays/overlays.js";
10
+ export * from "./row-menu/row-menu.js";
11
+ export * from "./sidebar/sidebar.js";
12
+ export * from "./stepper/stepper.js";
13
+ export * from "./tabs/tabs.js";
@@ -0,0 +1,6 @@
1
+ import { createNavbar } from "./navbars.shared.js";
2
+ import { androidSkin } from "./navbars.styles.js";
3
+
4
+ // Material 3 (top app bar) Navbar. Metro resolves this file on Android; the docs import it for preview.
5
+ export const Navbar = createNavbar(androidSkin);
6
+ export type { NavbarProps } from "./navbars.shared.js";
@@ -0,0 +1,6 @@
1
+ import { createNavbar } from "./navbars.shared.js";
2
+ import { iosSkin } from "./navbars.styles.js";
3
+
4
+ // iOS (HIG navigation bar) Navbar. Metro resolves this file on iOS; the docs import it for preview.
5
+ export const Navbar = createNavbar(iosSkin);
6
+ export type { NavbarProps } from "./navbars.shared.js";