@shadow-js/ui 0.0.0-stage → 1.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 (190) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +598 -2
  4. package/dist/cli.mjs +2293 -0
  5. package/dist/dev/cli/schema.d.ts +73 -0
  6. package/dist/dev/index.d.ts +6 -0
  7. package/dist/dev/index.js +5 -0
  8. package/dist/dev/locales/ar.d.ts +141 -0
  9. package/dist/dev/locales/ar.js +128 -0
  10. package/dist/dev/locales/ar.js.map +1 -0
  11. package/dist/dev/locales/en.d.ts +141 -0
  12. package/dist/dev/locales/en.js +128 -0
  13. package/dist/dev/locales/en.js.map +1 -0
  14. package/dist/dev/registry/utils/utils.d.ts +29 -0
  15. package/dist/dev/registry/utils/utils.js +27 -0
  16. package/dist/dev/registry/utils/utils.js.map +1 -0
  17. package/dist/dev/theme/theme-logic.d.ts +187 -0
  18. package/dist/dev/theme/theme-logic.js +184 -0
  19. package/dist/dev/theme/theme-logic.js.map +1 -0
  20. package/dist/dev/theme/theme-provider.d.ts +90 -0
  21. package/dist/dev/theme/theme-provider.js +150 -0
  22. package/dist/dev/theme/theme-provider.js.map +1 -0
  23. package/dist/prod/index.js +1 -0
  24. package/dist/prod/locales/ar.js +2 -0
  25. package/dist/prod/locales/ar.js.map +1 -0
  26. package/dist/prod/locales/en.js +2 -0
  27. package/dist/prod/locales/en.js.map +1 -0
  28. package/dist/prod/registry/utils/utils.js +2 -0
  29. package/dist/prod/registry/utils/utils.js.map +1 -0
  30. package/dist/prod/theme/theme-logic.js +2 -0
  31. package/dist/prod/theme/theme-logic.js.map +1 -0
  32. package/dist/prod/theme/theme-provider.js +2 -0
  33. package/dist/prod/theme/theme-provider.js.map +1 -0
  34. package/package.json +102 -4
  35. package/registry/accordion/accordion.tsx +210 -0
  36. package/registry/accordion/item.json +4 -0
  37. package/registry/alert/alert.tsx +200 -0
  38. package/registry/alert/item.json +4 -0
  39. package/registry/alert-dialog/alert-dialog.tsx +530 -0
  40. package/registry/alert-dialog/item.json +4 -0
  41. package/registry/aspect-ratio/aspect-ratio.tsx +71 -0
  42. package/registry/aspect-ratio/item.json +4 -0
  43. package/registry/attachment/attachment.tsx +459 -0
  44. package/registry/attachment/item.json +4 -0
  45. package/registry/avatar/avatar.tsx +385 -0
  46. package/registry/avatar/item.json +4 -0
  47. package/registry/badge/badge.tsx +108 -0
  48. package/registry/badge/item.json +4 -0
  49. package/registry/breadcrumb/breadcrumb.tsx +354 -0
  50. package/registry/breadcrumb/item.json +4 -0
  51. package/registry/bubble/bubble.tsx +260 -0
  52. package/registry/bubble/item.json +4 -0
  53. package/registry/button/button.tsx +125 -0
  54. package/registry/button/item.json +4 -0
  55. package/registry/button-group/button-group.tsx +189 -0
  56. package/registry/button-group/item.json +4 -0
  57. package/registry/calendar/calendar.tsx +348 -0
  58. package/registry/calendar/item.json +4 -0
  59. package/registry/card/card.tsx +311 -0
  60. package/registry/card/item.json +4 -0
  61. package/registry/carousel/carousel.tsx +851 -0
  62. package/registry/carousel/item.json +4 -0
  63. package/registry/chart/chart.tsx +1956 -0
  64. package/registry/chart/item.json +4 -0
  65. package/registry/chart-scale/chart-scale.ts +670 -0
  66. package/registry/chart-scale/item.json +5 -0
  67. package/registry/checkbox/checkbox.tsx +79 -0
  68. package/registry/checkbox/item.json +4 -0
  69. package/registry/code-block/code-block.tsx +470 -0
  70. package/registry/code-block/item.json +4 -0
  71. package/registry/collapsible/collapsible.tsx +117 -0
  72. package/registry/collapsible/item.json +4 -0
  73. package/registry/combobox/combobox.tsx +977 -0
  74. package/registry/combobox/item.json +4 -0
  75. package/registry/command/command.tsx +757 -0
  76. package/registry/command/item.json +4 -0
  77. package/registry/context-menu/context-menu.tsx +697 -0
  78. package/registry/context-menu/item.json +4 -0
  79. package/registry/data-table/data-table.tsx +437 -0
  80. package/registry/data-table/item.json +4 -0
  81. package/registry/date-picker/date-picker.tsx +349 -0
  82. package/registry/date-picker/item.json +4 -0
  83. package/registry/dialog/dialog.tsx +412 -0
  84. package/registry/dialog/item.json +4 -0
  85. package/registry/direction/direction.tsx +88 -0
  86. package/registry/direction/item.json +4 -0
  87. package/registry/drawer/drawer.tsx +787 -0
  88. package/registry/drawer/item.json +4 -0
  89. package/registry/dropdown-menu/dropdown-menu.tsx +692 -0
  90. package/registry/dropdown-menu/item.json +4 -0
  91. package/registry/empty/empty.tsx +240 -0
  92. package/registry/empty/item.json +4 -0
  93. package/registry/field/field.tsx +624 -0
  94. package/registry/field/item.json +4 -0
  95. package/registry/form/form.tsx +1112 -0
  96. package/registry/form/item.json +4 -0
  97. package/registry/hover-card/hover-card.tsx +144 -0
  98. package/registry/hover-card/item.json +4 -0
  99. package/registry/icons/icons.tsx +1691 -0
  100. package/registry/icons/item.json +4 -0
  101. package/registry/input/input.tsx +43 -0
  102. package/registry/input/item.json +4 -0
  103. package/registry/input-group/input-group.tsx +315 -0
  104. package/registry/input-group/item.json +4 -0
  105. package/registry/input-otp/input-otp.tsx +329 -0
  106. package/registry/input-otp/item.json +4 -0
  107. package/registry/item/item.json +4 -0
  108. package/registry/item/item.tsx +439 -0
  109. package/registry/kbd/item.json +4 -0
  110. package/registry/kbd/kbd.tsx +65 -0
  111. package/registry/label/item.json +4 -0
  112. package/registry/label/label.tsx +60 -0
  113. package/registry/marker/item.json +4 -0
  114. package/registry/marker/marker.tsx +175 -0
  115. package/registry/menubar/item.json +4 -0
  116. package/registry/menubar/menubar.tsx +694 -0
  117. package/registry/message/item.json +4 -0
  118. package/registry/message/message.tsx +280 -0
  119. package/registry/message-scroller/item.json +4 -0
  120. package/registry/message-scroller/message-scroller.tsx +262 -0
  121. package/registry/native-select/item.json +4 -0
  122. package/registry/native-select/native-select.tsx +113 -0
  123. package/registry/navigation-menu/item.json +4 -0
  124. package/registry/navigation-menu/navigation-menu.tsx +362 -0
  125. package/registry/pagination/item.json +4 -0
  126. package/registry/pagination/pagination.tsx +460 -0
  127. package/registry/password-input/item.json +5 -0
  128. package/registry/password-input/password-input.tsx +494 -0
  129. package/registry/popover/item.json +4 -0
  130. package/registry/popover/popover.tsx +284 -0
  131. package/registry/progress/item.json +4 -0
  132. package/registry/progress/progress.tsx +190 -0
  133. package/registry/questionnaire/item.json +4 -0
  134. package/registry/questionnaire/questionnaire.tsx +569 -0
  135. package/registry/radio-group/item.json +4 -0
  136. package/registry/radio-group/radio-group.tsx +101 -0
  137. package/registry/resizable/item.json +4 -0
  138. package/registry/resizable/resizable.tsx +455 -0
  139. package/registry/scroll-area/item.json +4 -0
  140. package/registry/scroll-area/scroll-area.tsx +149 -0
  141. package/registry/select/item.json +4 -0
  142. package/registry/select/select.tsx +571 -0
  143. package/registry/separator/item.json +4 -0
  144. package/registry/separator/separator.tsx +52 -0
  145. package/registry/sheet/item.json +4 -0
  146. package/registry/sheet/sheet.tsx +476 -0
  147. package/registry/sidebar/item.json +4 -0
  148. package/registry/sidebar/sidebar.tsx +1870 -0
  149. package/registry/skeleton/item.json +4 -0
  150. package/registry/skeleton/skeleton.tsx +47 -0
  151. package/registry/slider/item.json +4 -0
  152. package/registry/slider/slider.tsx +149 -0
  153. package/registry/spinner/item.json +4 -0
  154. package/registry/spinner/spinner.tsx +51 -0
  155. package/registry/switch/item.json +4 -0
  156. package/registry/switch/switch.tsx +93 -0
  157. package/registry/table/item.json +4 -0
  158. package/registry/table/table.tsx +373 -0
  159. package/registry/tabs/item.json +4 -0
  160. package/registry/tabs/tabs.tsx +209 -0
  161. package/registry/textarea/item.json +4 -0
  162. package/registry/textarea/textarea.tsx +51 -0
  163. package/registry/toast/item.json +4 -0
  164. package/registry/toast/toast.tsx +949 -0
  165. package/registry/toggle/item.json +4 -0
  166. package/registry/toggle/toggle.tsx +110 -0
  167. package/registry/toggle-group/item.json +4 -0
  168. package/registry/toggle-group/toggle-group.tsx +189 -0
  169. package/registry/tooltip/item.json +4 -0
  170. package/registry/tooltip/tooltip.tsx +169 -0
  171. package/registry/trace/item.json +4 -0
  172. package/registry/trace/trace.tsx +356 -0
  173. package/registry/ui-strings/item.json +5 -0
  174. package/registry/ui-strings/ui-strings.tsx +454 -0
  175. package/registry/use-highlighted-code/item.json +5 -0
  176. package/registry/use-highlighted-code/use-highlighted-code.tsx +145 -0
  177. package/registry/use-mobile/item.json +5 -0
  178. package/registry/use-mobile/use-mobile.ts +46 -0
  179. package/registry/utils/item.json +5 -0
  180. package/registry/utils/utils.ts +38 -0
  181. package/registry.json +3274 -0
  182. package/styles/animations.css +321 -0
  183. package/styles/theme-core.css +55 -0
  184. package/styles/theme.css +12 -0
  185. package/styles/themes/gray.css +69 -0
  186. package/styles/themes/neutral.css +69 -0
  187. package/styles/themes/slate.css +69 -0
  188. package/styles/themes/stone.css +69 -0
  189. package/styles/themes/zinc.css +69 -0
  190. package/styles/typeset.css +251 -0
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "Drawer",
3
+ "description": "A bottom sheet for mobile: drag the grip to snap it between heights or dismiss it, with the keyboard as well; it is a dialog."
4
+ }
@@ -0,0 +1,692 @@
1
+ import { splitProps } from "@shadow-js/core";
2
+ import {
3
+ DropdownMenu as DropdownMenuPrimitive,
4
+ type DropdownMenuProps,
5
+ type DropdownMenuTriggerProps,
6
+ type MenuCheckboxItemProps,
7
+ type MenuContentProps,
8
+ type MenuGroupProps,
9
+ type MenuItemOwnProps,
10
+ type MenuLabelProps,
11
+ type MenuPortalProps,
12
+ type MenuRadioGroupProps,
13
+ type MenuRadioItemProps,
14
+ type MenuSeparatorProps,
15
+ type MenuSubContentProps,
16
+ type MenuSubProps,
17
+ type MenuSubTriggerProps,
18
+ type PolymorphicProps,
19
+ type PolymorphicTarget,
20
+ } from "@shadow-js/primitives";
21
+ import type { ComponentProps, JSX } from "@shadow-js/web";
22
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from "@/components/ui/icons";
23
+ import { type ClassValue, cn } from "@/lib/utils";
24
+
25
+ /** The props as the implementation sees them: the typed own props plus whatever the target takes. */
26
+ type Loose = {
27
+ readonly as?: PolymorphicTarget;
28
+ readonly class?: ClassValue;
29
+ readonly inset?: boolean | undefined;
30
+ readonly variant?: "default" | "destructive" | undefined;
31
+ readonly children?: JSX.Element | undefined;
32
+ readonly [prop: string]: unknown;
33
+ };
34
+
35
+ /** The placement and entrance animation shared by the menu panels. */
36
+ const PANEL =
37
+ "z-50 min-w-[8rem] origin-(--primitives-transform-origin) rounded-md border bg-popover p-1 text-popover-foreground outline-hidden data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95";
38
+
39
+ /** What every selectable row shares. */
40
+ const ROW =
41
+ "relative flex cursor-default items-center gap-2 rounded-sm text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 forced-colors:focus:bg-[Highlight] forced-colors:focus:text-[HighlightText] forced-colors:data-[disabled]:text-[GrayText] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4";
42
+
43
+ /**
44
+ * A menu opened from a button, such as a user menu or a row's actions. It is controlled with `open` and
45
+ * `onOpenChange`, or uncontrolled with `defaultOpen`. `dir="rtl"` mirrors the arrow keys and the side
46
+ * of submenus.
47
+ *
48
+ * Keyboard and ARIA: this follows the WAI-ARIA menu button pattern. Enter and Space on the trigger
49
+ * toggle the menu, and opening it focuses the first item. ArrowDown opens it with the first item
50
+ * focused, and ArrowUp with the last. In the menu, the arrow keys, Home, End, PageUp, PageDown and
51
+ * typeahead move between items, and Enter or Space selects the focused item. Escape or Tab closes the
52
+ * menu and returns focus to the trigger. The trigger has `aria-haspopup="menu"`, `aria-expanded`, and
53
+ * `aria-controls` while the menu is open. The content is a `role="menu"` of `menuitem`,
54
+ * `menuitemcheckbox` and `menuitemradio` items, labeled by the trigger. A disabled item is skipped by
55
+ * the arrow keys and cannot be selected.
56
+ *
57
+ * shadcn/ui differences: `as` replaces `asChild` on the trigger and on items. The prop is `class`, not `className`, and every part has a `data-slot`.
58
+ *
59
+ * @param props The menu props: see {@link DropdownMenuProps}.
60
+ * @returns The menu. It renders no element of its own.
61
+ * @example
62
+ * import { Button } from "@/components/ui/button";
63
+ * import {
64
+ * DropdownMenu,
65
+ * DropdownMenuContent,
66
+ * DropdownMenuItem,
67
+ * DropdownMenuLabel,
68
+ * DropdownMenuSeparator,
69
+ * DropdownMenuTrigger,
70
+ * } from "@/components/ui/dropdown-menu";
71
+ *
72
+ * export function AccountMenu() {
73
+ * return (
74
+ * <DropdownMenu>
75
+ * <DropdownMenuTrigger as={Button} variant="outline">
76
+ * Open
77
+ * </DropdownMenuTrigger>
78
+ * <DropdownMenuContent>
79
+ * <DropdownMenuLabel>My Account</DropdownMenuLabel>
80
+ * <DropdownMenuSeparator />
81
+ * <DropdownMenuItem>Profile</DropdownMenuItem>
82
+ * </DropdownMenuContent>
83
+ * </DropdownMenu>
84
+ * );
85
+ * }
86
+ * @see {@link DropdownMenuTrigger}
87
+ * @see {@link DropdownMenuContent}
88
+ * @since 1.0.0
89
+ */
90
+ function DropdownMenu(props: DropdownMenuProps): JSX.Element {
91
+ return <DropdownMenuPrimitive {...props} />;
92
+ }
93
+
94
+ /**
95
+ * Renders the menu into `document.body`, or into the `container` you pass, while it is open. Nothing is
96
+ * rendered on the server. {@link DropdownMenuContent} already portals itself, so use this only to choose
97
+ * another container.
98
+ *
99
+ * @param props The portal props: `container` and `forceMount`.
100
+ * @returns The portal, or nothing while the menu is closed.
101
+ * @throws Error when rendered outside a {@link DropdownMenu}.
102
+ * @example
103
+ * import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
104
+ *
105
+ * export function Portaled() {
106
+ * return (
107
+ * <DropdownMenu>
108
+ * <DropdownMenuTrigger>Open</DropdownMenuTrigger>
109
+ * <DropdownMenuPortal>
110
+ * <DropdownMenuContent>
111
+ * <DropdownMenuItem>Profile</DropdownMenuItem>
112
+ * </DropdownMenuContent>
113
+ * </DropdownMenuPortal>
114
+ * </DropdownMenu>
115
+ * );
116
+ * }
117
+ * @see {@link DropdownMenuContent}
118
+ * @since 1.0.0
119
+ */
120
+ function DropdownMenuPortal(props: MenuPortalProps): JSX.Element {
121
+ return <DropdownMenuPrimitive.Portal {...props} />;
122
+ }
123
+
124
+ /**
125
+ * The button that opens the menu and anchors it. It is a native button by default; `as` renders another
126
+ * element, such as the shadcn `Button`. The keys are described on {@link DropdownMenu}.
127
+ *
128
+ * @param props The trigger props: the attributes of the rendered element, and `as`.
129
+ * @returns The trigger.
130
+ * @throws Error when rendered outside a {@link DropdownMenu}.
131
+ * @example
132
+ * import { Button } from "@/components/ui/button";
133
+ * import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
134
+ *
135
+ * export function OpenMenu() {
136
+ * return (
137
+ * <DropdownMenu>
138
+ * <DropdownMenuTrigger as={Button}>Open</DropdownMenuTrigger>
139
+ * <DropdownMenuContent>
140
+ * <DropdownMenuItem>Profile</DropdownMenuItem>
141
+ * </DropdownMenuContent>
142
+ * </DropdownMenu>
143
+ * );
144
+ * }
145
+ * @see {@link DropdownMenu}
146
+ * @since 1.0.0
147
+ */
148
+ function DropdownMenuTrigger<T extends PolymorphicTarget = "button">(
149
+ props: DropdownMenuTriggerProps<T>,
150
+ ): JSX.Element {
151
+ return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
152
+ }
153
+
154
+ /**
155
+ * The menu panel. It is rendered in `document.body`, placed against the trigger, and takes focus when it
156
+ * opens. The gap between the trigger and the panel is 4 px unless you pass `sideOffset`.
157
+ *
158
+ * @param props The panel props: the placement options (such as `align`), `sideOffset`, `class` and the
159
+ * attributes of the rendered `div`.
160
+ * @returns The panel, rendered in a portal while the menu is open.
161
+ * @throws Error when rendered outside a {@link DropdownMenu}.
162
+ * @example
163
+ * import { Button } from "@/components/ui/button";
164
+ * import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
165
+ *
166
+ * export function Actions() {
167
+ * return (
168
+ * <DropdownMenu>
169
+ * <DropdownMenuTrigger as={Button} variant="outline">
170
+ * Actions
171
+ * </DropdownMenuTrigger>
172
+ * <DropdownMenuContent align="end" class="w-56">
173
+ * <DropdownMenuItem>Duplicate</DropdownMenuItem>
174
+ * </DropdownMenuContent>
175
+ * </DropdownMenu>
176
+ * );
177
+ * }
178
+ * @see {@link DropdownMenuTrigger}
179
+ * @since 1.0.0
180
+ */
181
+ function DropdownMenuContent(props: MenuContentProps): JSX.Element {
182
+ const [local, rest] = splitProps(props, ["class", "sideOffset"]);
183
+ return (
184
+ <DropdownMenuPortal>
185
+ <DropdownMenuPrimitive.Content
186
+ data-slot="dropdown-menu-content"
187
+ sideOffset={local.sideOffset ?? 4}
188
+ class={cn(
189
+ PANEL,
190
+ "max-h-(--primitives-available-height) overflow-x-hidden overflow-y-auto shadow-md",
191
+ local.class,
192
+ )}
193
+ {...rest}
194
+ />
195
+ </DropdownMenuPortal>
196
+ );
197
+ }
198
+
199
+ /**
200
+ * A group of related items, announced as a `role="group"`. The arrow keys skip it.
201
+ *
202
+ * @param props The group props: the attributes of the rendered `div`.
203
+ * @returns The group.
204
+ * @throws Error when rendered outside a {@link DropdownMenu}.
205
+ * @example
206
+ * import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
207
+ *
208
+ * export function Account() {
209
+ * return (
210
+ * <DropdownMenu>
211
+ * <DropdownMenuTrigger>Open</DropdownMenuTrigger>
212
+ * <DropdownMenuContent>
213
+ * <DropdownMenuGroup>
214
+ * <DropdownMenuItem>Profile</DropdownMenuItem>
215
+ * </DropdownMenuGroup>
216
+ * </DropdownMenuContent>
217
+ * </DropdownMenu>
218
+ * );
219
+ * }
220
+ * @see {@link DropdownMenuItem}
221
+ * @since 1.0.0
222
+ */
223
+ function DropdownMenuGroup(props: MenuGroupProps): JSX.Element {
224
+ return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
225
+ }
226
+
227
+ /**
228
+ * Own props of {@link DropdownMenuItem}: the extra parts the item adds to the menu item props.
229
+ * @see {@link DropdownMenuItem}
230
+ * @since 1.0.0
231
+ */
232
+ export interface DropdownMenuItemExtraProps {
233
+ /**
234
+ * Indents the item so it lines up with items that have an indicator.
235
+ * @default false
236
+ */
237
+ readonly inset?: boolean | undefined;
238
+ /**
239
+ * Colors the item for a dangerous action when `destructive`.
240
+ * @default "default"
241
+ */
242
+ readonly variant?: "default" | "destructive" | undefined;
243
+ }
244
+
245
+ /**
246
+ * Props of {@link DropdownMenuItem}: `onSelect` (called when the item is picked, and `preventDefault()`
247
+ * keeps the menu open), `disabled`, `inset`, `variant` and every attribute of the chosen element.
248
+ * @see {@link DropdownMenuItem}
249
+ * @since 1.0.0
250
+ */
251
+ export type DropdownMenuItemProps<T extends PolymorphicTarget = "div"> = PolymorphicProps<
252
+ "div",
253
+ MenuItemOwnProps & DropdownMenuItemExtraProps,
254
+ T
255
+ >;
256
+
257
+ /**
258
+ * An action in the menu. Selecting it, by a click, Enter or Space, closes the menu unless `onSelect`
259
+ * calls `event.preventDefault()`. A disabled item is skipped by the arrow keys and cannot be selected.
260
+ * It is a `role="menuitem"` with `tabindex="-1"`, and it has `data-highlighted` while it has focus.
261
+ * shadcn's `asChild` is the `as` prop here: `<DropdownMenuItem as="a" href="/settings">`.
262
+ *
263
+ * @param props The item props: see {@link DropdownMenuItemProps}.
264
+ * @returns The menu item.
265
+ * @throws Error when rendered outside a {@link DropdownMenu}.
266
+ * @example
267
+ * import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
268
+ *
269
+ * export function Delete() {
270
+ * const remove = (): void => {
271
+ * // Delete the item here.
272
+ * };
273
+ * return (
274
+ * <DropdownMenu>
275
+ * <DropdownMenuTrigger>Open</DropdownMenuTrigger>
276
+ * <DropdownMenuContent>
277
+ * <DropdownMenuItem variant="destructive" onSelect={remove}>
278
+ * Delete
279
+ * </DropdownMenuItem>
280
+ * </DropdownMenuContent>
281
+ * </DropdownMenu>
282
+ * );
283
+ * }
284
+ * @see {@link DropdownMenuContent}
285
+ * @since 1.0.0
286
+ */
287
+ function DropdownMenuItem<T extends PolymorphicTarget = "div">(
288
+ props: DropdownMenuItemProps<T>,
289
+ ): JSX.Element {
290
+ const [local, rest] = splitProps(props as unknown as Loose, ["class", "inset", "variant"]);
291
+ return (
292
+ <DropdownMenuPrimitive.Item
293
+ data-slot="dropdown-menu-item"
294
+ data-inset={local.inset === true ? "" : undefined}
295
+ data-variant={local.variant ?? "default"}
296
+ class={cn(
297
+ ROW,
298
+ "px-2 py-1.5 data-[inset]:ps-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:*:[svg]:text-destructive! [&_svg:not([class*='text-'])]:text-muted-foreground",
299
+ local.class,
300
+ )}
301
+ {...rest}
302
+ />
303
+ );
304
+ }
305
+
306
+ /**
307
+ * An item that toggles a boolean and shows a check mark while it is checked. Enter, Space and a click
308
+ * toggle it, and it has `role="menuitemcheckbox"` with `aria-checked` (`"mixed"` while indeterminate).
309
+ * The state changes even when `onSelect` keeps the menu open.
310
+ *
311
+ * @param props The item props: `checked` (`true`, `false` or `"indeterminate"`), `onCheckedChange`, and
312
+ * the attributes of the item.
313
+ * @returns The checkbox item.
314
+ * @throws Error when rendered outside a {@link DropdownMenu}.
315
+ * @example
316
+ * import { useSignal } from "@shadow-js/core";
317
+ * import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
318
+ *
319
+ * export function View() {
320
+ * const [bookmarks, setBookmarks] = useSignal(true);
321
+ * return (
322
+ * <DropdownMenu>
323
+ * <DropdownMenuTrigger>View</DropdownMenuTrigger>
324
+ * <DropdownMenuContent>
325
+ * <DropdownMenuCheckboxItem checked={bookmarks()} onCheckedChange={setBookmarks}>
326
+ * Show bookmarks
327
+ * </DropdownMenuCheckboxItem>
328
+ * </DropdownMenuContent>
329
+ * </DropdownMenu>
330
+ * );
331
+ * }
332
+ * @see {@link DropdownMenuRadioGroup}
333
+ * @since 1.0.0
334
+ */
335
+ function DropdownMenuCheckboxItem(props: MenuCheckboxItemProps): JSX.Element {
336
+ const [local, rest] = splitProps(props, ["class", "children"]);
337
+ return (
338
+ <DropdownMenuPrimitive.CheckboxItem
339
+ data-slot="dropdown-menu-checkbox-item"
340
+ class={cn(ROW, "py-1.5 ps-8 pe-2", local.class)}
341
+ {...rest}
342
+ >
343
+ <span class="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center">
344
+ <DropdownMenuPrimitive.ItemIndicator>
345
+ <CheckIcon class="size-4" />
346
+ </DropdownMenuPrimitive.ItemIndicator>
347
+ </span>
348
+ {local.children}
349
+ </DropdownMenuPrimitive.CheckboxItem>
350
+ );
351
+ }
352
+
353
+ /**
354
+ * A group of mutually exclusive {@link DropdownMenuRadioItem}s. It is a `role="group"` and handles no
355
+ * keys itself; the items are navigated like any other menu item.
356
+ *
357
+ * @param props The group props: `value` (the selected item's value), `onValueChange`, and the
358
+ * attributes of the rendered `div`.
359
+ * @returns The group.
360
+ * @throws Error when rendered outside a {@link DropdownMenu}.
361
+ * @example
362
+ * import { useSignal } from "@shadow-js/core";
363
+ * import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
364
+ *
365
+ * export function Position() {
366
+ * const [position, setPosition] = useSignal("bottom");
367
+ * return (
368
+ * <DropdownMenu>
369
+ * <DropdownMenuTrigger>Position</DropdownMenuTrigger>
370
+ * <DropdownMenuContent>
371
+ * <DropdownMenuRadioGroup value={position()} onValueChange={setPosition}>
372
+ * <DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
373
+ * <DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
374
+ * </DropdownMenuRadioGroup>
375
+ * </DropdownMenuContent>
376
+ * </DropdownMenu>
377
+ * );
378
+ * }
379
+ * @see {@link DropdownMenuRadioItem}
380
+ * @since 1.0.0
381
+ */
382
+ function DropdownMenuRadioGroup(props: MenuRadioGroupProps): JSX.Element {
383
+ return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
384
+ }
385
+
386
+ /**
387
+ * An item of a {@link DropdownMenuRadioGroup}, with a dot while it is the selected one. It is a
388
+ * `role="menuitemradio"` with `aria-checked`. Enter, Space and a click select it, even when `onSelect`
389
+ * keeps the menu open.
390
+ *
391
+ * @param props The item props: `value` and the attributes of the item.
392
+ * @returns The radio item.
393
+ * @throws Error when rendered outside a {@link DropdownMenuRadioGroup}.
394
+ * @example
395
+ * import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
396
+ *
397
+ * export function Density() {
398
+ * return (
399
+ * <DropdownMenu>
400
+ * <DropdownMenuTrigger>Density</DropdownMenuTrigger>
401
+ * <DropdownMenuContent>
402
+ * <DropdownMenuRadioGroup value="compact">
403
+ * <DropdownMenuRadioItem value="compact">Compact</DropdownMenuRadioItem>
404
+ * </DropdownMenuRadioGroup>
405
+ * </DropdownMenuContent>
406
+ * </DropdownMenu>
407
+ * );
408
+ * }
409
+ * @see {@link DropdownMenuRadioGroup}
410
+ * @since 1.0.0
411
+ */
412
+ function DropdownMenuRadioItem(props: MenuRadioItemProps): JSX.Element {
413
+ const [local, rest] = splitProps(props, ["class", "children"]);
414
+ return (
415
+ <DropdownMenuPrimitive.RadioItem
416
+ data-slot="dropdown-menu-radio-item"
417
+ class={cn(ROW, "py-1.5 ps-8 pe-2", local.class)}
418
+ {...rest}
419
+ >
420
+ <span class="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center">
421
+ <DropdownMenuPrimitive.ItemIndicator>
422
+ <CircleIcon class="size-2 fill-current" />
423
+ </DropdownMenuPrimitive.ItemIndicator>
424
+ </span>
425
+ {local.children}
426
+ </DropdownMenuPrimitive.RadioItem>
427
+ );
428
+ }
429
+
430
+ /**
431
+ * Props of {@link DropdownMenuLabel}: the attributes of the rendered `div` and `inset`, which indents the
432
+ * heading to line up with indented items.
433
+ * @see {@link DropdownMenuLabel}
434
+ * @since 1.0.0
435
+ */
436
+ export type DropdownMenuLabelProps = MenuLabelProps & {
437
+ /**
438
+ * Indents the heading to line up with items that have an indicator.
439
+ * @default false
440
+ */
441
+ readonly inset?: boolean | undefined;
442
+ };
443
+
444
+ /**
445
+ * A heading for a group of items. It has no role, is not focusable, and the arrow keys skip it.
446
+ *
447
+ * @param props The heading props: see {@link DropdownMenuLabelProps}.
448
+ * @returns The heading.
449
+ * @throws Error when rendered outside a {@link DropdownMenu}.
450
+ * @example
451
+ * import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
452
+ *
453
+ * export function AccountMenu() {
454
+ * return (
455
+ * <DropdownMenu>
456
+ * <DropdownMenuTrigger>Account</DropdownMenuTrigger>
457
+ * <DropdownMenuContent>
458
+ * <DropdownMenuLabel>My Account</DropdownMenuLabel>
459
+ * <DropdownMenuItem>Profile</DropdownMenuItem>
460
+ * </DropdownMenuContent>
461
+ * </DropdownMenu>
462
+ * );
463
+ * }
464
+ * @see {@link DropdownMenuGroup}
465
+ * @since 1.0.0
466
+ */
467
+ function DropdownMenuLabel(props: DropdownMenuLabelProps): JSX.Element {
468
+ const [local, rest] = splitProps(props, ["class", "inset"]);
469
+ return (
470
+ <DropdownMenuPrimitive.Label
471
+ data-slot="dropdown-menu-label"
472
+ data-inset={local.inset === true ? "" : undefined}
473
+ class={cn("px-2 py-1.5 text-sm font-medium data-[inset]:ps-8", local.class)}
474
+ {...rest}
475
+ />
476
+ );
477
+ }
478
+
479
+ /**
480
+ * A divider between groups of items. It is a `role="separator"` with a horizontal orientation.
481
+ *
482
+ * @param props The separator props: the attributes of the rendered `div`.
483
+ * @returns The separator.
484
+ * @throws Error when rendered outside a {@link DropdownMenu}.
485
+ * @example
486
+ * import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
487
+ *
488
+ * export function Divided() {
489
+ * return (
490
+ * <DropdownMenu>
491
+ * <DropdownMenuTrigger>Open</DropdownMenuTrigger>
492
+ * <DropdownMenuContent>
493
+ * <DropdownMenuItem>Back</DropdownMenuItem>
494
+ * <DropdownMenuSeparator />
495
+ * <DropdownMenuItem>Delete</DropdownMenuItem>
496
+ * </DropdownMenuContent>
497
+ * </DropdownMenu>
498
+ * );
499
+ * }
500
+ * @see {@link DropdownMenuGroup}
501
+ * @since 1.0.0
502
+ */
503
+ function DropdownMenuSeparator(props: MenuSeparatorProps): JSX.Element {
504
+ const [local, rest] = splitProps(props, ["class"]);
505
+ return (
506
+ <DropdownMenuPrimitive.Separator
507
+ data-slot="dropdown-menu-separator"
508
+ class={cn("-mx-1 my-1 h-px bg-border", local.class)}
509
+ {...rest}
510
+ />
511
+ );
512
+ }
513
+
514
+ /**
515
+ * A keyboard shortcut hint at the end of an item. It is decorative: it does not add a key binding.
516
+ *
517
+ * @param props The hint props: the attributes of the rendered `span`, and `children` (the shortcut text).
518
+ * @returns The hint.
519
+ * @example
520
+ * import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuShortcut, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
521
+ *
522
+ * export function Editing() {
523
+ * return (
524
+ * <DropdownMenu>
525
+ * <DropdownMenuTrigger>Edit</DropdownMenuTrigger>
526
+ * <DropdownMenuContent>
527
+ * <DropdownMenuItem>
528
+ * Save
529
+ * <DropdownMenuShortcut>Ctrl+S</DropdownMenuShortcut>
530
+ * </DropdownMenuItem>
531
+ * </DropdownMenuContent>
532
+ * </DropdownMenu>
533
+ * );
534
+ * }
535
+ * @see {@link DropdownMenuItem}
536
+ * @since 1.0.0
537
+ */
538
+ function DropdownMenuShortcut(props: ComponentProps<"span">): JSX.Element {
539
+ const [local, rest] = splitProps(props, ["class"]);
540
+ return (
541
+ <span
542
+ data-slot="dropdown-menu-shortcut"
543
+ class={cn("ms-auto text-xs tracking-widest text-muted-foreground", local.class)}
544
+ {...rest}
545
+ />
546
+ );
547
+ }
548
+
549
+ /**
550
+ * The root of a submenu. It renders no element; place a {@link DropdownMenuSubTrigger} and a
551
+ * {@link DropdownMenuSubContent} in it, inside a {@link DropdownMenuContent}. The submenu closes when its
552
+ * menu does.
553
+ *
554
+ * @param props The submenu props: `open`, `defaultOpen` and `onOpenChange` of the submenu.
555
+ * @returns The submenu's context around its children.
556
+ * @throws Error when rendered outside a {@link DropdownMenu}.
557
+ * @example
558
+ * import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
559
+ *
560
+ * export function Share() {
561
+ * return (
562
+ * <DropdownMenu>
563
+ * <DropdownMenuTrigger>Share</DropdownMenuTrigger>
564
+ * <DropdownMenuContent>
565
+ * <DropdownMenuSub>
566
+ * <DropdownMenuSubTrigger>Invite users</DropdownMenuSubTrigger>
567
+ * <DropdownMenuSubContent>
568
+ * <DropdownMenuItem>Email</DropdownMenuItem>
569
+ * </DropdownMenuSubContent>
570
+ * </DropdownMenuSub>
571
+ * </DropdownMenuContent>
572
+ * </DropdownMenu>
573
+ * );
574
+ * }
575
+ * @see {@link DropdownMenuSubTrigger}
576
+ * @see {@link DropdownMenuSubContent}
577
+ * @since 1.0.0
578
+ */
579
+ function DropdownMenuSub(props: MenuSubProps): JSX.Element {
580
+ return <DropdownMenuPrimitive.Sub {...props} />;
581
+ }
582
+
583
+ /**
584
+ * The item that opens a submenu, with a chevron that points the other way in right-to-left layouts. It is
585
+ * a `role="menuitem"` with `aria-haspopup="menu"` and `aria-expanded`. ArrowRight opens the submenu
586
+ * (ArrowLeft in right-to-left layouts).
587
+ *
588
+ * @param props The trigger props: the attributes of the rendered `div`, `inset`, and `children`.
589
+ * @returns The submenu trigger.
590
+ * @throws Error when rendered outside a {@link DropdownMenuSub}.
591
+ * @example
592
+ * import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
593
+ *
594
+ * export function Invite() {
595
+ * return (
596
+ * <DropdownMenu>
597
+ * <DropdownMenuTrigger>Share</DropdownMenuTrigger>
598
+ * <DropdownMenuContent>
599
+ * <DropdownMenuSub>
600
+ * <DropdownMenuSubTrigger inset>Invite users</DropdownMenuSubTrigger>
601
+ * <DropdownMenuSubContent>
602
+ * <DropdownMenuItem>Email</DropdownMenuItem>
603
+ * </DropdownMenuSubContent>
604
+ * </DropdownMenuSub>
605
+ * </DropdownMenuContent>
606
+ * </DropdownMenu>
607
+ * );
608
+ * }
609
+ * @see {@link DropdownMenuSub}
610
+ * @since 1.0.0
611
+ */
612
+ function DropdownMenuSubTrigger<T extends PolymorphicTarget = "div">(
613
+ props: MenuSubTriggerProps<T> & { readonly inset?: boolean | undefined },
614
+ ): JSX.Element {
615
+ const [local, rest] = splitProps(props as unknown as Loose, ["class", "inset", "children"]);
616
+ return (
617
+ <DropdownMenuPrimitive.SubTrigger
618
+ data-slot="dropdown-menu-sub-trigger"
619
+ data-inset={local.inset === true ? "" : undefined}
620
+ class={cn(
621
+ ROW,
622
+ "px-2 py-1.5 data-[inset]:ps-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground forced-colors:data-[state=open]:bg-[Highlight] forced-colors:data-[state=open]:text-[HighlightText] [&_svg:not([class*='text-'])]:text-muted-foreground",
623
+ local.class,
624
+ )}
625
+ {...rest}
626
+ >
627
+ {local.children}
628
+ <ChevronRightIcon class="ms-auto size-4 rtl:rotate-180" />
629
+ </DropdownMenuPrimitive.SubTrigger>
630
+ );
631
+ }
632
+
633
+ /**
634
+ * The submenu panel, placed beside its trigger. It is a `role="menu"` labeled by its trigger. ArrowLeft
635
+ * (ArrowRight in right-to-left layouts) and Escape close it and return focus to the trigger.
636
+ *
637
+ * @param props The panel props: the attributes of the rendered `div` and `class`.
638
+ * @returns The submenu panel.
639
+ * @throws Error when rendered outside a {@link DropdownMenuSub}.
640
+ * @example
641
+ * import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
642
+ *
643
+ * export function Share() {
644
+ * return (
645
+ * <DropdownMenu>
646
+ * <DropdownMenuTrigger>Share</DropdownMenuTrigger>
647
+ * <DropdownMenuContent>
648
+ * <DropdownMenuSub>
649
+ * <DropdownMenuSubTrigger>Invite users</DropdownMenuSubTrigger>
650
+ * <DropdownMenuSubContent class="w-44">
651
+ * <DropdownMenuItem>Email</DropdownMenuItem>
652
+ * </DropdownMenuSubContent>
653
+ * </DropdownMenuSub>
654
+ * </DropdownMenuContent>
655
+ * </DropdownMenu>
656
+ * );
657
+ * }
658
+ * @see {@link DropdownMenuSubTrigger}
659
+ * @since 1.0.0
660
+ */
661
+ function DropdownMenuSubContent(props: MenuSubContentProps): JSX.Element {
662
+ const [local, rest] = splitProps(props, ["class"]);
663
+ return (
664
+ <DropdownMenuPrimitive.SubContent
665
+ data-slot="dropdown-menu-sub-content"
666
+ class={cn(
667
+ PANEL,
668
+ "max-h-(--primitives-available-height) overflow-x-hidden overflow-y-auto shadow-lg",
669
+ local.class,
670
+ )}
671
+ {...rest}
672
+ />
673
+ );
674
+ }
675
+
676
+ export {
677
+ DropdownMenu,
678
+ DropdownMenuCheckboxItem,
679
+ DropdownMenuContent,
680
+ DropdownMenuGroup,
681
+ DropdownMenuItem,
682
+ DropdownMenuLabel,
683
+ DropdownMenuPortal,
684
+ DropdownMenuRadioGroup,
685
+ DropdownMenuRadioItem,
686
+ DropdownMenuSeparator,
687
+ DropdownMenuShortcut,
688
+ DropdownMenuSub,
689
+ DropdownMenuSubContent,
690
+ DropdownMenuSubTrigger,
691
+ DropdownMenuTrigger,
692
+ };