@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,105 @@
1
+ import { useState } from "react";
2
+ import { View, Pressable, Text, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
3
+ import { anchorLifted, type RowMenuItem, type RowMenuSkin } from "./row-menu.styles.js";
4
+
5
+ // Shared RowMenu shell. The structure (the self-start anchor, the ⋯ icon-button
6
+ // trigger, and the floating card of an optional section label plus the item
7
+ // rows), the public boolean-prop API, the controlled/uncontrolled open state, the
8
+ // select/close handlers, the overlay open-close behavior, the per-row destructive
9
+ // tint, the link/action role, and accessibility all live here once. A platform
10
+ // file supplies only its skin (the trigger and card shape/fill/shadow, whether
11
+ // separators are drawn, the row text scale, and the press feedback mode) and
12
+ // calls createRowMenu.
13
+ //
14
+ // Overlay note: a real row menu portals its card over the page and dismisses on
15
+ // outside click. Here, for the docs playground (which has no portal/Modal), the
16
+ // open menu renders INLINE directly below the trigger as a floating card so the
17
+ // preview is never covered. The `open` boolean keeps it shown; this behavior is
18
+ // identical across platforms.
19
+
20
+ export type { RowMenuItem };
21
+
22
+ export interface RowMenuProps {
23
+ /** The menu rows, top to bottom. */
24
+ items: RowMenuItem[];
25
+ /** Controlled open state. Omit for uncontrolled (the trigger toggles it). */
26
+ open?: boolean;
27
+ /** Fired when the open state changes. */
28
+ onOpenChange?: (open: boolean) => void;
29
+ /** Render rows as navigation links rather than action buttons. */
30
+ links?: boolean;
31
+ /** Show a muted section label heading the menu. */
32
+ sectionLabel?: string;
33
+ /** Fired with the selected item and its index when a row is pressed. */
34
+ onSelect?: (item: RowMenuItem, index: number) => void;
35
+ /** Escape hatch for layout/positioning composition. */
36
+ style?: StyleProp<ViewStyle>;
37
+ }
38
+
39
+ /** Build a RowMenu component from a platform skin. */
40
+ export function createRowMenu(skin: RowMenuSkin) {
41
+ return function RowMenu(props: RowMenuProps) {
42
+ const { items, links = false, sectionLabel, onSelect, onOpenChange, style } = props;
43
+ const { tokens, dark } = useTheme();
44
+ // Uncontrolled by default: the ⋯ trigger toggles the menu (closed), a select
45
+ // closes it; a controlled `open` prop overrides this.
46
+ const [internalOpen, setInternalOpen] = useState(false);
47
+ const open = props.open ?? internalOpen;
48
+ const setOpen = (next: boolean) => {
49
+ if (props.open === undefined) setInternalOpen(next);
50
+ onOpenChange?.(next);
51
+ };
52
+
53
+ const ripple = skin.ripple ? skin.ripple(tokens) : undefined;
54
+
55
+ return (
56
+ // self-start keeps the trigger from stretching; relative anchors the menu.
57
+ <View style={[skin.anchor, open ? anchorLifted : null, style]}>
58
+ <Pressable
59
+ style={({ pressed }) => [
60
+ skin.trigger,
61
+ // Android ripples; iOS dims via opacity; web tints the fill.
62
+ skin.triggerPressedOpacity != null && pressed ? { opacity: skin.triggerPressedOpacity } : null,
63
+ skin.ripple == null && skin.triggerPressedOpacity == null && pressed
64
+ ? skin.triggerPressed(tokens)
65
+ : null,
66
+ ]}
67
+ onPress={() => setOpen(!open)}
68
+ android_ripple={ripple}
69
+ accessibilityRole="button"
70
+ >
71
+ <Text style={skin.triggerGlyph(tokens)}>⋯</Text>
72
+ </Pressable>
73
+
74
+ {open ? (
75
+ <View style={skin.menuCard(tokens)}>
76
+ {sectionLabel ? <Text style={skin.menuLabel(tokens)}>{sectionLabel}</Text> : null}
77
+ {items.map((item, index) => (
78
+ <View key={`${item.label}-${index}`}>
79
+ {item.separatorBefore && skin.showSeparators ? <View style={skin.separator(tokens)} /> : null}
80
+ <Pressable
81
+ style={({ pressed }) => [
82
+ skin.itemRow,
83
+ // Web/iOS tint the row on press here; Android uses the ripple instead.
84
+ skin.ripple == null && pressed ? skin.itemPressed(tokens) : null,
85
+ ]}
86
+ onPress={() => {
87
+ onSelect?.(item, index);
88
+ setOpen(false);
89
+ }}
90
+ android_ripple={ripple}
91
+ accessibilityRole={links ? "link" : "menuitem"}
92
+ >
93
+ {item.icon ? (
94
+ <Text style={[skin.rowTextSize, skin.rowTextColor(item, links, tokens, dark)]}>{item.icon}</Text>
95
+ ) : null}
96
+ <Text style={[skin.rowTextSize, skin.rowTextColor(item, links, tokens, dark)]}>{item.label}</Text>
97
+ </Pressable>
98
+ </View>
99
+ ))}
100
+ </View>
101
+ ) : null}
102
+ </View>
103
+ );
104
+ };
105
+ }
@@ -0,0 +1,262 @@
1
+ import { type ViewStyle, type TextStyle } from "react-native";
2
+ import { type ColorTokens, palette, shadow, alpha } from "../../style/index.js";
3
+
4
+ // Co-located RowMenu skins, one per platform, all driven by the brand tokens
5
+ // (passed in from useTheme so they follow light/dark and the glass surface, since
6
+ // tokens.popover is swapped translucent at the theming level). The BRAND survives
7
+ // on every platform (the destructive red is a fixed Tailwind hue, the trigger and
8
+ // rows take the brand foreground/popover tokens, never a platform default); only
9
+ // the native SHAPE, sizing, structure, and interaction feedback change per OS:
10
+ // iOS (HIG context menu): the floating card is a rounded popover (~13 radius)
11
+ // over `popover` with a soft shadow, NO border; rows are ~44pt tall with a
12
+ // LEADING icon, groups split by hairline `separator` lines; destructive rows
13
+ // are red; pressed = a `secondary` highlight (no ripple). The ⋯ trigger dims
14
+ // to ~0.8 opacity on press.
15
+ // Android (Material 3 menu): the card is an elevated surface (4 radius,
16
+ // `popover`, soft shadow), NO border and NO separators between rows; rows are
17
+ // ~48dp tall with a LEADING icon; press = an android_ripple (alpha(primary,
18
+ // 0.12) state layer). Destructive rows are red. The ⋯ trigger uses the same
19
+ // ripple.
20
+ // Web: the established Canvas look (the current row menu, lifted verbatim) — a
21
+ // bordered popover card (6 radius, `border`, shadow-lg, min-w 180), rounded-sm
22
+ // rows (px-2 py-1.5), hairline `border` separators, destructive red, and an
23
+ // `accent` fill on press for both the trigger and the rows.
24
+
25
+ export interface RowMenuItem {
26
+ label: string;
27
+ /** Optional leading glyph rendered before the label (a single character). */
28
+ icon?: string;
29
+ /** Red-tinted row for destructive actions (e.g. Delete). */
30
+ destructive?: boolean;
31
+ /** Draw a hairline separator above this row to start a new group. */
32
+ separatorBefore?: boolean;
33
+ }
34
+
35
+ // The contract a platform skin fulfills. The shell owns the structure (anchor +
36
+ // ⋯ trigger + floating card of section label and item rows) and the open/close
37
+ // state; the skin maps tokens and the active row state to RN style objects, and
38
+ // declares its press-feedback mode (iOS/web dim or tint inline, Android ripples).
39
+ export interface RowMenuSkin {
40
+ /** The relative anchor: keeps the trigger from stretching, positions the card. */
41
+ anchor: ViewStyle;
42
+ /** The ⋯ icon-button surface (square, centered, platform radius). */
43
+ trigger: ViewStyle;
44
+ /** The ⋯ glyph text. */
45
+ triggerGlyph: (t: ColorTokens) => TextStyle;
46
+ /** The fill applied to the trigger on press (web/iOS tint via this; Android ripples). */
47
+ triggerPressed: (t: ColorTokens) => ViewStyle;
48
+ /** The floating menu card surface (shape, fill, border, shadow, placement). */
49
+ menuCard: (t: ColorTokens) => ViewStyle;
50
+ /** The muted section heading above the rows. */
51
+ menuLabel: (t: ColorTokens) => TextStyle;
52
+ /** A single action/link row layout. */
53
+ itemRow: ViewStyle;
54
+ /** The fill applied to a row on press (web/iOS tint via this; Android ripples). */
55
+ itemPressed: (t: ColorTokens) => ViewStyle;
56
+ /** Whether this platform draws hairline separators between groups (iOS/web yes, Android no). */
57
+ showSeparators: boolean;
58
+ /** The hairline separator above a row that sets `separatorBefore`. */
59
+ separator: (t: ColorTokens) => ViewStyle;
60
+ /** The shared icon/label text size, so they line up in a row. */
61
+ rowTextSize: TextStyle;
62
+ /** The per-row text color (destructive red, link foreground, action popover fg). */
63
+ rowTextColor: (item: RowMenuItem, links: boolean, t: ColorTokens, dark: boolean) => TextStyle;
64
+ /** iOS/web dim the trigger on press; Android ripples instead (null). */
65
+ triggerPressedOpacity: number | null;
66
+ /** Android ripple over the trigger and rows; null on iOS/web. */
67
+ ripple: ((t: ColorTokens) => { color: string; borderless: boolean }) | null;
68
+ }
69
+
70
+ // When the menu is open, the anchor is lifted into its own stacking context above
71
+ // sibling content. react-native-web gives every positioned View an implicit
72
+ // stacking context, so the card's own `zIndex` is scoped INSIDE the `relative`
73
+ // anchor and cannot rise above a later sibling. Raising the anchor's zIndex while
74
+ // open lifts the whole control — trigger and card together — above everything
75
+ // painted after it. Shared across platforms (the anchor shape is identical).
76
+ export const anchorLifted: ViewStyle = { zIndex: 50 };
77
+
78
+ // ---------- Web: the established Canvas look (lifted verbatim) ----------
79
+ // A bordered popover card (min-w 180, 6 radius, 1px `border`, `popover` fill,
80
+ // p-1, shadow-lg) placed inline below the trigger; rounded-sm rows (px-2 py-1.5)
81
+ // with a 2-radius corner; hairline `border` separators split groups; destructive
82
+ // rows are red-600/red-400; the trigger and rows tint with `accent` on press.
83
+ export const webSkin: RowMenuSkin = {
84
+ anchor: { position: "relative", alignSelf: "flex-start" },
85
+ trigger: {
86
+ width: 32,
87
+ height: 32,
88
+ alignItems: "center",
89
+ justifyContent: "center",
90
+ borderRadius: 6,
91
+ },
92
+ triggerGlyph: (t) => ({ fontSize: 16, lineHeight: 24, color: t.foreground }),
93
+ triggerPressed: (t) => ({ backgroundColor: t.accent }),
94
+ menuCard: (t) => ({
95
+ minWidth: 180,
96
+ borderRadius: 6,
97
+ borderWidth: 1,
98
+ borderColor: t.border,
99
+ backgroundColor: t.popover,
100
+ padding: 4,
101
+ ...shadow("lg"),
102
+ position: "absolute",
103
+ top: "100%",
104
+ left: 0,
105
+ zIndex: 50,
106
+ marginTop: 4,
107
+ }),
108
+ menuLabel: (t) => ({
109
+ paddingHorizontal: 8,
110
+ paddingVertical: 6,
111
+ fontSize: 12,
112
+ lineHeight: 16,
113
+ fontWeight: "500",
114
+ color: t["muted-foreground"],
115
+ }),
116
+ itemRow: {
117
+ flexDirection: "row",
118
+ alignItems: "center",
119
+ gap: 8,
120
+ borderRadius: 2,
121
+ paddingHorizontal: 8,
122
+ paddingVertical: 6,
123
+ },
124
+ itemPressed: (t) => ({ backgroundColor: t.accent }),
125
+ showSeparators: true,
126
+ separator: (t) => ({ marginVertical: 4, height: 1, backgroundColor: t.border }),
127
+ rowTextSize: { fontSize: 14, lineHeight: 20 },
128
+ rowTextColor: (item, links, t, dark) => {
129
+ if (item.destructive) return { color: dark ? palette["red-400"] : palette["red-600"] };
130
+ return { color: links ? t.foreground : t["popover-foreground"] };
131
+ },
132
+ triggerPressedOpacity: null,
133
+ ripple: null,
134
+ };
135
+
136
+ // ---------- iOS 27 (Liquid Glass context menu): big-radius popover, leading icons, hairlines ----------
137
+ // Apple's iOS 26+/iOS 27 context menu: a floating, deeply rounded card (~28pt
138
+ // continuous corner, up from the old ~13pt) over `popover` with a soft shadow and
139
+ // NO border; rows are ~44pt tall with comfortable horizontal padding and a LEADING
140
+ // glyph; groups are split by full-bleed hairline separators; a destructive row is
141
+ // red; the pressed row tints with the `secondary` system fill (not a ripple). The
142
+ // ⋯ trigger dims to ~0.8 opacity on press. The larger radius is what reads as the
143
+ // modern Liquid Glass menu; the rest of the structure (leading icons, hairlines,
144
+ // destructive red, section titles) is unchanged from the HIG layout.
145
+ const IOS_RADIUS = 28;
146
+ export const iosSkin: RowMenuSkin = {
147
+ anchor: { position: "relative", alignSelf: "flex-start" },
148
+ trigger: {
149
+ width: 32,
150
+ height: 32,
151
+ alignItems: "center",
152
+ justifyContent: "center",
153
+ borderRadius: 8,
154
+ },
155
+ triggerGlyph: (t) => ({ fontSize: 17, lineHeight: 22, color: t.foreground }),
156
+ // iOS dims the whole trigger on press (pressedOpacity); no fill tint.
157
+ triggerPressed: () => ({}),
158
+ menuCard: (t) => ({
159
+ minWidth: 250,
160
+ borderRadius: IOS_RADIUS,
161
+ backgroundColor: t.popover,
162
+ // The deep corner clips the first/last row fills cleanly; vertical inset keeps
163
+ // the top/bottom rows clear of the rounded corners.
164
+ paddingVertical: 6,
165
+ overflow: "hidden",
166
+ ...shadow("lg"),
167
+ position: "absolute",
168
+ top: "100%",
169
+ left: 0,
170
+ zIndex: 50,
171
+ marginTop: 8,
172
+ }),
173
+ menuLabel: (t) => ({
174
+ paddingHorizontal: 16,
175
+ paddingTop: 10,
176
+ paddingBottom: 6,
177
+ fontSize: 13,
178
+ lineHeight: 18,
179
+ fontWeight: "600",
180
+ color: t["muted-foreground"],
181
+ }),
182
+ itemRow: {
183
+ flexDirection: "row",
184
+ alignItems: "center",
185
+ gap: 12,
186
+ paddingHorizontal: 16,
187
+ paddingVertical: 11,
188
+ minHeight: 44,
189
+ },
190
+ // The selected/pressed row uses the iOS system highlight (the `secondary` fill).
191
+ itemPressed: (t) => ({ backgroundColor: t.secondary }),
192
+ showSeparators: true,
193
+ // A full-bleed hairline divider (no horizontal inset) splitting menu groups.
194
+ separator: (t) => ({ height: 1, backgroundColor: t.border, marginVertical: 4 }),
195
+ rowTextSize: { fontSize: 17, lineHeight: 22 },
196
+ rowTextColor: (item, links, t, dark) => {
197
+ if (item.destructive) return { color: dark ? palette["red-400"] : palette["red-600"] };
198
+ return { color: links ? t.foreground : t["popover-foreground"] };
199
+ },
200
+ triggerPressedOpacity: 0.8,
201
+ ripple: null,
202
+ };
203
+
204
+ // ---------- Android (Material 3 menu): elevated surface, ripple rows, no dividers ----------
205
+ // M3 dropdown/context menu: an elevated surface (4dp radius, `popover` fill, soft
206
+ // shadow) with NO border; rows are ~48dp tall with a LEADING icon and a brand
207
+ // ripple on press (alpha(primary, 0.12) state layer); M3 menus do NOT draw
208
+ // dividers between every group, so separators are suppressed. Destructive rows
209
+ // are red. The ⋯ trigger shares the ripple.
210
+ const ANDROID_RADIUS = 4;
211
+ export const androidSkin: RowMenuSkin = {
212
+ anchor: { position: "relative", alignSelf: "flex-start" },
213
+ trigger: {
214
+ width: 40,
215
+ height: 40,
216
+ alignItems: "center",
217
+ justifyContent: "center",
218
+ borderRadius: 20,
219
+ },
220
+ triggerGlyph: (t) => ({ fontSize: 20, lineHeight: 24, color: t.foreground }),
221
+ // Android tints the trigger via the ripple, not a fill.
222
+ triggerPressed: () => ({}),
223
+ menuCard: (t) => ({
224
+ minWidth: 200,
225
+ borderRadius: ANDROID_RADIUS,
226
+ backgroundColor: t.popover,
227
+ paddingVertical: 8,
228
+ ...shadow("md"),
229
+ position: "absolute",
230
+ top: "100%",
231
+ left: 0,
232
+ zIndex: 50,
233
+ marginTop: 2,
234
+ }),
235
+ menuLabel: (t) => ({
236
+ paddingHorizontal: 16,
237
+ paddingVertical: 8,
238
+ fontSize: 12,
239
+ lineHeight: 16,
240
+ fontWeight: "500",
241
+ color: t["muted-foreground"],
242
+ }),
243
+ itemRow: {
244
+ flexDirection: "row",
245
+ alignItems: "center",
246
+ gap: 12,
247
+ paddingHorizontal: 16,
248
+ paddingVertical: 12,
249
+ minHeight: 48,
250
+ },
251
+ // The M3 pressed state layer: the brand primary at ~12% alpha (the ripple tint).
252
+ itemPressed: (t) => ({ backgroundColor: alpha(t.primary, 0.12) }),
253
+ showSeparators: false,
254
+ separator: (t) => ({ height: 1, backgroundColor: t.border, marginVertical: 4 }),
255
+ rowTextSize: { fontSize: 16, lineHeight: 24 },
256
+ rowTextColor: (item, links, t, dark) => {
257
+ if (item.destructive) return { color: dark ? palette["red-400"] : palette["red-600"] };
258
+ return { color: links ? t.foreground : t["popover-foreground"] };
259
+ },
260
+ triggerPressedOpacity: null,
261
+ ripple: (t) => ({ color: alpha(t.primary, 0.12), borderless: false }),
262
+ };
@@ -0,0 +1,6 @@
1
+ import { createRowMenu } from "./row-menu.shared.js";
2
+ import { webSkin } from "./row-menu.styles.js";
3
+
4
+ // Web RowMenu (the base; Metro falls back to it on native, web bundlers resolve it).
5
+ export const RowMenu = createRowMenu(webSkin);
6
+ export type { RowMenuProps, RowMenuItem } from "./row-menu.shared.js";
@@ -0,0 +1,6 @@
1
+ import { createSidebar } from "./sidebar.shared.js";
2
+ import { androidSkin } from "./sidebar.styles.js";
3
+
4
+ // Material 3 (navigation drawer) Sidebar. Metro resolves this file on Android; the docs import it for preview.
5
+ export const Sidebar = createSidebar(androidSkin);
6
+ export type { SidebarProps, SidebarItem, SidebarSection } from "./sidebar.shared.js";
@@ -0,0 +1,6 @@
1
+ import { createSidebar } from "./sidebar.shared.js";
2
+ import { iosSkin } from "./sidebar.styles.js";
3
+
4
+ // iOS (HIG sidebar) Sidebar. Metro resolves this file on iOS; the docs import it for preview.
5
+ export const Sidebar = createSidebar(iosSkin);
6
+ export type { SidebarProps, SidebarItem, SidebarSection } from "./sidebar.shared.js";
@@ -0,0 +1,188 @@
1
+ # Navigation
2
+
3
+ Sidebar + Topbar + breadcrumbs + page header. The sidebar you see on the left of this very page is the production sidebar: same component, same width, same drawer behavior.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ <Sidebar
9
+ active="Dashboard"
10
+ sections={[
11
+ { title: "Main", items: [
12
+ { label: "Dashboard", icon: "◉" },
13
+ { label: "Users", icon: "○", badge: "12" },
14
+ { label: "Settings", icon: "◇" }
15
+ ] },
16
+ { title: "Reports", items: [
17
+ { label: "Analytics", icon: "△" }
18
+ ] }
19
+ ]}
20
+ />
21
+ ```
22
+
23
+ ## Do & Don't
24
+
25
+ ### Sidebar
26
+
27
+ **Do** — Exactly one item carries the accent background; clicking moves it so the active page is always unambiguous.
28
+
29
+ ```tsx
30
+ <Sidebar bordered active="Dashboard" sections={[
31
+ { title: "Main", items: [
32
+ { label: "Dashboard", icon: "◉" },
33
+ { label: "Users", icon: "○", badge: "12" },
34
+ { label: "Settings", icon: "◇" }
35
+ ] }
36
+ ]} />
37
+ ```
38
+
39
+ **Don't** — Click an item: two rows wearing the active background means the nav can't tell you which page you're on.
40
+
41
+ ```tsx
42
+ <View style={{ width: 240, overflow: "hidden", borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.background }}>
43
+ <View style={{ height: 56, flexDirection: "row", alignItems: "center", borderBottomWidth: 1, borderColor: tokens.border, paddingHorizontal: 16 }}>
44
+ <Text style={{ fontSize: 16, lineHeight: 24, fontWeight: "600", color: tokens.foreground }}>Acme</Text>
45
+ </View>
46
+ <View style={{ gap: 4, padding: 8 }}>
47
+ <Pressable style={{ flexDirection: "row", alignItems: "center", gap: 12, borderRadius: 6, backgroundColor: tokens.accent, paddingHorizontal: 12, paddingVertical: 8 }}>
48
+ <Text style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Dashboard</Text>
49
+ </Pressable>
50
+ <Pressable style={{ flexDirection: "row", alignItems: "center", gap: 12, borderRadius: 6, backgroundColor: tokens.accent, paddingHorizontal: 12, paddingVertical: 8 }}>
51
+ <Text style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Users</Text>
52
+ </Pressable>
53
+ <Pressable style={{ flexDirection: "row", alignItems: "center", gap: 12, borderRadius: 6, paddingHorizontal: 12, paddingVertical: 8 }}>
54
+ <Text style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Settings</Text>
55
+ </Pressable>
56
+ </View>
57
+ </View>
58
+ ```
59
+
60
+ ### Topbar
61
+
62
+ **Do** — Push utilities to the right, keep one primary button, and demote the rest to ghost so the New action leads.
63
+
64
+ ```tsx
65
+ <View style={{ height: 56, width: 420, maxWidth: "100%", flexDirection: "row", alignItems: "center", borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, paddingHorizontal: 16 }}>
66
+ <Text style={{ fontSize: 16, lineHeight: 24, fontWeight: "600", color: tokens.foreground }}>Dashboard</Text>
67
+ <View style={{ marginLeft: "auto", flexDirection: "row", gap: 8 }}>
68
+ <Button ghost small>Search</Button>
69
+ <Button primary small>New</Button>
70
+ </View>
71
+ </View>
72
+ ```
73
+
74
+ **Don't** — Four solid primary buttons crammed left-to-right give the topbar no focal action and no breathing room.
75
+
76
+ ```tsx
77
+ <View style={{ height: 56, width: 420, maxWidth: "100%", flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, paddingHorizontal: 16 }}>
78
+ <Text style={{ fontSize: 16, lineHeight: 24, fontWeight: "600", color: tokens.foreground }}>Dashboard</Text>
79
+ <Button primary small>Search</Button>
80
+ <Button primary small>Filter</Button>
81
+ <Button primary small>Export</Button>
82
+ <Button primary small>New</Button>
83
+ </View>
84
+ ```
85
+
86
+ ### Page header
87
+
88
+ **Do** — Page-header titles are 20-22px semibold: clearly the page label, never larger than the topbar brand.
89
+
90
+ ```tsx
91
+ <View style={{ width: 420, maxWidth: "100%" }}>
92
+ <Typography h4>Users</Typography>
93
+ <Typography muted style={{ marginTop: 4 }}>Manage your team members.</Typography>
94
+ </View>
95
+ ```
96
+
97
+ **Don't** — A 36px display heading on the page body competes with the topbar and screams louder than the content beneath it.
98
+
99
+ ```tsx
100
+ <View style={{ width: 420, maxWidth: "100%" }}>
101
+ <Typography h1>Users</Typography>
102
+ <Typography muted style={{ marginTop: 4 }}>Manage your team members.</Typography>
103
+ </View>
104
+ ```
105
+
106
+ ### Breadcrumbs
107
+
108
+ **Do** — The final crumb is plain current-page text, not a link: it marks where you are, not where you can go.
109
+
110
+ ```tsx
111
+ <Breadcrumb slash items={["Home", "Team", "Rachel Chen"]} />
112
+ ```
113
+
114
+ **Don't** — Making the last crumb a link implies the current page is somewhere else to navigate to.
115
+
116
+ ```tsx
117
+ <View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
118
+ <Pressable style={({ pressed }) => [pressed ? { opacity: 0.7 } : null]}>
119
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Home</Text>
120
+ </Pressable>
121
+ <Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>/</Text>
122
+ <Pressable style={({ pressed }) => [pressed ? { opacity: 0.7 } : null]}>
123
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Team</Text>
124
+ </Pressable>
125
+ <Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>/</Text>
126
+ <Pressable style={({ pressed }) => [pressed ? { opacity: 0.7 } : null]}>
127
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Rachel Chen</Text>
128
+ </Pressable>
129
+ </View>
130
+ ```
131
+
132
+ ### Tabs
133
+
134
+ **Do** — Click a tab: exactly one carries the primary underline so the active facet is always singular.
135
+
136
+ ```tsx
137
+ <Tabs underline active={0} tabs={["Overview", "Sessions", "Audit log"]} />
138
+ ```
139
+
140
+ **Don't** — Two underlined tabs at once breaks the one-active-facet contract and hides which view you're reading.
141
+
142
+ ```tsx
143
+ <View style={{ width: 420, maxWidth: "100%", flexDirection: "row", alignItems: "center", borderBottomWidth: 1, borderColor: tokens.border }}>
144
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", paddingHorizontal: 16, paddingVertical: 10 }}>
145
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Overview</Text>
146
+ <View style={{ position: "absolute", bottom: 0, left: 0, right: 0, height: 2, borderRadius: 9999, backgroundColor: tokens.primary }} />
147
+ </Pressable>
148
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", paddingHorizontal: 16, paddingVertical: 10 }}>
149
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Sessions</Text>
150
+ <View style={{ position: "absolute", bottom: 0, left: 0, right: 0, height: 2, borderRadius: 9999, backgroundColor: tokens.primary }} />
151
+ </Pressable>
152
+ <Pressable style={{ flexDirection: "row", alignItems: "center", justifyContent: "center", paddingHorizontal: 16, paddingVertical: 10 }}>
153
+ <Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens["muted-foreground"] }}>Audit log</Text>
154
+ <View style={{ position: "absolute", bottom: 0, left: 0, right: 0, height: 2, borderRadius: 9999, backgroundColor: "transparent" }} />
155
+ </Pressable>
156
+ </View>
157
+ ```
158
+
159
+ ### Command palette
160
+
161
+ **Do** — A search icon and esc hint signal input, and a single highlighted row shows exactly what Enter will run.
162
+
163
+ ```tsx
164
+ <Command open active={0} placeholder="Type a command or search..." groups={[
165
+ { heading: "Actions", items: [
166
+ { label: "Create identity", shortcut: "C" },
167
+ { label: "Invite teammate" }
168
+ ] }
169
+ ]} />
170
+ ```
171
+
172
+ **Don't** — No search affordance and two highlighted rows: nothing tells you to type or which result Enter will run.
173
+
174
+ ```tsx
175
+ <View style={{ width: 420, maxWidth: 480, overflow: "hidden", borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.popover, ...shadow("xl") }}>
176
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 8, borderBottomWidth: 1, borderColor: tokens.border, paddingHorizontal: 12, paddingVertical: 12 }}>
177
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Type a command or search...</Text>
178
+ </View>
179
+ <View style={{ padding: 6 }}>
180
+ <Pressable style={{ flexDirection: "row", alignItems: "center", gap: 12, borderRadius: 6, backgroundColor: tokens.accent, paddingHorizontal: 12, paddingVertical: 8 }}>
181
+ <Text style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", fontSize: 14, lineHeight: 20, color: tokens.foreground }}>Create identity</Text>
182
+ </Pressable>
183
+ <Pressable style={{ flexDirection: "row", alignItems: "center", gap: 12, borderRadius: 6, backgroundColor: tokens.accent, paddingHorizontal: 12, paddingVertical: 8 }}>
184
+ <Text style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", fontSize: 14, lineHeight: 20, color: tokens.foreground }}>Invite teammate</Text>
185
+ </Pressable>
186
+ </View>
187
+ </View>
188
+ ```