@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": "Command",
3
+ "description": "A command palette: a search field and a filtered, keyboard-navigable list of commands, inline or in a dialog."
4
+ }
@@ -0,0 +1,697 @@
1
+ import { splitProps } from "@shadow-js/core";
2
+ import {
3
+ type ContextMenuContentProps,
4
+ ContextMenu as ContextMenuPrimitive,
5
+ type ContextMenuProps,
6
+ type ContextMenuTriggerProps,
7
+ type MenuCheckboxItemProps,
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 at the pointer by a right click, the keyboard's menu key or Shift+F10, or by a long
45
+ * press on touch. Use it for actions on one area of the page, such as a canvas or a file row.
46
+ * `dir="rtl"` mirrors the arrow keys and the side of submenus.
47
+ *
48
+ * Keyboard and ARIA: Shift+F10 or the ContextMenu key on the focused trigger opens the menu with its
49
+ * first item focused. The menu is a `role="menu"` of `menuitem`, `menuitemcheckbox` and
50
+ * `menuitemradio` items. Arrow keys, Home, End and typeahead move between items, Enter or Space
51
+ * selects the focused item, and Escape or Tab closes the menu and returns focus to the element that had it before the menu opened. The
52
+ * trigger is a Tab stop and has no role of its own. A disabled item is skipped by the arrow keys and
53
+ * cannot be selected.
54
+ *
55
+ * shadcn/ui differences: the trigger renders a `span`, so pass `as="div"` for a block-level area.
56
+ * `as` replaces `asChild`. The prop is `class`, not `className`, and every part has a `data-slot`.
57
+ *
58
+ * @param props The root props: see {@link ContextMenuProps}.
59
+ * @returns The menu, which renders no element of its own.
60
+ * @example
61
+ * import {
62
+ * ContextMenu,
63
+ * ContextMenuContent,
64
+ * ContextMenuItem,
65
+ * ContextMenuTrigger,
66
+ * } from "@/components/ui/context-menu";
67
+ *
68
+ * export function Canvas() {
69
+ * return (
70
+ * <ContextMenu>
71
+ * <ContextMenuTrigger class="flex h-40 w-72 items-center justify-center rounded-md border border-dashed">
72
+ * Right click here
73
+ * </ContextMenuTrigger>
74
+ * <ContextMenuContent class="w-52">
75
+ * <ContextMenuItem>Back</ContextMenuItem>
76
+ * <ContextMenuItem>Forward</ContextMenuItem>
77
+ * </ContextMenuContent>
78
+ * </ContextMenu>
79
+ * );
80
+ * }
81
+ * @see {@link ContextMenuTrigger}
82
+ * @see {@link ContextMenuContent}
83
+ * @since 1.0.0
84
+ */
85
+ function ContextMenu(props: ContextMenuProps): JSX.Element {
86
+ return <ContextMenuPrimitive {...props} />;
87
+ }
88
+
89
+ /**
90
+ * Renders the menu into `document.body`, or into the `container` you pass, while it is open. Nothing
91
+ * is rendered on the server. {@link ContextMenuContent} already portals itself, so use this only to
92
+ * choose another container.
93
+ *
94
+ * @param props The portal props: `container` (where to render the menu) and `forceMount`.
95
+ * @returns The portal, or nothing while the menu is closed.
96
+ * @example
97
+ * import {
98
+ * ContextMenu,
99
+ * ContextMenuContent,
100
+ * ContextMenuItem,
101
+ * ContextMenuPortal,
102
+ * ContextMenuTrigger,
103
+ * } from "@/components/ui/context-menu";
104
+ *
105
+ * export function Panel() {
106
+ * return (
107
+ * <ContextMenu>
108
+ * <ContextMenuTrigger>Right click here</ContextMenuTrigger>
109
+ * <ContextMenuPortal>
110
+ * <ContextMenuContent>
111
+ * <ContextMenuItem>Back</ContextMenuItem>
112
+ * </ContextMenuContent>
113
+ * </ContextMenuPortal>
114
+ * </ContextMenu>
115
+ * );
116
+ * }
117
+ * @see {@link ContextMenuContent}
118
+ * @since 1.0.0
119
+ */
120
+ function ContextMenuPortal(props: MenuPortalProps): JSX.Element {
121
+ return <ContextMenuPrimitive.Portal {...props} />;
122
+ }
123
+
124
+ /**
125
+ * The area that opens the menu. It renders a focusable `span` (a Tab stop); pass `as="div"` for a
126
+ * block-level area. A right click, a touch or pen held down for 700 ms, Shift+F10 and the ContextMenu
127
+ * key open the menu at the trigger. Set `disabled` to keep the browser's own context menu.
128
+ *
129
+ * @param props The trigger props: the attributes of the rendered element, `as`, and `disabled`.
130
+ * @returns The trigger element.
131
+ * @throws Error when rendered outside a {@link ContextMenu}.
132
+ * @example
133
+ * import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from "@/components/ui/context-menu";
134
+ *
135
+ * export function Row() {
136
+ * return (
137
+ * <ContextMenu>
138
+ * <ContextMenuTrigger as="div" class="h-40 rounded-md border">
139
+ * Right click here
140
+ * </ContextMenuTrigger>
141
+ * <ContextMenuContent>
142
+ * <ContextMenuItem>Back</ContextMenuItem>
143
+ * </ContextMenuContent>
144
+ * </ContextMenu>
145
+ * );
146
+ * }
147
+ * @see {@link ContextMenu}
148
+ * @since 1.0.0
149
+ */
150
+ function ContextMenuTrigger<T extends PolymorphicTarget = "span">(
151
+ props: ContextMenuTriggerProps<T>,
152
+ ): JSX.Element {
153
+ return <ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />;
154
+ }
155
+
156
+ /**
157
+ * The menu panel. It is rendered in `document.body` and placed beside the point that opened it, and
158
+ * it takes focus when it opens. Its height is limited to the space available in the viewport.
159
+ *
160
+ * @param props The panel props: the attributes of the rendered `div` and `class`, which is merged with
161
+ * the panel classes.
162
+ * @returns The panel, rendered in a portal.
163
+ * @throws Error when rendered outside a {@link ContextMenu}.
164
+ * @example
165
+ * import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from "@/components/ui/context-menu";
166
+ *
167
+ * export function Menu() {
168
+ * return (
169
+ * <ContextMenu>
170
+ * <ContextMenuTrigger>Right click here</ContextMenuTrigger>
171
+ * <ContextMenuContent class="w-52">
172
+ * <ContextMenuItem>Back</ContextMenuItem>
173
+ * </ContextMenuContent>
174
+ * </ContextMenu>
175
+ * );
176
+ * }
177
+ * @see {@link ContextMenuTrigger}
178
+ * @since 1.0.0
179
+ */
180
+ function ContextMenuContent(props: ContextMenuContentProps): JSX.Element {
181
+ const [local, rest] = splitProps(props, ["class"]);
182
+ return (
183
+ <ContextMenuPortal>
184
+ <ContextMenuPrimitive.Content
185
+ data-slot="context-menu-content"
186
+ class={cn(
187
+ PANEL,
188
+ "max-h-(--primitives-available-height) overflow-x-hidden overflow-y-auto shadow-md",
189
+ local.class,
190
+ )}
191
+ {...rest}
192
+ />
193
+ </ContextMenuPortal>
194
+ );
195
+ }
196
+
197
+ /**
198
+ * A group of related items, announced as a `role="group"`. The arrow keys skip it.
199
+ *
200
+ * @param props The group props: the attributes of the rendered `div`.
201
+ * @returns The group.
202
+ * @throws Error when rendered outside a {@link ContextMenu}.
203
+ * @example
204
+ * import { ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuTrigger } from "@/components/ui/context-menu";
205
+ *
206
+ * export function Account() {
207
+ * return (
208
+ * <ContextMenu>
209
+ * <ContextMenuTrigger>Right click here</ContextMenuTrigger>
210
+ * <ContextMenuContent>
211
+ * <ContextMenuGroup>
212
+ * <ContextMenuItem>Profile</ContextMenuItem>
213
+ * </ContextMenuGroup>
214
+ * </ContextMenuContent>
215
+ * </ContextMenu>
216
+ * );
217
+ * }
218
+ * @see {@link ContextMenuItem}
219
+ * @since 1.0.0
220
+ */
221
+ function ContextMenuGroup(props: MenuGroupProps): JSX.Element {
222
+ return <ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />;
223
+ }
224
+
225
+ /**
226
+ * Own props of {@link ContextMenuItem}: the extra parts the item adds to the menu item props.
227
+ * @see {@link ContextMenuItem}
228
+ * @since 1.0.0
229
+ */
230
+ export interface ContextMenuItemExtraProps {
231
+ /**
232
+ * Indents the item so it lines up with items that have an indicator.
233
+ * @default false
234
+ */
235
+ readonly inset?: boolean | undefined;
236
+ /**
237
+ * Colors the item for a dangerous action when `destructive`.
238
+ * @default "default"
239
+ */
240
+ readonly variant?: "default" | "destructive" | undefined;
241
+ }
242
+
243
+ /**
244
+ * Props of {@link ContextMenuItem}: `onSelect` (called when the item is picked, and `preventDefault()`
245
+ * keeps the menu open), `disabled`, `inset`, `variant` and every attribute of the chosen element.
246
+ * @see {@link ContextMenuItem}
247
+ * @since 1.0.0
248
+ */
249
+ export type ContextMenuItemProps<T extends PolymorphicTarget = "div"> = PolymorphicProps<
250
+ "div",
251
+ MenuItemOwnProps & ContextMenuItemExtraProps,
252
+ T
253
+ >;
254
+
255
+ /**
256
+ * An action in the menu. Selecting it, by a click, by Enter or by Space, closes the menu unless
257
+ * `onSelect` calls `event.preventDefault()`. A disabled item is skipped by the arrow keys and cannot
258
+ * be selected. While it has focus it has `data-highlighted`.
259
+ *
260
+ * Keyboard and ARIA: the item is a `role="menuitem"` with `tabindex="-1"`; the menu moves focus to it.
261
+ * The item renders as a `div` by default. shadcn's `asChild` is the `as` prop here, for example
262
+ * `<ContextMenuItem as="a" href="/settings">`.
263
+ *
264
+ * @param props The item props: see {@link ContextMenuItemProps}.
265
+ * @returns The menu item.
266
+ * @throws Error when rendered outside a {@link ContextMenu}.
267
+ * @example
268
+ * import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from "@/components/ui/context-menu";
269
+ *
270
+ * export function Delete() {
271
+ * const remove = (): void => {
272
+ * // Delete the selected row here.
273
+ * };
274
+ * return (
275
+ * <ContextMenu>
276
+ * <ContextMenuTrigger>Right click here</ContextMenuTrigger>
277
+ * <ContextMenuContent>
278
+ * <ContextMenuItem variant="destructive" onSelect={remove}>
279
+ * Delete
280
+ * </ContextMenuItem>
281
+ * </ContextMenuContent>
282
+ * </ContextMenu>
283
+ * );
284
+ * }
285
+ * @see {@link ContextMenuContent}
286
+ * @see {@link ContextMenuCheckboxItem}
287
+ * @since 1.0.0
288
+ */
289
+ function ContextMenuItem<T extends PolymorphicTarget = "div">(
290
+ props: ContextMenuItemProps<T>,
291
+ ): JSX.Element {
292
+ const [local, rest] = splitProps(props as unknown as Loose, ["class", "inset", "variant"]);
293
+ return (
294
+ <ContextMenuPrimitive.Item
295
+ data-slot="context-menu-item"
296
+ data-inset={local.inset === true ? "" : undefined}
297
+ data-variant={local.variant ?? "default"}
298
+ class={cn(
299
+ ROW,
300
+ "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",
301
+ local.class,
302
+ )}
303
+ {...rest}
304
+ />
305
+ );
306
+ }
307
+
308
+ /**
309
+ * An item that toggles a boolean and shows a check mark while it is checked. Enter, Space and a click
310
+ * toggle it, and it has `role="menuitemcheckbox"` with `aria-checked` (`"mixed"` while indeterminate).
311
+ * The state changes even when `onSelect` keeps the menu open.
312
+ *
313
+ * @param props The item props: `checked` (`true`, `false` or `"indeterminate"`), `onCheckedChange`, and
314
+ * the attributes of the item.
315
+ * @returns The checkbox item.
316
+ * @throws Error when rendered outside a {@link ContextMenu}.
317
+ * @example
318
+ * import { useSignal } from "@shadow-js/core";
319
+ * import { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuTrigger } from "@/components/ui/context-menu";
320
+ *
321
+ * export function View() {
322
+ * const [bookmarks, setBookmarks] = useSignal(true);
323
+ * return (
324
+ * <ContextMenu>
325
+ * <ContextMenuTrigger>Right click here</ContextMenuTrigger>
326
+ * <ContextMenuContent>
327
+ * <ContextMenuCheckboxItem checked={bookmarks()} onCheckedChange={setBookmarks}>
328
+ * Show bookmarks
329
+ * </ContextMenuCheckboxItem>
330
+ * </ContextMenuContent>
331
+ * </ContextMenu>
332
+ * );
333
+ * }
334
+ * @see {@link ContextMenuRadioGroup}
335
+ * @since 1.0.0
336
+ */
337
+ function ContextMenuCheckboxItem(props: MenuCheckboxItemProps): JSX.Element {
338
+ const [local, rest] = splitProps(props, ["class", "children"]);
339
+ return (
340
+ <ContextMenuPrimitive.CheckboxItem
341
+ data-slot="context-menu-checkbox-item"
342
+ class={cn(ROW, "py-1.5 ps-8 pe-2", local.class)}
343
+ {...rest}
344
+ >
345
+ <span class="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center">
346
+ <ContextMenuPrimitive.ItemIndicator>
347
+ <CheckIcon class="size-4" />
348
+ </ContextMenuPrimitive.ItemIndicator>
349
+ </span>
350
+ {local.children}
351
+ </ContextMenuPrimitive.CheckboxItem>
352
+ );
353
+ }
354
+
355
+ /**
356
+ * A group of mutually exclusive {@link ContextMenuRadioItem}s. It is a `role="group"` and handles no
357
+ * keys itself; the items are navigated like any other menu item.
358
+ *
359
+ * @param props The group props: `value` (the selected item's value), `onValueChange`, and the
360
+ * attributes of the rendered `div`.
361
+ * @returns The group.
362
+ * @throws Error when rendered outside a {@link ContextMenu}.
363
+ * @example
364
+ * import { useSignal } from "@shadow-js/core";
365
+ * import { ContextMenu, ContextMenuContent, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuTrigger } from "@/components/ui/context-menu";
366
+ *
367
+ * export function Position() {
368
+ * const [position, setPosition] = useSignal("bottom");
369
+ * return (
370
+ * <ContextMenu>
371
+ * <ContextMenuTrigger>Right click here</ContextMenuTrigger>
372
+ * <ContextMenuContent>
373
+ * <ContextMenuRadioGroup value={position()} onValueChange={setPosition}>
374
+ * <ContextMenuRadioItem value="top">Top</ContextMenuRadioItem>
375
+ * <ContextMenuRadioItem value="bottom">Bottom</ContextMenuRadioItem>
376
+ * </ContextMenuRadioGroup>
377
+ * </ContextMenuContent>
378
+ * </ContextMenu>
379
+ * );
380
+ * }
381
+ * @see {@link ContextMenuRadioItem}
382
+ * @since 1.0.0
383
+ */
384
+ function ContextMenuRadioGroup(props: MenuRadioGroupProps): JSX.Element {
385
+ return <ContextMenuPrimitive.RadioGroup data-slot="context-menu-radio-group" {...props} />;
386
+ }
387
+
388
+ /**
389
+ * An item of a {@link ContextMenuRadioGroup}, with a dot while it is the selected one. It is a
390
+ * `role="menuitemradio"` with `aria-checked`. Enter, Space and a click select it, even when `onSelect`
391
+ * keeps the menu open.
392
+ *
393
+ * @param props The item props: `value` and the attributes of the item.
394
+ * @returns The radio item.
395
+ * @throws Error when rendered outside a {@link ContextMenuRadioGroup}.
396
+ * @example
397
+ * import { ContextMenu, ContextMenuContent, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuTrigger } from "@/components/ui/context-menu";
398
+ *
399
+ * export function Density() {
400
+ * return (
401
+ * <ContextMenu>
402
+ * <ContextMenuTrigger>Right click here</ContextMenuTrigger>
403
+ * <ContextMenuContent>
404
+ * <ContextMenuRadioGroup value="compact">
405
+ * <ContextMenuRadioItem value="compact">Compact</ContextMenuRadioItem>
406
+ * </ContextMenuRadioGroup>
407
+ * </ContextMenuContent>
408
+ * </ContextMenu>
409
+ * );
410
+ * }
411
+ * @see {@link ContextMenuRadioGroup}
412
+ * @since 1.0.0
413
+ */
414
+ function ContextMenuRadioItem(props: MenuRadioItemProps): JSX.Element {
415
+ const [local, rest] = splitProps(props, ["class", "children"]);
416
+ return (
417
+ <ContextMenuPrimitive.RadioItem
418
+ data-slot="context-menu-radio-item"
419
+ class={cn(ROW, "py-1.5 ps-8 pe-2", local.class)}
420
+ {...rest}
421
+ >
422
+ <span class="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center">
423
+ <ContextMenuPrimitive.ItemIndicator>
424
+ <CircleIcon class="size-2 fill-current" />
425
+ </ContextMenuPrimitive.ItemIndicator>
426
+ </span>
427
+ {local.children}
428
+ </ContextMenuPrimitive.RadioItem>
429
+ );
430
+ }
431
+
432
+ /**
433
+ * Props of {@link ContextMenuLabel}: the attributes of the rendered `div` and `inset`, which indents the
434
+ * heading to line up with indented items.
435
+ * @see {@link ContextMenuLabel}
436
+ * @since 1.0.0
437
+ */
438
+ export type ContextMenuLabelProps = MenuLabelProps & {
439
+ /**
440
+ * Indents the heading to line up with items that have an indicator.
441
+ * @default false
442
+ */
443
+ readonly inset?: boolean | undefined;
444
+ };
445
+
446
+ /**
447
+ * A heading for a group of items. It has no role, is not focusable, and the arrow keys skip it.
448
+ *
449
+ * @param props The heading props: see {@link ContextMenuLabelProps}.
450
+ * @returns The heading.
451
+ * @throws Error when rendered outside a {@link ContextMenu}.
452
+ * @example
453
+ * import { ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuTrigger } from "@/components/ui/context-menu";
454
+ *
455
+ * export function AccountMenu() {
456
+ * return (
457
+ * <ContextMenu>
458
+ * <ContextMenuTrigger>Right click here</ContextMenuTrigger>
459
+ * <ContextMenuContent>
460
+ * <ContextMenuLabel>My Account</ContextMenuLabel>
461
+ * <ContextMenuGroup>
462
+ * <ContextMenuItem>Profile</ContextMenuItem>
463
+ * </ContextMenuGroup>
464
+ * </ContextMenuContent>
465
+ * </ContextMenu>
466
+ * );
467
+ * }
468
+ * @see {@link ContextMenuGroup}
469
+ * @since 1.0.0
470
+ */
471
+ function ContextMenuLabel(props: ContextMenuLabelProps): JSX.Element {
472
+ const [local, rest] = splitProps(props, ["class", "inset"]);
473
+ return (
474
+ <ContextMenuPrimitive.Label
475
+ data-slot="context-menu-label"
476
+ data-inset={local.inset === true ? "" : undefined}
477
+ class={cn("px-2 py-1.5 text-sm font-medium data-[inset]:ps-8", local.class)}
478
+ {...rest}
479
+ />
480
+ );
481
+ }
482
+
483
+ /**
484
+ * A divider between groups of items. It is a `role="separator"` with a horizontal orientation.
485
+ *
486
+ * @param props The separator props: the attributes of the rendered `div`.
487
+ * @returns The separator.
488
+ * @throws Error when rendered outside a {@link ContextMenu}.
489
+ * @example
490
+ * import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "@/components/ui/context-menu";
491
+ *
492
+ * export function Divided() {
493
+ * return (
494
+ * <ContextMenu>
495
+ * <ContextMenuTrigger>Right click here</ContextMenuTrigger>
496
+ * <ContextMenuContent>
497
+ * <ContextMenuItem>Back</ContextMenuItem>
498
+ * <ContextMenuSeparator />
499
+ * <ContextMenuItem>Delete</ContextMenuItem>
500
+ * </ContextMenuContent>
501
+ * </ContextMenu>
502
+ * );
503
+ * }
504
+ * @see {@link ContextMenuGroup}
505
+ * @since 1.0.0
506
+ */
507
+ function ContextMenuSeparator(props: MenuSeparatorProps): JSX.Element {
508
+ const [local, rest] = splitProps(props, ["class"]);
509
+ return (
510
+ <ContextMenuPrimitive.Separator
511
+ data-slot="context-menu-separator"
512
+ class={cn("-mx-1 my-1 h-px bg-border", local.class)}
513
+ {...rest}
514
+ />
515
+ );
516
+ }
517
+
518
+ /**
519
+ * A keyboard shortcut hint at the end of an item. It is decorative: it does not add a key binding.
520
+ *
521
+ * @param props The hint props: the attributes of the rendered `span`, and `children` (the shortcut text).
522
+ * @returns The hint.
523
+ * @example
524
+ * import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuShortcut, ContextMenuTrigger } from "@/components/ui/context-menu";
525
+ *
526
+ * export function Editing() {
527
+ * return (
528
+ * <ContextMenu>
529
+ * <ContextMenuTrigger>Right click here</ContextMenuTrigger>
530
+ * <ContextMenuContent>
531
+ * <ContextMenuItem>
532
+ * Save
533
+ * <ContextMenuShortcut>Ctrl+S</ContextMenuShortcut>
534
+ * </ContextMenuItem>
535
+ * </ContextMenuContent>
536
+ * </ContextMenu>
537
+ * );
538
+ * }
539
+ * @see {@link ContextMenuItem}
540
+ * @since 1.0.0
541
+ */
542
+ function ContextMenuShortcut(props: ComponentProps<"span">): JSX.Element {
543
+ const [local, rest] = splitProps(props, ["class"]);
544
+ return (
545
+ <span
546
+ data-slot="context-menu-shortcut"
547
+ class={cn("ms-auto text-xs tracking-widest text-muted-foreground", local.class)}
548
+ {...rest}
549
+ />
550
+ );
551
+ }
552
+
553
+ /**
554
+ * The root of a submenu. It renders no element; place a {@link ContextMenuSubTrigger} and a
555
+ * {@link ContextMenuSubContent} in it, inside a {@link ContextMenuContent}. The submenu closes when its
556
+ * menu does.
557
+ *
558
+ * @param props The submenu props: `open`, `defaultOpen` and `onOpenChange` of the submenu.
559
+ * @returns The submenu's context around its children.
560
+ * @throws Error when rendered outside a {@link ContextMenu}.
561
+ * @example
562
+ * import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger } from "@/components/ui/context-menu";
563
+ *
564
+ * export function Share() {
565
+ * return (
566
+ * <ContextMenu>
567
+ * <ContextMenuTrigger>Right click here</ContextMenuTrigger>
568
+ * <ContextMenuContent>
569
+ * <ContextMenuSub>
570
+ * <ContextMenuSubTrigger>Invite users</ContextMenuSubTrigger>
571
+ * <ContextMenuSubContent>
572
+ * <ContextMenuItem>Email</ContextMenuItem>
573
+ * </ContextMenuSubContent>
574
+ * </ContextMenuSub>
575
+ * </ContextMenuContent>
576
+ * </ContextMenu>
577
+ * );
578
+ * }
579
+ * @see {@link ContextMenuSubTrigger}
580
+ * @see {@link ContextMenuSubContent}
581
+ * @since 1.0.0
582
+ */
583
+ function ContextMenuSub(props: MenuSubProps): JSX.Element {
584
+ return <ContextMenuPrimitive.Sub {...props} />;
585
+ }
586
+
587
+ /**
588
+ * The item that opens a submenu, with a chevron that points the other way in right-to-left layouts.
589
+ * It is a `role="menuitem"` with `aria-haspopup="menu"` and `aria-expanded`. ArrowRight opens the
590
+ * submenu (ArrowLeft in right-to-left layouts), and the item has `data-state="open"` or
591
+ * `data-state="closed"`.
592
+ *
593
+ * @param props The trigger props: the attributes of the rendered `div`, `inset`, and `children`.
594
+ * @returns The submenu trigger.
595
+ * @throws Error when rendered outside a {@link ContextMenuSub}.
596
+ * @example
597
+ * import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger } from "@/components/ui/context-menu";
598
+ *
599
+ * export function Invite() {
600
+ * return (
601
+ * <ContextMenu>
602
+ * <ContextMenuTrigger>Right click here</ContextMenuTrigger>
603
+ * <ContextMenuContent>
604
+ * <ContextMenuSub>
605
+ * <ContextMenuSubTrigger inset>Invite users</ContextMenuSubTrigger>
606
+ * <ContextMenuSubContent>
607
+ * <ContextMenuItem>Email</ContextMenuItem>
608
+ * </ContextMenuSubContent>
609
+ * </ContextMenuSub>
610
+ * </ContextMenuContent>
611
+ * </ContextMenu>
612
+ * );
613
+ * }
614
+ * @see {@link ContextMenuSub}
615
+ * @since 1.0.0
616
+ */
617
+ function ContextMenuSubTrigger<T extends PolymorphicTarget = "div">(
618
+ props: MenuSubTriggerProps<T> & { readonly inset?: boolean | undefined },
619
+ ): JSX.Element {
620
+ const [local, rest] = splitProps(props as unknown as Loose, ["class", "inset", "children"]);
621
+ return (
622
+ <ContextMenuPrimitive.SubTrigger
623
+ data-slot="context-menu-sub-trigger"
624
+ data-inset={local.inset === true ? "" : undefined}
625
+ class={cn(
626
+ ROW,
627
+ "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",
628
+ local.class,
629
+ )}
630
+ {...rest}
631
+ >
632
+ {local.children}
633
+ <ChevronRightIcon class="ms-auto size-4 rtl:rotate-180" />
634
+ </ContextMenuPrimitive.SubTrigger>
635
+ );
636
+ }
637
+
638
+ /**
639
+ * The submenu panel, placed beside its trigger. It is a `role="menu"` labeled by its trigger.
640
+ * ArrowLeft (ArrowRight in right-to-left layouts) and Escape close it and return focus to the trigger.
641
+ *
642
+ * @param props The panel props: the attributes of the rendered `div` and `class`.
643
+ * @returns The submenu panel.
644
+ * @throws Error when rendered outside a {@link ContextMenuSub}.
645
+ * @example
646
+ * import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger } from "@/components/ui/context-menu";
647
+ *
648
+ * export function Share() {
649
+ * return (
650
+ * <ContextMenu>
651
+ * <ContextMenuTrigger>Right click here</ContextMenuTrigger>
652
+ * <ContextMenuContent>
653
+ * <ContextMenuSub>
654
+ * <ContextMenuSubTrigger>Invite users</ContextMenuSubTrigger>
655
+ * <ContextMenuSubContent class="w-44">
656
+ * <ContextMenuItem>Email</ContextMenuItem>
657
+ * </ContextMenuSubContent>
658
+ * </ContextMenuSub>
659
+ * </ContextMenuContent>
660
+ * </ContextMenu>
661
+ * );
662
+ * }
663
+ * @see {@link ContextMenuSubTrigger}
664
+ * @since 1.0.0
665
+ */
666
+ function ContextMenuSubContent(props: MenuSubContentProps): JSX.Element {
667
+ const [local, rest] = splitProps(props, ["class"]);
668
+ return (
669
+ <ContextMenuPrimitive.SubContent
670
+ data-slot="context-menu-sub-content"
671
+ class={cn(
672
+ PANEL,
673
+ "max-h-(--primitives-available-height) overflow-x-hidden overflow-y-auto shadow-lg",
674
+ local.class,
675
+ )}
676
+ {...rest}
677
+ />
678
+ );
679
+ }
680
+
681
+ export {
682
+ ContextMenu,
683
+ ContextMenuCheckboxItem,
684
+ ContextMenuContent,
685
+ ContextMenuGroup,
686
+ ContextMenuItem,
687
+ ContextMenuLabel,
688
+ ContextMenuPortal,
689
+ ContextMenuRadioGroup,
690
+ ContextMenuRadioItem,
691
+ ContextMenuSeparator,
692
+ ContextMenuShortcut,
693
+ ContextMenuSub,
694
+ ContextMenuSubContent,
695
+ ContextMenuSubTrigger,
696
+ ContextMenuTrigger,
697
+ };