@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,144 @@
1
+ # Navbars
2
+
3
+ Topbars with navigation links, search, and action buttons. Used as the primary app-level navigation.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ <Navbar
9
+ brand="Canvas"
10
+ links={["Dashboard", "Users", "Settings"]}
11
+ active={0}
12
+ actionLabel="New"
13
+ avatar="RC"
14
+ bordered
15
+ />
16
+ ```
17
+
18
+ ## Variants
19
+
20
+ ### Layout - search
21
+
22
+ ```tsx
23
+ <Navbar
24
+ brand="Canvas"
25
+ links={["Dashboard", "Users", "Settings"]}
26
+ active={0}
27
+ avatar="RC"
28
+ bordered
29
+ />
30
+ ```
31
+
32
+ ### Layout - mobile
33
+
34
+ ```tsx
35
+ <Navbar
36
+ brand="Canvas"
37
+ links={["Dashboard", "Users", "Settings"]}
38
+ active={0}
39
+ actionLabel="New"
40
+ avatar="RC"
41
+ />
42
+ ```
43
+
44
+ ## Do & Don't
45
+
46
+ ### Standard topbar
47
+
48
+ **Do** — Keep a few primary links inline and fold the rest behind a More menu.
49
+
50
+ ```tsx
51
+ <Navbar bordered brand="Canvas" active={0} links={["Dashboard", "Users", "Settings"]} actionLabel="New" avatar="RC" />
52
+ ```
53
+
54
+ **Don't** — Cramming every destination into the bar wraps the row and buries the primary links.
55
+
56
+ ```tsx
57
+ <Navbar bordered brand="Canvas" active={0} links={[
58
+ "Dashboard",
59
+ "Users",
60
+ "Settings",
61
+ "Billing",
62
+ "Reports",
63
+ "Integrations",
64
+ "Audit",
65
+ "Webhooks"
66
+ ]} avatar="RC" />
67
+ ```
68
+
69
+ ### With search bar
70
+
71
+ **Do** — Use a button that opens the command palette and advertise the ⌘K shortcut.
72
+
73
+ ```tsx
74
+ <View style={{ width: "100%", overflow: "hidden", borderRadius: 8, borderWidth: 1, borderColor: tokens.border }}>
75
+ <View style={{ flexDirection: "row", height: 56, alignItems: "center", gap: 8, backgroundColor: tokens.card, paddingHorizontal: 16 }}>
76
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "600", color: tokens.foreground }}>Canvas</Text>
77
+ <View style={{ marginHorizontal: 16, maxWidth: 400, flexGrow: 1, flexShrink: 1, flexBasis: "0%" }}>
78
+ <Pressable style={({ pressed }) => [{ flexDirection: "row", height: 34, width: "100%", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, paddingHorizontal: 10 }, pressed ? { opacity: 0.9 } : null]}>
79
+ <Icon search muted size={13} />
80
+ <Text style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", textAlign: "left", fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Search…</Text>
81
+ <Kbd>⌘K</Kbd>
82
+ </Pressable>
83
+ </View>
84
+ </View>
85
+ </View>
86
+ ```
87
+
88
+ **Don't** — A live text field in the bar reads as a form input and offers no keyboard affordance.
89
+
90
+ ```tsx
91
+ <View style={{ width: "100%", overflow: "hidden", borderRadius: 8, borderWidth: 1, borderColor: tokens.border }}>
92
+ <View style={{ flexDirection: "row", height: 56, alignItems: "center", gap: 8, backgroundColor: tokens.card, paddingHorizontal: 16 }}>
93
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "600", color: tokens.foreground }}>Canvas</Text>
94
+ <View style={{ marginHorizontal: 16, maxWidth: 400, flexGrow: 1, flexShrink: 1, flexBasis: "0%" }}>
95
+ <TextInput placeholder="Search…" style={{ height: 36, width: "100%", borderRadius: 6, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.background, paddingHorizontal: 12, paddingVertical: 4, fontSize: 14, lineHeight: 20, color: tokens.foreground }} />
96
+ </View>
97
+ </View>
98
+ </View>
99
+ ```
100
+
101
+ ### Mobile
102
+
103
+ **Do** — Collapse the links into a hamburger and keep only the logo and avatar in the bar.
104
+
105
+ ```tsx
106
+ <View style={{ width: "100%", maxWidth: 360, overflow: "hidden", borderRadius: 8, borderWidth: 1, borderColor: tokens.border }}>
107
+ <View style={{ flexDirection: "row", height: 56, alignItems: "center", gap: 8, backgroundColor: tokens.card, paddingHorizontal: 12 }}>
108
+ <Pressable style={({ pressed }) => [{ height: 36, width: 36, alignItems: "center", justifyContent: "center", borderRadius: 6 }, pressed ? { backgroundColor: tokens.accent } : null]}>
109
+ <View style={{ width: 18, gap: 4 }}>
110
+ <View style={{ height: 2, width: "100%", borderRadius: 9999, backgroundColor: tokens["muted-foreground"] }} />
111
+ <View style={{ height: 2, width: "100%", borderRadius: 9999, backgroundColor: tokens["muted-foreground"] }} />
112
+ <View style={{ height: 2, width: "100%", borderRadius: 9999, backgroundColor: tokens["muted-foreground"] }} />
113
+ </View>
114
+ </Pressable>
115
+ <Text style={{ fontSize: 13, fontWeight: "600", color: tokens.foreground }}>Canvas</Text>
116
+ <View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%" }} />
117
+ <Avatar small src="/rachel-chen.jpg" name="RC" />
118
+ </View>
119
+ </View>
120
+ ```
121
+
122
+ **Don't** — A full horizontal nav at phone width wraps onto a second row and crowds out the logo.
123
+
124
+ ```tsx
125
+ <View style={{ width: "100%", maxWidth: 360, overflow: "hidden", borderRadius: 8, borderWidth: 1, borderColor: tokens.border }}>
126
+ <View style={{ flexDirection: "row", height: 56, alignItems: "center", gap: 4, backgroundColor: tokens.card, paddingHorizontal: 12 }}>
127
+ <Text style={{ fontSize: 13, fontWeight: "600", color: tokens.foreground }}>Canvas</Text>
128
+ <View style={{ marginLeft: 8, flexDirection: "row", flexWrap: "wrap", gap: 4 }}>
129
+ <Pressable style={({ pressed }) => [{ borderRadius: 6, backgroundColor: tokens.accent, paddingHorizontal: 12, paddingVertical: 6 }, pressed ? { opacity: 0.9 } : null]}>
130
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Dashboard</Text>
131
+ </Pressable>
132
+ <Pressable style={({ pressed }) => [{ borderRadius: 6, paddingHorizontal: 12, paddingVertical: 6 }, pressed ? { opacity: 0.9 } : null]}>
133
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens["muted-foreground"] }}>Users</Text>
134
+ </Pressable>
135
+ <Pressable style={({ pressed }) => [{ borderRadius: 6, paddingHorizontal: 12, paddingVertical: 6 }, pressed ? { opacity: 0.9 } : null]}>
136
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens["muted-foreground"] }}>Settings</Text>
137
+ </Pressable>
138
+ <Pressable style={({ pressed }) => [{ borderRadius: 6, paddingHorizontal: 12, paddingVertical: 6 }, pressed ? { opacity: 0.9 } : null]}>
139
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens["muted-foreground"] }}>Billing</Text>
140
+ </Pressable>
141
+ </View>
142
+ </View>
143
+ </View>
144
+ ```
@@ -0,0 +1,137 @@
1
+ import { type ReactNode } from "react";
2
+ import { View, Pressable, Text, useTheme, type ColorTokens, type StyleProp, type ViewStyle, type TextStyle } from "../../style/index.js";
3
+ import { Button } from "../../atoms/button/button.js";
4
+ import { Avatar } from "../../atoms/avatar/avatar.js";
5
+ import { type Surface } from "./navbars.styles.js";
6
+
7
+ // Shared Navbar shell. The structure (the left brand + links cluster, the right
8
+ // action + avatar cluster), the surface-axis precedence, the active-link
9
+ // selection, the accessibility, and the press handlers live here once; a
10
+ // platform file supplies only its skin (the native bar height/padding, the
11
+ // surface treatment, the brand type, the link tile/label, and the press
12
+ // feedback) and calls createNavbar.
13
+ //
14
+ // The navbar is the primary app-level topbar: a brand on the left, a row of
15
+ // navigation links in the middle with one active, and actions on the right (a
16
+ // primary action button and/or an account avatar). It is a fixed-height
17
+ // horizontal bar, laid out desktop-first.
18
+ //
19
+ // Boolean-prop API: one boolean per option, grouped by axis, first-match
20
+ // precedence within an axis (mirrors Button's intentOf). The surface axis sets
21
+ // how the bar sits on the page: the default rests flush with a bottom hairline,
22
+ // `bordered` boxes it on all four sides with a rounded outline, and `floating`
23
+ // lifts it as a rounded, shadowed card detached from the page edge. Each
24
+ // platform skin renders that axis in its own native idiom (iOS keeps a slim
25
+ // hairline-separated bar; Android keeps a flat borderless top app bar).
26
+
27
+ // The platform-varying surface. Everything color/shape-bearing the bar needs
28
+ // lives here, built from the active tokens (so each follows light/dark/glass).
29
+ export interface NavbarSkin {
30
+ /** iOS/web dim a pressed link; Android uses a ripple instead (null). */
31
+ pressedOpacity: number | null;
32
+ /** Android ripple over a pressed link; null on iOS/web. */
33
+ ripple: ((t: ColorTokens) => { color: string; borderless: boolean }) | null;
34
+
35
+ /** The bar row: height, padding, and flex layout. */
36
+ bar: (t: ColorTokens) => ViewStyle;
37
+ /** The token-driven background fill (follows the scheme/glass surface). */
38
+ surface: (t: ColorTokens) => ViewStyle;
39
+ /** The surface-axis treatment (hairline / outline / floating card). */
40
+ surfaceContainer: (t: ColorTokens, surface: Surface) => ViewStyle;
41
+
42
+ /** The left cluster row (brand + links). */
43
+ leftGroup: (t: ColorTokens) => ViewStyle;
44
+ /** The brand wordmark type. */
45
+ brand: (t: ColorTokens) => TextStyle;
46
+ /** The links row. */
47
+ linksRow: (t: ColorTokens) => ViewStyle;
48
+ /** A nav link tile (padding + active fill). */
49
+ linkTile: (t: ColorTokens, active: boolean) => ViewStyle;
50
+ /** A nav link label (weight + active/inactive color). */
51
+ linkLabel: (t: ColorTokens, active: boolean) => TextStyle;
52
+
53
+ /** The right cluster row (action + avatar). */
54
+ rightGroup: (t: ColorTokens) => ViewStyle;
55
+ }
56
+
57
+ export interface NavbarProps {
58
+ /** Brand or product name shown at the left, in a semibold face. */
59
+ brand: string;
60
+ /** Ordered navigation link labels rendered in the middle row. */
61
+ links: string[];
62
+ /** Index of the active link (its label reads in the foreground color). */
63
+ active?: number;
64
+ /** Optional primary action; renders a <Button primary small> on the right. */
65
+ actionLabel?: string;
66
+ /** Called when the action button is pressed. */
67
+ onAction?: () => void;
68
+ /** Optional account initials/name; renders an <Avatar small> on the right. */
69
+ avatar?: string;
70
+ /** Called when a nav link is pressed, with its index. */
71
+ onSelect?: (index: number) => void;
72
+ // Surface (pick one; default is the flush bottom-hairline bar).
73
+ bordered?: boolean;
74
+ floating?: boolean;
75
+ children?: ReactNode;
76
+ /** Escape hatch for layout/positioning composition (width, margins). */
77
+ style?: StyleProp<ViewStyle>;
78
+ }
79
+
80
+ // Surface precedence when more than one is passed: first match wins.
81
+ function surfaceOf(p: NavbarProps): Surface {
82
+ if (p.bordered) return "bordered";
83
+ if (p.floating) return "floating";
84
+ return "default";
85
+ }
86
+
87
+ /** Build a Navbar component from a platform skin. */
88
+ export function createNavbar(skin: NavbarSkin) {
89
+ return function Navbar(props: NavbarProps) {
90
+ const { brand, links, active = 0, actionLabel, onAction, avatar, onSelect, style } = props;
91
+ const { tokens } = useTheme();
92
+ const surface = surfaceOf(props);
93
+
94
+ const container: StyleProp<ViewStyle> = [
95
+ skin.bar(tokens),
96
+ skin.surface(tokens),
97
+ skin.surfaceContainer(tokens, surface),
98
+ style,
99
+ ];
100
+
101
+ return (
102
+ <View style={container}>
103
+ <View style={skin.leftGroup(tokens)}>
104
+ <Text style={skin.brand(tokens)}>{brand}</Text>
105
+ <View style={skin.linksRow(tokens)}>
106
+ {links.map((link, index) => {
107
+ const isActive = index === active;
108
+ return (
109
+ <Pressable
110
+ key={`${link}-${index}`}
111
+ onPress={onSelect ? () => onSelect(index) : undefined}
112
+ android_ripple={skin.ripple ? skin.ripple(tokens) : undefined}
113
+ accessibilityRole="link"
114
+ accessibilityState={{ selected: isActive }}
115
+ style={({ pressed }) => [
116
+ skin.linkTile(tokens, isActive),
117
+ skin.pressedOpacity != null && pressed ? { opacity: skin.pressedOpacity } : null,
118
+ ]}
119
+ >
120
+ <Text style={skin.linkLabel(tokens, isActive)}>{link}</Text>
121
+ </Pressable>
122
+ );
123
+ })}
124
+ </View>
125
+ </View>
126
+ <View style={skin.rightGroup(tokens)}>
127
+ {actionLabel ? (
128
+ <Button primary small onPress={onAction}>
129
+ {actionLabel}
130
+ </Button>
131
+ ) : null}
132
+ {avatar ? <Avatar small name={avatar} /> : null}
133
+ </View>
134
+ </View>
135
+ );
136
+ };
137
+ }
@@ -0,0 +1,251 @@
1
+ import { type ViewStyle, type TextStyle } from "react-native";
2
+ import { type ColorTokens, shadow, alpha } from "../../style/index.js";
3
+ import { type NavbarSkin } from "./navbars.shared.js";
4
+
5
+ // Co-located Navbar skins, one per platform. The shell resolves the surface axis
6
+ // (default / bordered / floating), the brand + link cluster, and the action +
7
+ // avatar cluster; the skin supplies only the native SHAPE, sizing, label weight,
8
+ // fill, separator, and press feedback. The BRAND survives on every platform (the
9
+ // indigo `primary` token and the semantic tokens, never a platform default), so
10
+ // each follows light/dark and the glass surface.
11
+ //
12
+ // iOS (iOS 26+ / Liquid Glass navigation bar): a slim 44pt bar with a 1px
13
+ // hairline bottom separator (no heavy shadow); the brand reads as a ~17pt
14
+ // weight-600 SF title. Bar-button items are CIRCULAR tinted toolbar buttons
15
+ // (a translucent neutral `secondary` circle), the glyph/label tints the
16
+ // brand `primary`; the active item reads as a brand-FILLED circle. Press =
17
+ // opacity dim 0.8.
18
+ // Android (M3 top app bar): a ~56dp bar with a ~22sp weight-500 LEADING title;
19
+ // no hairline border, a flat `background` surface (soft elevation only when
20
+ // `floating`); nav links use a tonal `primary` active fill; press =
21
+ // android_ripple.
22
+ // Web: the established Canvas look (h-14 bar, bottom hairline / rounded outline
23
+ // / floating shadowed card), lifted verbatim from the original file.
24
+
25
+ export type Surface = "default" | "bordered" | "floating";
26
+
27
+ // =============================================================================
28
+ // Web: the established Canvas look (lifted verbatim from the original file).
29
+ // =============================================================================
30
+
31
+ export const webSkin: NavbarSkin = {
32
+ // The original bar dims links on press (active:opacity-90); no ripple.
33
+ pressedOpacity: 0.9,
34
+ ripple: null,
35
+
36
+ // flex-row items-center justify-between h-14 px-4
37
+ bar() {
38
+ return {
39
+ flexDirection: "row",
40
+ alignItems: "center",
41
+ justifyContent: "space-between",
42
+ height: 56,
43
+ paddingHorizontal: 16,
44
+ };
45
+ },
46
+ // bg-background fill (token-driven, follows the scheme/glass surface).
47
+ surface(tokens) {
48
+ return { backgroundColor: tokens.background };
49
+ },
50
+ // The surface axis. default rests flush with a bottom hairline; bordered boxes
51
+ // it in a rounded outline; floating lifts it as a rounded, shadowed card.
52
+ surfaceContainer(tokens, surface) {
53
+ switch (surface) {
54
+ case "default":
55
+ return { borderBottomWidth: 1, borderColor: tokens.border };
56
+ case "bordered":
57
+ return { borderRadius: 8, borderWidth: 1, borderColor: tokens.border };
58
+ case "floating":
59
+ return { borderRadius: 8, borderWidth: 1, borderColor: tokens.border, ...shadow("md") };
60
+ }
61
+ },
62
+
63
+ // flex-row items-center gap-4
64
+ leftGroup() {
65
+ return { flexDirection: "row", alignItems: "center", gap: 16 };
66
+ },
67
+ // text-base font-semibold text-foreground
68
+ brand(tokens) {
69
+ return { fontSize: 16, lineHeight: 24, fontWeight: "600", color: tokens.foreground };
70
+ },
71
+ // flex-row items-center gap-1
72
+ linksRow() {
73
+ return { flexDirection: "row", alignItems: "center", gap: 4 };
74
+ },
75
+ // rounded-md px-3 py-1.5; the active tile fills bg-accent.
76
+ linkTile(tokens, active) {
77
+ return {
78
+ borderRadius: 6,
79
+ paddingHorizontal: 12,
80
+ paddingVertical: 6,
81
+ backgroundColor: active ? tokens.accent : "transparent",
82
+ };
83
+ },
84
+ // text-sm font-medium; active reads in the foreground, inactive in muted.
85
+ linkLabel(tokens, active) {
86
+ return {
87
+ fontSize: 14,
88
+ lineHeight: 20,
89
+ fontWeight: "500",
90
+ color: active ? tokens.foreground : tokens["muted-foreground"],
91
+ };
92
+ },
93
+
94
+ // flex-row items-center gap-2
95
+ rightGroup() {
96
+ return { flexDirection: "row", alignItems: "center", gap: 8 };
97
+ },
98
+ };
99
+
100
+ // =============================================================================
101
+ // iOS (iOS 26+ / Liquid Glass): a slim navigation bar. A 44pt bar with a 1px
102
+ // hairline bottom separator and no heavy shadow; the brand is a ~17pt weight-600
103
+ // SF title. Bar-button items are CIRCULAR tinted toolbar buttons: a translucent
104
+ // neutral `secondary` circle that the brand `primary` glyph/label sits on; the
105
+ // active destination reads as a brand-FILLED circle. Press = opacity dim 0.8.
106
+ // =============================================================================
107
+
108
+ export const iosSkin: NavbarSkin = {
109
+ pressedOpacity: 0.8, // HIG: dim on press
110
+ ripple: null,
111
+
112
+ // Slim 44pt bar. iOS navigation bars are shorter than the Canvas web bar.
113
+ bar() {
114
+ return {
115
+ flexDirection: "row",
116
+ alignItems: "center",
117
+ justifyContent: "space-between",
118
+ height: 44,
119
+ paddingHorizontal: 16,
120
+ };
121
+ },
122
+ surface(tokens) {
123
+ return { backgroundColor: tokens.background };
124
+ },
125
+ // default: a 1px hairline bottom separator only (no shadow). bordered keeps the
126
+ // rounded outline; floating lifts a light card (HIG keeps elevation subtle).
127
+ surfaceContainer(tokens, surface) {
128
+ switch (surface) {
129
+ case "default":
130
+ return { borderBottomWidth: 1, borderColor: tokens.border };
131
+ case "bordered":
132
+ return { borderRadius: 10, borderWidth: 1, borderColor: tokens.border };
133
+ case "floating":
134
+ return { borderRadius: 10, borderWidth: 1, borderColor: tokens.border, ...shadow("sm") };
135
+ }
136
+ },
137
+
138
+ leftGroup() {
139
+ return { flexDirection: "row", alignItems: "center", gap: 16 };
140
+ },
141
+ // ~17pt SF title, weight 600 (iOS inline navigation title).
142
+ brand(tokens) {
143
+ return { fontSize: 17, lineHeight: 22, fontWeight: "600", color: tokens.foreground };
144
+ },
145
+ linksRow() {
146
+ return { flexDirection: "row", alignItems: "center", gap: 8 };
147
+ },
148
+ // iOS 26 bar-button items are CIRCULAR tinted toolbar buttons. Each chip is a
149
+ // 32pt fully-rounded pill on a translucent neutral `secondary` fill; the active
150
+ // destination flips to a solid brand `primary` fill (the filled-circle state).
151
+ // minWidth == height keeps a single short label reading as a true circle while
152
+ // a longer label grows the pill horizontally, matching the iOS capsule.
153
+ linkTile(tokens, active) {
154
+ return {
155
+ height: 32,
156
+ minWidth: 32,
157
+ borderRadius: 9999,
158
+ paddingHorizontal: 12,
159
+ alignItems: "center",
160
+ justifyContent: "center",
161
+ backgroundColor: active ? tokens.primary : alpha(tokens.secondary, 0.9),
162
+ };
163
+ },
164
+ // The glyph/label tints the brand `primary` on the neutral chip; on the active
165
+ // filled circle it reads in the contrasting `primary-foreground`.
166
+ linkLabel(tokens, active) {
167
+ return {
168
+ fontSize: 15,
169
+ lineHeight: 20,
170
+ fontWeight: "600",
171
+ textAlign: "center",
172
+ color: active ? tokens["primary-foreground"] : tokens.primary,
173
+ };
174
+ },
175
+
176
+ rightGroup() {
177
+ return { flexDirection: "row", alignItems: "center", gap: 8 };
178
+ },
179
+ };
180
+
181
+ // =============================================================================
182
+ // Android (Material 3): a top app bar. A ~56dp bar with a ~22sp weight-500
183
+ // LEADING title and no hairline border; the surface is flat `background` (soft
184
+ // elevation only when floating). Nav links use a tonal `primary` active fill.
185
+ // Press = android_ripple.
186
+ // =============================================================================
187
+
188
+ export const androidSkin: NavbarSkin = {
189
+ pressedOpacity: null, // Android uses a ripple instead
190
+ ripple: (tokens) => ({ color: alpha(tokens.primary, 0.12), borderless: false }),
191
+
192
+ // ~56dp small top app bar.
193
+ bar() {
194
+ return {
195
+ flexDirection: "row",
196
+ alignItems: "center",
197
+ justifyContent: "space-between",
198
+ height: 56,
199
+ paddingHorizontal: 16,
200
+ };
201
+ },
202
+ surface(tokens) {
203
+ return { backgroundColor: tokens.background };
204
+ },
205
+ // M3 small top app bar is borderless and flat at rest; we rely on the
206
+ // background fill. bordered/floating keep a container so those layouts still
207
+ // box (floating gets the M3 elevated-surface shadow on scroll).
208
+ surfaceContainer(tokens, surface) {
209
+ switch (surface) {
210
+ case "default":
211
+ return {}; // flat, no hairline; the background fill carries the bar
212
+ case "bordered":
213
+ return { borderRadius: 12, borderWidth: 1, borderColor: tokens.border };
214
+ case "floating":
215
+ return { borderRadius: 12, ...shadow("md") };
216
+ }
217
+ },
218
+
219
+ leftGroup() {
220
+ return { flexDirection: "row", alignItems: "center", gap: 16 };
221
+ },
222
+ // M3 titleLarge ~22sp, weight 500, leading.
223
+ brand(tokens) {
224
+ return { fontSize: 22, lineHeight: 28, fontWeight: "500", color: tokens.foreground };
225
+ },
226
+ linksRow() {
227
+ return { flexDirection: "row", alignItems: "center", gap: 4 };
228
+ },
229
+ // M3 active destination carries a tonal `primary` fill (secondaryContainer ~
230
+ // alpha(primary, .12)); inactive sits flat with a muted label.
231
+ linkTile(tokens, active) {
232
+ return {
233
+ borderRadius: 9999,
234
+ paddingHorizontal: 16,
235
+ paddingVertical: 6,
236
+ backgroundColor: active ? alpha(tokens.primary, 0.12) : "transparent",
237
+ };
238
+ },
239
+ linkLabel(tokens, active) {
240
+ return {
241
+ fontSize: 14,
242
+ lineHeight: 20,
243
+ fontWeight: "500",
244
+ color: active ? tokens.primary : tokens["muted-foreground"],
245
+ };
246
+ },
247
+
248
+ rightGroup() {
249
+ return { flexDirection: "row", alignItems: "center", gap: 8 };
250
+ },
251
+ };
@@ -0,0 +1,6 @@
1
+ import { createNavbar } from "./navbars.shared.js";
2
+ import { webSkin } from "./navbars.styles.js";
3
+
4
+ // Web Navbar (the base; Metro falls back to it on native, web bundlers resolve it).
5
+ export const Navbar = createNavbar(webSkin);
6
+ export type { NavbarProps } from "./navbars.shared.js";
@@ -0,0 +1,6 @@
1
+ import { createOverlay } from "./overlays.shared.js";
2
+ import { androidSkin } from "./overlays.styles.js";
3
+
4
+ // Material 3 bottom sheet Overlay. Metro resolves this file on Android; the docs import it for preview.
5
+ export const Overlay = createOverlay(androidSkin);
6
+ export type { OverlayProps } from "./overlays.shared.js";
@@ -0,0 +1,6 @@
1
+ import { createOverlay } from "./overlays.shared.js";
2
+ import { iosSkin } from "./overlays.styles.js";
3
+
4
+ // iOS (HIG sheet) Overlay. Metro resolves this file on iOS; the docs import it for preview.
5
+ export const Overlay = createOverlay(iosSkin);
6
+ export type { OverlayProps } from "./overlays.shared.js";
@@ -0,0 +1,123 @@
1
+ # Overlays
2
+
3
+ Floating surfaces: drawers, modals, popovers, toasts.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ <Overlay
9
+ trigger="Open panel"
10
+ title="Edit Identity"
11
+ description="Visible above the parent page so the user can compare."
12
+ drawer
13
+ />
14
+ ```
15
+
16
+ ## Variants
17
+
18
+ ### Surface - modal
19
+
20
+ ```tsx
21
+ <Overlay
22
+ trigger="Open panel"
23
+ title="Edit Identity"
24
+ description="Visible above the parent page so the user can compare."
25
+ modal
26
+ />
27
+ ```
28
+
29
+ ### Surface - toast
30
+
31
+ ```tsx
32
+ <Overlay
33
+ trigger="Open panel"
34
+ title="Edit Identity"
35
+ description="Visible above the parent page so the user can compare."
36
+ sheet
37
+ />
38
+ ```
39
+
40
+ ## Do & Don't
41
+
42
+ ### Drawer (SlideOver)
43
+
44
+ **Do** — Slide the form in from the edge so the parent page stays visible beneath it.
45
+
46
+ ```tsx
47
+ <Overlay open drawer title="Edit Identity" description="Visible above the parent page so the user can compare." doneLabel="Save" />
48
+ ```
49
+
50
+ **Don't** — A centered, page-blocking modal for a routine edit hides the very record the user wants to reference.
51
+
52
+ ```tsx
53
+ <Dialog open destructive title="Delete identity?" description="This cannot be undone." confirmLabel="Delete" cancelLabel="Cancel" />
54
+ ```
55
+
56
+ ### Modal (Confirm)
57
+
58
+ **Do** — Name the verb (Delete) and flag destructive actions with the danger style.
59
+
60
+ ```tsx
61
+ <Dialog open destructive title="Delete identity?" description="This cannot be undone." confirmLabel="Delete" cancelLabel="Cancel" />
62
+ ```
63
+
64
+ **Don't** — Generic Yes/No on a default-styled button hides both the stakes and what is being confirmed.
65
+
66
+ ```tsx
67
+ <Dialog open title="Delete identity?" confirmLabel="Yes" cancelLabel="No" />
68
+ ```
69
+
70
+ ### Toast
71
+
72
+ **Do** — Keep toasts to passive confirmation of something already done; offer Undo, not a blocking choice.
73
+
74
+ ```tsx
75
+ <View style={{ flexDirection: "row", alignItems: "flex-start", justifyContent: "space-between", gap: 12, borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.popover, paddingHorizontal: 16, paddingVertical: 12, ...shadow("lg") }}>
76
+ <View>
77
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens["popover-foreground"] }}>Identity deleted</Text>
78
+ <Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>You can undo this for 10 seconds.</Text>
79
+ </View>
80
+ <Pressable style={({ pressed }) => [pressed ? { opacity: 0.7 } : null]}>
81
+ <Text style={{ color: tokens["muted-foreground"] }}>×</Text>
82
+ </Pressable>
83
+ </View>
84
+ ```
85
+
86
+ **Don't** — A toast that demands a decision can be auto-dismissed before the user acts, and steals focus from a transient surface.
87
+
88
+ ```tsx
89
+ <View style={{ flexDirection: "row", alignItems: "flex-start", justifyContent: "space-between", gap: 12, borderRadius: 8, borderWidth: 1, borderColor: alpha(tokens.destructive, 0.3), backgroundColor: tokens.popover, paddingHorizontal: 16, paddingVertical: 12, ...shadow("lg") }}>
90
+ <View>
91
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens["popover-foreground"] }}>Delete this identity?</Text>
92
+ <View style={{ marginTop: 8, flexDirection: "row", gap: 8 }}>
93
+ <Button outline small>Cancel</Button>
94
+ <Button destructive small>Delete</Button>
95
+ </View>
96
+ </View>
97
+ <Pressable style={({ pressed }) => [pressed ? { opacity: 0.7 } : null]}>
98
+ <Text style={{ color: tokens["muted-foreground"] }}>×</Text>
99
+ </Pressable>
100
+ </View>
101
+ ```
102
+
103
+ ### Row menu
104
+
105
+ **Do** — Collapse per-row actions behind a ··· trigger; keep Delete separated and danger-colored.
106
+
107
+ ```tsx
108
+ <RowMenu open sectionLabel="Actions" items={[
109
+ { label: "Edit", icon: "✎" },
110
+ { label: "Duplicate", icon: "⧉" },
111
+ { label: "Delete", icon: "🗑", destructive: true, separatorBefore: true }
112
+ ]} />
113
+ ```
114
+
115
+ **Don't** — Splaying every row action inline multiplies visual noise across every table row.
116
+
117
+ ```tsx
118
+ <View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
119
+ <Button ghost small>Edit</Button>
120
+ <Button ghost small>Duplicate</Button>
121
+ <Button destructive small>Delete</Button>
122
+ </View>
123
+ ```