@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,221 @@
1
+ # Dropdowns
2
+
3
+ Floating menus triggered by a button: actions, options, navigation.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ <Dropdown
9
+ trigger="Actions"
10
+ items={[
11
+ { label: "Edit profile", icon: "✎" },
12
+ { label: "Duplicate", icon: "⧉" },
13
+ { label: "Settings", icon: "⚙" }
14
+ ]}
15
+ />
16
+ ```
17
+
18
+ ## Variants
19
+
20
+ ### Section label
21
+
22
+ ```tsx
23
+ <Dropdown
24
+ trigger="Actions"
25
+ label="Actions"
26
+ items={[
27
+ { label: "Edit profile", icon: "✎" },
28
+ { label: "Duplicate", icon: "⧉" },
29
+ { label: "Settings", icon: "⚙" }
30
+ ]}
31
+ />
32
+ ```
33
+
34
+ ### Keyboard shortcuts
35
+
36
+ ```tsx
37
+ <Dropdown
38
+ trigger="Actions"
39
+ items={[
40
+ { label: "Edit profile", icon: "✎", shortcut: "⌘E" },
41
+ { label: "Duplicate", icon: "⧉", shortcut: "⌘D" },
42
+ { label: "Settings", icon: "⚙", shortcut: "⌘," }
43
+ ]}
44
+ />
45
+ ```
46
+
47
+ ### Disabled item
48
+
49
+ ```tsx
50
+ <Dropdown
51
+ trigger="Actions"
52
+ items={[
53
+ { label: "Edit profile", icon: "✎" },
54
+ { label: "Duplicate", icon: "⧉" },
55
+ { label: "Settings", icon: "⚙" },
56
+ { label: "Archive", icon: "📦", disabled: true }
57
+ ]}
58
+ />
59
+ ```
60
+
61
+ ### Destructive item
62
+
63
+ ```tsx
64
+ <Dropdown
65
+ trigger="Actions"
66
+ items={[
67
+ { label: "Edit profile", icon: "✎" },
68
+ { label: "Duplicate", icon: "⧉" },
69
+ { label: "Settings", icon: "⚙" },
70
+ { label: "Delete…", icon: "🗑", destructive: true, separatorBefore: true }
71
+ ]}
72
+ />
73
+ ```
74
+
75
+ ## Do & Don't
76
+
77
+ ### Trigger
78
+
79
+ **Do** — Click Actions to open; click outside to dismiss.
80
+
81
+ ```tsx
82
+ <Dropdown trigger="Actions" items={[
83
+ { label: "Edit profile" },
84
+ { label: "Duplicate" },
85
+ { label: "Settings" }
86
+ ]} />
87
+ ```
88
+
89
+ **Don't** — Always open: it clutters the page and there's no way to dismiss it.
90
+
91
+ ```tsx
92
+ <Dropdown trigger="Actions" open items={[
93
+ { label: "Edit profile" },
94
+ { label: "Duplicate" },
95
+ { label: "Settings" }
96
+ ]} />
97
+ ```
98
+
99
+ ### Sectioning
100
+
101
+ **Do** — Click an item: group related actions under labels with a separator.
102
+
103
+ ```tsx
104
+ <Dropdown trigger="Actions" label="Create" items={[
105
+ { label: "New file" },
106
+ { label: "New folder" },
107
+ { label: "Upload" },
108
+ { label: "Rename", separatorBefore: true },
109
+ { label: "Move to…" },
110
+ { label: "Download" }
111
+ ]} />
112
+ ```
113
+
114
+ **Don't** — Click an item: a long, flat menu of eight actions is hard to scan.
115
+
116
+ ```tsx
117
+ <Dropdown trigger="Actions" items={[
118
+ { label: "New file" },
119
+ { label: "New folder" },
120
+ { label: "Upload" },
121
+ { label: "Rename" },
122
+ { label: "Duplicate" },
123
+ { label: "Move to…" },
124
+ { label: "Download" },
125
+ { label: "Delete" }
126
+ ]} />
127
+ ```
128
+
129
+ ### Leading icons
130
+
131
+ **Do** — Click an item: give every row a leading icon so labels share one start column.
132
+
133
+ ```tsx
134
+ <Dropdown trigger="Actions" items={[
135
+ { label: "Edit", icon: "✎" },
136
+ { label: "Duplicate", icon: "⧉" },
137
+ { label: "Settings", icon: "⚙" }
138
+ ]} />
139
+ ```
140
+
141
+ **Don't** — Click an item: icons on some rows but not others leave labels misaligned and the column ragged.
142
+
143
+ ```tsx
144
+ <Dropdown trigger="Actions" items={[
145
+ { label: "Edit", icon: "✎" },
146
+ { label: "Duplicate" },
147
+ { label: "Settings", icon: "⚙" }
148
+ ]} />
149
+ ```
150
+
151
+ ### Keyboard shortcuts
152
+
153
+ **Do** — Click an item: push shortcuts to a muted, right-aligned column so the eye can scan them.
154
+
155
+ ```tsx
156
+ <Dropdown trigger="Actions" items={[
157
+ { label: "Edit profile", shortcut: "⌘E" },
158
+ { label: "Duplicate", shortcut: "⌘D" },
159
+ { label: "Settings", shortcut: "⌘," }
160
+ ]} />
161
+ ```
162
+
163
+ **Don't** — Click an item: hints inline after the label crowd the text and never line up into a readable column.
164
+
165
+ ```tsx
166
+ <Dropdown trigger="Actions" items={[
167
+ { label: "Edit profile ⌘E" },
168
+ { label: "Duplicate ⌘D" },
169
+ { label: "Settings ⌘," }
170
+ ]} />
171
+ ```
172
+
173
+ ### Disabled item
174
+
175
+ **Do** — Click Archive: nothing happens; a real disabled item doesn't respond.
176
+
177
+ ```tsx
178
+ <Dropdown trigger="Actions" items={[
179
+ { label: "Edit" },
180
+ { label: "Archive", disabled: true },
181
+ { label: "Duplicate" }
182
+ ]} />
183
+ ```
184
+
185
+ **Don't** — Click Archive: it looks disabled but still fires, a greyed item that works is a trap.
186
+
187
+ ```tsx
188
+ <View style={{ alignSelf: "flex-start", borderRadius: 6, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.popover, padding: 4, ...shadow("lg"), minWidth: 200 }}>
189
+ <Pressable style={({ pressed }) => [{ flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 2, paddingHorizontal: 8, paddingVertical: 6 }, pressed ? { backgroundColor: tokens.accent } : null]}>
190
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["popover-foreground"] }}>Edit</Text>
191
+ </Pressable>
192
+ <Pressable style={({ pressed }) => [{ flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 2, paddingHorizontal: 8, paddingVertical: 6, opacity: 0.5 }, pressed ? { backgroundColor: tokens.accent } : null]}>
193
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["popover-foreground"] }}>Archive</Text>
194
+ </Pressable>
195
+ <Pressable style={({ pressed }) => [{ flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 2, paddingHorizontal: 8, paddingVertical: 6 }, pressed ? { backgroundColor: tokens.accent } : null]}>
196
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["popover-foreground"] }}>Duplicate</Text>
197
+ </Pressable>
198
+ </View>
199
+ ```
200
+
201
+ ### Destructive item
202
+
203
+ **Do** — Click an item: separate destructive actions with a divider, color them, and place them last.
204
+
205
+ ```tsx
206
+ <Dropdown trigger="Actions" items={[
207
+ { label: "Edit" },
208
+ { label: "Duplicate" },
209
+ { label: "Delete", destructive: true, separatorBefore: true }
210
+ ]} />
211
+ ```
212
+
213
+ **Don't** — Click an item: a destructive action wedged between routine ones invites a costly misclick.
214
+
215
+ ```tsx
216
+ <Dropdown trigger="Actions" items={[
217
+ { label: "Edit" },
218
+ { label: "Delete" },
219
+ { label: "Duplicate" }
220
+ ]} />
221
+ ```
@@ -0,0 +1,190 @@
1
+ import { useState, type ReactNode } from "react";
2
+ import { Platform } from "react-native";
3
+ import { View, Pressable, Text, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
4
+ import { Button } from "../button/button.js";
5
+ import { wrapper, wrapperLifted, customTrigger, type DropdownSkin } from "./dropdown.styles.js";
6
+
7
+ // Shared Dropdown shell. The structure (the trigger plus a floating menu of
8
+ // action rows, each with an optional leading icon glyph, a label, and an
9
+ // optional trailing keyboard shortcut, with hairline separators between groups
10
+ // and red-tinted destructive rows), the public boolean-prop API, the
11
+ // controlled/uncontrolled open state, the trigger/select close handlers, the
12
+ // web-only dismiss backdrop, the disabled handling, and accessibility all live
13
+ // here once. A platform file supplies only its skin (the menu card shape/fill/
14
+ // border, the separators, the row sizing, and the press feedback) and calls
15
+ // createDropdown.
16
+ //
17
+ // The trigger defaults to an outline button labelled by `trigger`. Pass
18
+ // `children` to supply a CUSTOM trigger instead (e.g. an avatar account chip in
19
+ // a topbar): the children render in place of the button, inside a Pressable that
20
+ // toggles the menu. Either way the menu rows still come from `items`.
21
+ //
22
+ // Overlay note: the open menu renders as a floating card positioned absolutely
23
+ // below the trigger (the wrapper is `relative`), so it overflows its container
24
+ // (e.g. a docs card or the playground stage) instead of growing it, with no
25
+ // portal/Modal. On the web, an UNCONTROLLED menu also lays down a transparent
26
+ // full-viewport backdrop so a press anywhere off the menu dismisses it; a
27
+ // controlled `open` menu and native get no backdrop (native would use a Modal).
28
+ //
29
+ // There are no visual style axes on the menu itself, so there is no boolean-prop
30
+ // precedence to resolve; the per-item `destructive` flag is the only variant and
31
+ // it is scoped to its own row.
32
+
33
+ export interface DropdownItem {
34
+ label: string;
35
+ /** Optional leading glyph rendered before the label (a single character). */
36
+ icon?: string;
37
+ /** Optional trailing keyboard shortcut, right-aligned and muted. */
38
+ shortcut?: string;
39
+ /** Red-tinted row for destructive actions (e.g. Delete). */
40
+ destructive?: boolean;
41
+ /** Dimmed, non-interactive row: skips onSelect and renders at reduced opacity. */
42
+ disabled?: boolean;
43
+ /** Draw a hairline separator above this row to start a new group. */
44
+ separatorBefore?: boolean;
45
+ }
46
+
47
+ export interface DropdownProps {
48
+ /** Label for the default outline trigger button. Omit when supplying a custom
49
+ * trigger via `children`. */
50
+ trigger?: string;
51
+ /** A custom trigger rendered in place of the default outline button, e.g. an
52
+ * avatar account chip. It is wrapped in a Pressable that toggles the menu;
53
+ * the menu itself still comes from `items`. */
54
+ children?: ReactNode;
55
+ /** Optional muted section heading rendered above the rows (e.g. "Actions"). */
56
+ label?: string;
57
+ /** The menu rows, top to bottom. */
58
+ items: DropdownItem[];
59
+ /** Controlled open state. Omit for uncontrolled (the trigger opens/closes it). */
60
+ open?: boolean;
61
+ /** Fired when the open state changes (trigger press, select, etc.). */
62
+ onOpenChange?: (open: boolean) => void;
63
+ /** Fired with the selected item and its index when a row is pressed. */
64
+ onSelect?: (item: DropdownItem, index: number) => void;
65
+ /** Escape hatch for layout/positioning composition (mainly width). */
66
+ style?: StyleProp<ViewStyle>;
67
+ }
68
+
69
+ // The menu's width floor. A menu under a small trigger (e.g. an outline button)
70
+ // stays at least this wide; a wider trigger (an account chip) sets the width.
71
+ const MENU_MIN_WIDTH = 200;
72
+
73
+ // A transparent full-viewport layer behind the open menu (web only): a press off
74
+ // the menu dismisses it. `position: fixed` is not in RN's ViewStyle type but
75
+ // react-native-web honors it; cast through unknown. zIndex sits below the menu's
76
+ // z-50 so the menu and its items stay clickable above the backdrop.
77
+ const DISMISS_BACKDROP = {
78
+ position: "fixed",
79
+ top: 0,
80
+ right: 0,
81
+ bottom: 0,
82
+ left: 0,
83
+ zIndex: 40,
84
+ } as unknown as StyleProp<ViewStyle>;
85
+
86
+ // The menu card is positioned absolutely below the trigger on every platform; the
87
+ // skin owns the card's shape/fill/shadow, this owns the anchoring.
88
+ const MENU_ANCHOR: ViewStyle = { position: "absolute", top: "100%", left: 0, zIndex: 50, marginTop: 4 };
89
+
90
+ /** Build a Dropdown component from a platform skin. */
91
+ export function createDropdown(skin: DropdownSkin) {
92
+ return function Dropdown(props: DropdownProps) {
93
+ const { trigger, children, label, items, open: openProp, onOpenChange, onSelect, style } = props;
94
+ const { tokens, dark } = useTheme();
95
+ // Uncontrolled by default (Headless-UI style): the trigger opens/closes the
96
+ // menu and a select closes it; a controlled `open` prop overrides this.
97
+ const [internalOpen, setInternalOpen] = useState(false);
98
+ const open = openProp ?? internalOpen;
99
+ const setOpen = (next: boolean) => {
100
+ if (openProp === undefined) setInternalOpen(next);
101
+ onOpenChange?.(next);
102
+ };
103
+
104
+ // Match the menu width to the trigger (and let longer rows grow past it), so a
105
+ // wide trigger like a topbar account chip gets a menu of the same width.
106
+ // Measured via the wrapper's layout; the menu is absolute, so it never feeds
107
+ // back into this width.
108
+ const [triggerWidth, setTriggerWidth] = useState(0);
109
+
110
+ const ripple = skin.ripple ? skin.ripple(tokens) : undefined;
111
+
112
+ return (
113
+ // self-start keeps the trigger from stretching; relative anchors the menu.
114
+ <View
115
+ style={[wrapper, open ? wrapperLifted : null, style]}
116
+ onLayout={(e) => setTriggerWidth(e.nativeEvent.layout.width)}
117
+ >
118
+ {children != null ? (
119
+ <Pressable
120
+ style={customTrigger}
121
+ onPress={() => setOpen(!open)}
122
+ accessibilityRole="button"
123
+ accessibilityState={{ expanded: open }}
124
+ >
125
+ {children}
126
+ </Pressable>
127
+ ) : (
128
+ <Button outline small onPress={() => setOpen(!open)}>
129
+ {trigger}
130
+ </Button>
131
+ )}
132
+
133
+ {open && openProp === undefined && Platform.OS === "web" ? (
134
+ <Pressable accessible={false} onPress={() => setOpen(false)} style={DISMISS_BACKDROP} />
135
+ ) : null}
136
+
137
+ {open ? (
138
+ <View
139
+ style={[
140
+ MENU_ANCHOR,
141
+ skin.menuCard(tokens),
142
+ { minWidth: Math.max(triggerWidth, MENU_MIN_WIDTH) },
143
+ ]}
144
+ >
145
+ {label ? (
146
+ <Text style={skin.menuLabel(tokens)}>
147
+ {label}
148
+ </Text>
149
+ ) : null}
150
+ {items.map((item, index) => (
151
+ <View key={`${item.label}-${index}`}>
152
+ {item.separatorBefore && skin.separator ? (
153
+ <View style={skin.separator(tokens)} />
154
+ ) : null}
155
+ <Pressable
156
+ style={({ pressed }) => [
157
+ skin.itemRow,
158
+ // iOS/web tint the row on press here; Android uses the ripple instead.
159
+ skin.itemPressed != null && pressed ? skin.itemPressed(tokens) : null,
160
+ skin.pressedOpacity != null && pressed ? { opacity: skin.pressedOpacity } : null,
161
+ item.disabled ? { opacity: skin.disabledOpacity } : null,
162
+ ]}
163
+ onPress={item.disabled ? undefined : () => { onSelect?.(item, index); setOpen(false); }}
164
+ disabled={item.disabled}
165
+ android_ripple={ripple}
166
+ accessibilityRole="menuitem"
167
+ accessibilityState={{ disabled: item.disabled }}
168
+ >
169
+ {item.icon ? (
170
+ <Text style={[skin.itemTextType, skin.itemTextColor(tokens, dark, !!item.destructive)]}>
171
+ {item.icon}
172
+ </Text>
173
+ ) : null}
174
+ <Text style={[skin.itemTextType, skin.itemTextColor(tokens, dark, !!item.destructive)]}>
175
+ {item.label}
176
+ </Text>
177
+ {item.shortcut ? (
178
+ <Text style={skin.shortcut(tokens)}>
179
+ {item.shortcut}
180
+ </Text>
181
+ ) : null}
182
+ </Pressable>
183
+ </View>
184
+ ))}
185
+ </View>
186
+ ) : null}
187
+ </View>
188
+ );
189
+ };
190
+ }
@@ -0,0 +1,233 @@
1
+ import { type ViewStyle, type TextStyle } from "react-native";
2
+ import { type ColorTokens, palette, shadow, alpha } from "../../style/index.js";
3
+
4
+ // Co-located Dropdown 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` press tint on Android, the
8
+ // `destructive` red for destructive rows); only the native SHAPE, sizing, fill,
9
+ // separators, and press feedback change per OS:
10
+ // iOS (iOS 26 / Liquid Glass pull-down menu): a VERY rounded popover panel
11
+ // (~26 radius, `popover` fill, NO border, soft shadow), rows ~44pt tall with
12
+ // ~17pt labels, hairline `border` separators between groups, a destructive
13
+ // row in `destructive` red, an optional trailing SF-style icon; a pressed row
14
+ // tints with a subtle `secondary` highlight (no ripple) at pressedOpacity 0.8.
15
+ // Android (Material 3 menu): an ELEVATED surface (4 radius, `popover`, shadow
16
+ // md, paddingVertical 8), rows ~48dp tall with 14sp labels and a leading
17
+ // icon gutter, an android_ripple (alpha(primary, 0.12) state layer) on rows,
18
+ // and NO separators.
19
+ // Web: the established Canvas look (the current dropdown, lifted verbatim) — a
20
+ // bordered popover card (6 radius, `border`, `popover` fill, shadow-lg,
21
+ // padding 4), rows with rounded-sm px-2 py-1.5 layout, hairline `border`
22
+ // separators, an `accent` pressed/active fill, and `red-600/red-400`
23
+ // destructive rows.
24
+
25
+ // The contract a platform skin fulfills. The shell renders the wrapper, the
26
+ // trigger, the optional backdrop, the menu card, the optional section label, the
27
+ // per-group separators, and the item rows; the skin maps the active platform's
28
+ // shape/fill/sizing/feedback onto each piece.
29
+ export interface DropdownSkin {
30
+ /** The floating menu card: shape, fill, border (or lack of), shadow, padding.
31
+ * The shell supplies the measured minWidth inline. */
32
+ menuCard: (t: ColorTokens) => ViewStyle;
33
+ /** Muted section heading rendered above the rows. */
34
+ menuLabel: (t: ColorTokens) => TextStyle;
35
+ /** A hairline separator above a row that starts a new group. iOS/web draw one;
36
+ * Android (M3) returns null (no dividers). */
37
+ separator: ((t: ColorTokens) => ViewStyle) | null;
38
+ /** An item row layout (the flex-row + gap + radius + padding box). */
39
+ itemRow: ViewStyle;
40
+ /** The fill applied to a pressed row (iOS/web tint here; Android uses a
41
+ * ripple, so this is null). */
42
+ itemPressed: ((t: ColorTokens) => ViewStyle) | null;
43
+ /** Item icon + label type scale. */
44
+ itemTextType: TextStyle;
45
+ /** Item icon + label color; branches on `destructive`. */
46
+ itemTextColor: (t: ColorTokens, dark: boolean, destructive: boolean) => TextStyle;
47
+ /** Trailing keyboard shortcut, right-aligned and muted. */
48
+ shortcut: (t: ColorTokens) => TextStyle;
49
+ /** Opacity applied to a disabled row. */
50
+ disabledOpacity: number;
51
+ /** iOS/web dim a row on press via this; Android uses a ripple instead (null). */
52
+ pressedOpacity: number | null;
53
+ /** Android ripple over the rows; null on iOS/web. */
54
+ ripple: ((t: ColorTokens) => { color: string; borderless: boolean }) | null;
55
+ }
56
+
57
+ // --- wrapper + trigger (identical across platforms) -------------------------
58
+
59
+ // self-start keeps the trigger from stretching; relative anchors the menu.
60
+ export const wrapper: ViewStyle = { position: "relative", alignSelf: "flex-start" };
61
+
62
+ // When the menu is open, the wrapper is lifted into its own stacking context
63
+ // above sibling content. react-native-web gives every positioned View an
64
+ // implicit stacking context, so the menu's own `zIndex` is scoped INSIDE the
65
+ // `relative` wrapper and cannot rise above a later sibling. Raising the wrapper's
66
+ // zIndex while open lifts the whole control — trigger and menu together — above
67
+ // everything painted after it.
68
+ export const wrapperLifted: ViewStyle = { zIndex: 50 };
69
+
70
+ // The custom-trigger Pressable: keeps the chip from stretching.
71
+ export const customTrigger: ViewStyle = { alignSelf: "flex-start" };
72
+
73
+ // ---------- Web: the established Canvas look (lifted verbatim) ----------
74
+ // The current dropdown: a popover card (rounded-md border bg-popover p-1
75
+ // shadow-lg) positioned absolutely below the trigger; rows with the
76
+ // flex-row items-center gap-2 rounded-sm px-2 py-1.5 layout, hairline
77
+ // my-1 h-px bg-border separators, an active:bg-accent pressed fill, and
78
+ // text-red-600 dark:text-red-400 destructive rows.
79
+ export const webSkin: DropdownSkin = {
80
+ menuCard: (t) => ({
81
+ borderRadius: 6,
82
+ borderWidth: 1,
83
+ borderColor: t.border,
84
+ backgroundColor: t.popover,
85
+ padding: 4,
86
+ ...shadow("lg"),
87
+ }),
88
+ menuLabel: (t) => ({
89
+ paddingHorizontal: 8,
90
+ paddingVertical: 6,
91
+ fontSize: 12,
92
+ lineHeight: 16,
93
+ fontWeight: "500",
94
+ color: t["muted-foreground"],
95
+ }),
96
+ separator: (t) => ({ marginTop: 4, marginBottom: 4, height: 1, backgroundColor: t.border }),
97
+ itemRow: {
98
+ flexDirection: "row",
99
+ alignItems: "center",
100
+ gap: 8,
101
+ borderRadius: 2,
102
+ paddingHorizontal: 8,
103
+ paddingVertical: 6,
104
+ },
105
+ itemPressed: (t) => ({ backgroundColor: t.accent }),
106
+ itemTextType: { fontSize: 14, lineHeight: 20 },
107
+ itemTextColor: (t, dark, destructive) => {
108
+ if (destructive) return { color: dark ? palette["red-400"] : palette["red-600"] };
109
+ return { color: t["popover-foreground"] };
110
+ },
111
+ shortcut: (t) => ({
112
+ marginLeft: "auto",
113
+ fontSize: 12,
114
+ lineHeight: 16,
115
+ letterSpacing: 1.6,
116
+ color: t["muted-foreground"],
117
+ }),
118
+ disabledOpacity: 0.5,
119
+ pressedOpacity: null, // web tints the row fill on press, no opacity dim
120
+ ripple: null,
121
+ };
122
+
123
+ // ---------- iOS 26 (Liquid Glass pull-down menu): VERY rounded popover, no border, hairline groups ----------
124
+ // Apple's iOS 26 pull-down/context menu: a very rounded popover panel (~26pt
125
+ // continuous radius, matching the Liquid Glass kit render in
126
+ // docs/public/refs/ios/dropdown/menu-iphone-light.png) over the `popover` fill
127
+ // with NO visible border and a soft shadow; rows ~44pt tall with ~17pt labels,
128
+ // full-bleed hairline `border` separators between groups, a destructive row in
129
+ // the `destructive` red (icon + label), section titles in `muted-foreground`,
130
+ // and an optional trailing SF-style icon (e.g. a submenu chevron). A pressed row
131
+ // tints with a subtle `secondary` highlight (no ripple) at pressedOpacity 0.8.
132
+ // iOS 26 menus are markedly rounder than legacy (~13pt) menus; the larger radius
133
+ // is the headline shape change.
134
+ const IOS_RADIUS = 26;
135
+ export const iosSkin: DropdownSkin = {
136
+ menuCard: (t) => ({
137
+ borderRadius: IOS_RADIUS,
138
+ backgroundColor: t.popover,
139
+ paddingVertical: 6,
140
+ // Clip the pressed-row highlight and full-bleed separators to the heavily
141
+ // rounded panel so they don't poke past the ~26pt corners. iOS still draws
142
+ // the soft shadow outside these bounds.
143
+ overflow: "hidden",
144
+ ...shadow("lg"),
145
+ }),
146
+ menuLabel: (t) => ({
147
+ paddingHorizontal: 16,
148
+ paddingVertical: 6,
149
+ fontSize: 13,
150
+ lineHeight: 18,
151
+ fontWeight: "600",
152
+ color: t["muted-foreground"],
153
+ }),
154
+ // Hairline group separators run full-bleed across the panel (iOS 26 groups
155
+ // rows with a thin divider).
156
+ separator: (t) => ({ marginTop: 6, marginBottom: 6, height: 1, backgroundColor: t.border }),
157
+ itemRow: {
158
+ flexDirection: "row",
159
+ alignItems: "center",
160
+ gap: 12,
161
+ paddingHorizontal: 16,
162
+ paddingVertical: 11,
163
+ minHeight: 44,
164
+ },
165
+ // Subtle highlight under a pressed row (no ripple on iOS).
166
+ itemPressed: (t) => ({ backgroundColor: t.secondary }),
167
+ itemTextType: { fontSize: 17, lineHeight: 22 },
168
+ itemTextColor: (t, _dark, destructive) => {
169
+ if (destructive) return { color: t.destructive };
170
+ return { color: t["popover-foreground"] };
171
+ },
172
+ shortcut: (t) => ({
173
+ marginLeft: "auto",
174
+ fontSize: 15,
175
+ lineHeight: 20,
176
+ letterSpacing: 0.5,
177
+ color: t["muted-foreground"],
178
+ }),
179
+ disabledOpacity: 0.5,
180
+ pressedOpacity: 0.8,
181
+ ripple: null,
182
+ };
183
+
184
+ // ---------- Android (Material 3 menu): elevated surface, ripple rows, no dividers ----------
185
+ // M3 dropdown menu: an ELEVATED surface (4dp radius, `popover` fill, soft shadow,
186
+ // 8dp vertical padding); rows ~48dp tall with 14sp labels and a leading icon
187
+ // gutter, an android_ripple (alpha(primary, 0.12) state layer) on each row, and
188
+ // NO separators (M3 menus group with spacing, not dividers).
189
+ const ANDROID_RADIUS = 4;
190
+ export const androidSkin: DropdownSkin = {
191
+ menuCard: (t) => ({
192
+ borderRadius: ANDROID_RADIUS,
193
+ backgroundColor: t.popover,
194
+ paddingVertical: 8,
195
+ ...shadow("md"),
196
+ }),
197
+ menuLabel: (t) => ({
198
+ paddingHorizontal: 16,
199
+ paddingVertical: 8,
200
+ fontSize: 12,
201
+ lineHeight: 16,
202
+ fontWeight: "500",
203
+ letterSpacing: 0.5,
204
+ color: t["muted-foreground"],
205
+ }),
206
+ // M3 menus use no dividers between items.
207
+ separator: null,
208
+ itemRow: {
209
+ flexDirection: "row",
210
+ alignItems: "center",
211
+ gap: 12,
212
+ paddingHorizontal: 16,
213
+ paddingVertical: 0,
214
+ minHeight: 48,
215
+ },
216
+ // Android uses the ripple, so no static pressed fill.
217
+ itemPressed: null,
218
+ itemTextType: { fontSize: 14, lineHeight: 20 },
219
+ itemTextColor: (t, _dark, destructive) => {
220
+ if (destructive) return { color: t.destructive };
221
+ return { color: t["popover-foreground"] };
222
+ },
223
+ shortcut: (t) => ({
224
+ marginLeft: "auto",
225
+ fontSize: 12,
226
+ lineHeight: 16,
227
+ letterSpacing: 0.5,
228
+ color: t["muted-foreground"],
229
+ }),
230
+ disabledOpacity: 0.38, // M3 disabled opacity
231
+ pressedOpacity: null, // Android uses a ripple instead
232
+ ripple: (t) => ({ color: alpha(t.primary, 0.12), borderless: false }),
233
+ };
@@ -0,0 +1,6 @@
1
+ import { createDropdown } from "./dropdown.shared.js";
2
+ import { webSkin } from "./dropdown.styles.js";
3
+
4
+ // Web Dropdown (the base; Metro falls back to it on native, web bundlers resolve it).
5
+ export const Dropdown = createDropdown(webSkin);
6
+ export type { DropdownProps, DropdownItem } from "./dropdown.shared.js";