@shadow-js/ui 0.0.0-stage → 1.0.1

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 +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +598 -2
  4. package/dist/cli.mjs +2281 -0
  5. package/dist/dev/cli/schema.d.ts +71 -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 +139 -0
  9. package/dist/dev/locales/ar.js +127 -0
  10. package/dist/dev/locales/ar.js.map +1 -0
  11. package/dist/dev/locales/en.d.ts +139 -0
  12. package/dist/dev/locales/en.js +127 -0
  13. package/dist/dev/locales/en.js.map +1 -0
  14. package/dist/dev/registry/utils/utils.d.ts +27 -0
  15. package/dist/dev/registry/utils/utils.js +26 -0
  16. package/dist/dev/registry/utils/utils.js.map +1 -0
  17. package/dist/dev/theme/theme-logic.d.ts +185 -0
  18. package/dist/dev/theme/theme-logic.js +183 -0
  19. package/dist/dev/theme/theme-logic.js.map +1 -0
  20. package/dist/dev/theme/theme-provider.d.ts +88 -0
  21. package/dist/dev/theme/theme-provider.js +149 -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,354 @@
1
+ import { splitProps } from "@shadow-js/core";
2
+ import { Polymorphic, type PolymorphicProps, type PolymorphicTarget } from "@shadow-js/primitives";
3
+ import type { ComponentProps, JSX } from "@shadow-js/web";
4
+ import { ChevronRightIcon, EllipsisIcon } from "@/components/ui/icons";
5
+ import { useUiStrings } from "@/lib/ui-strings";
6
+ import { type ClassValue, cn } from "@/lib/utils";
7
+
8
+ /**
9
+ * A trail of links that shows where the current page sits in the site. It renders a `nav` with
10
+ * the accessible name "breadcrumb" and holds a {@link BreadcrumbList}.
11
+ *
12
+ * Keyboard and ARIA: the links are native anchors, so Tab moves between them and Enter follows
13
+ * one. The current page is marked with `aria-current="page"` (see {@link BreadcrumbPage}). The
14
+ * separators are hidden from assistive technology.
15
+ *
16
+ * Compared with shadcn/ui: `class` replaces `className`, `as` replaces `asChild` on
17
+ * {@link BreadcrumbLink}, and every element carries `data-slot`.
18
+ *
19
+ * @param props - The navigation props: `children` and the `nav` attributes.
20
+ * @returns The navigation element.
21
+ * @example
22
+ * ```tsx
23
+ * import {
24
+ * Breadcrumb,
25
+ * BreadcrumbItem,
26
+ * BreadcrumbLink,
27
+ * BreadcrumbList,
28
+ * BreadcrumbPage,
29
+ * BreadcrumbSeparator,
30
+ * } from "@/components/ui/breadcrumb";
31
+ *
32
+ * export function Trail() {
33
+ * return (
34
+ * <Breadcrumb>
35
+ * <BreadcrumbList>
36
+ * <BreadcrumbItem>
37
+ * <BreadcrumbLink href="/">Home</BreadcrumbLink>
38
+ * </BreadcrumbItem>
39
+ * <BreadcrumbSeparator />
40
+ * <BreadcrumbItem>
41
+ * <BreadcrumbPage>Components</BreadcrumbPage>
42
+ * </BreadcrumbItem>
43
+ * </BreadcrumbList>
44
+ * </Breadcrumb>
45
+ * );
46
+ * }
47
+ * ```
48
+ * @since 1.0.0
49
+ * @see {@link BreadcrumbList}
50
+ * @see {@link BreadcrumbLink}
51
+ */
52
+ function Breadcrumb(props: ComponentProps<"nav">): JSX.Element {
53
+ const [local, rest] = splitProps(props, ["children"]);
54
+ const t = useUiStrings();
55
+ return (
56
+ <nav aria-label={t("breadcrumb.label", "breadcrumb")} data-slot="breadcrumb" {...rest}>
57
+ {local.children}
58
+ </nav>
59
+ );
60
+ }
61
+
62
+ /**
63
+ * The ordered list of the trail. Each step is a {@link BreadcrumbItem}, and the steps are read in
64
+ * order.
65
+ *
66
+ * @param props - The list props: `class`, `children`, and the `ol` attributes.
67
+ * @returns The list element.
68
+ * @example
69
+ * ```tsx
70
+ * import {
71
+ * Breadcrumb,
72
+ * BreadcrumbItem,
73
+ * BreadcrumbLink,
74
+ * BreadcrumbList,
75
+ * } from "@/components/ui/breadcrumb";
76
+ *
77
+ * export function Steps() {
78
+ * return (
79
+ * <Breadcrumb>
80
+ * <BreadcrumbList>
81
+ * <BreadcrumbItem>
82
+ * <BreadcrumbLink href="/docs">Docs</BreadcrumbLink>
83
+ * </BreadcrumbItem>
84
+ * </BreadcrumbList>
85
+ * </Breadcrumb>
86
+ * );
87
+ * }
88
+ * ```
89
+ * @since 1.0.0
90
+ * @see {@link Breadcrumb}
91
+ * @see {@link BreadcrumbItem}
92
+ */
93
+ function BreadcrumbList(props: ComponentProps<"ol">): JSX.Element {
94
+ const [local, rest] = splitProps(props, ["class", "children"]);
95
+ return (
96
+ <ol
97
+ data-slot="breadcrumb-list"
98
+ class={cn(
99
+ "flex flex-wrap items-center gap-1.5 text-sm wrap-break-word text-muted-foreground sm:gap-2.5",
100
+ local.class,
101
+ )}
102
+ {...rest}
103
+ >
104
+ {local.children}
105
+ </ol>
106
+ );
107
+ }
108
+
109
+ /**
110
+ * One step of the trail: a link, the current page or an ellipsis. It renders an `li` inside a
111
+ * {@link BreadcrumbList}.
112
+ *
113
+ * @param props - The item props: `class`, `children`, and the `li` attributes.
114
+ * @returns The item element.
115
+ * @example
116
+ * ```tsx
117
+ * import { BreadcrumbItem, BreadcrumbLink, BreadcrumbList } from "@/components/ui/breadcrumb";
118
+ *
119
+ * export function DocsStep() {
120
+ * return (
121
+ * <BreadcrumbList>
122
+ * <BreadcrumbItem>
123
+ * <BreadcrumbLink href="/docs">Docs</BreadcrumbLink>
124
+ * </BreadcrumbItem>
125
+ * </BreadcrumbList>
126
+ * );
127
+ * }
128
+ * ```
129
+ * @since 1.0.0
130
+ * @see {@link BreadcrumbList}
131
+ */
132
+ function BreadcrumbItem(props: ComponentProps<"li">): JSX.Element {
133
+ const [local, rest] = splitProps(props, ["class", "children"]);
134
+ return (
135
+ <li
136
+ data-slot="breadcrumb-item"
137
+ class={cn("inline-flex items-center gap-1.5", local.class)}
138
+ {...rest}
139
+ >
140
+ {local.children}
141
+ </li>
142
+ );
143
+ }
144
+
145
+ /** The props as the implementation sees them. */
146
+ type Loose = {
147
+ readonly as?: PolymorphicTarget;
148
+ readonly class?: ClassValue;
149
+ readonly [prop: string]: unknown;
150
+ };
151
+
152
+ /**
153
+ * Props of {@link BreadcrumbLink}: every attribute of the chosen element. The element is an `a`
154
+ * by default; the `as` prop picks another one.
155
+ * @since 1.0.0
156
+ * @see {@link BreadcrumbLink}
157
+ */
158
+ export type BreadcrumbLinkProps<T extends PolymorphicTarget = "a"> = PolymorphicProps<
159
+ "a",
160
+ object,
161
+ T
162
+ >;
163
+
164
+ /**
165
+ * A link to an earlier step of the trail. It renders an `a` with `href`. Use `as` to render your
166
+ * router's link component instead, which keeps the same styles.
167
+ *
168
+ * Keyboard and ARIA: a native link. Tab focuses it and Enter follows it. The link's accessible name
169
+ * is its text.
170
+ *
171
+ * @param props - The link props: `href`, `as`, `children`, and the attributes of the element.
172
+ * @returns The link element.
173
+ * @example
174
+ * ```tsx
175
+ * import { BreadcrumbItem, BreadcrumbLink } from "@/components/ui/breadcrumb";
176
+ *
177
+ * export function HomeLink() {
178
+ * return (
179
+ * <BreadcrumbItem>
180
+ * <BreadcrumbLink href="/">Home</BreadcrumbLink>
181
+ * </BreadcrumbItem>
182
+ * );
183
+ * }
184
+ * ```
185
+ * @since 1.0.0
186
+ * @see {@link BreadcrumbLinkProps}
187
+ * @see {@link BreadcrumbPage}
188
+ */
189
+ function BreadcrumbLink<T extends PolymorphicTarget = "a">(
190
+ props: BreadcrumbLinkProps<T>,
191
+ ): JSX.Element {
192
+ const [local, rest] = splitProps(props as unknown as Loose, ["as", "class"]);
193
+ return (
194
+ <Polymorphic
195
+ as={local.as ?? "a"}
196
+ data-slot="breadcrumb-link"
197
+ class={cn(
198
+ "transition-colors hover:text-foreground motion-reduce:transition-none",
199
+ local.class,
200
+ )}
201
+ {...rest}
202
+ />
203
+ );
204
+ }
205
+
206
+ /**
207
+ * The current page: the last step of the trail. It is not a link.
208
+ *
209
+ * Keyboard and ARIA: the element is a `span` with `aria-current="page"`, `role="link"` and
210
+ * `aria-disabled="true"`, so assistive technology announces a disabled link that is the current
211
+ * page. It has no `href` and no `tabindex`, so it is not focusable.
212
+ *
213
+ * @param props - The page props: `children` and the `span` attributes.
214
+ * @returns The page element.
215
+ * @example
216
+ * ```tsx
217
+ * import { BreadcrumbItem, BreadcrumbPage } from "@/components/ui/breadcrumb";
218
+ *
219
+ * export function CurrentPage() {
220
+ * return (
221
+ * <BreadcrumbItem>
222
+ * <BreadcrumbPage>Breadcrumb</BreadcrumbPage>
223
+ * </BreadcrumbItem>
224
+ * );
225
+ * }
226
+ * ```
227
+ * @since 1.0.0
228
+ * @see {@link BreadcrumbLink}
229
+ */
230
+ function BreadcrumbPage(props: ComponentProps<"span">): JSX.Element {
231
+ const [local, rest] = splitProps(props, ["class", "children"]);
232
+ return (
233
+ <span
234
+ data-slot="breadcrumb-page"
235
+ role="link"
236
+ aria-disabled="true"
237
+ aria-current="page"
238
+ class={cn("font-normal text-foreground", local.class)}
239
+ {...rest}
240
+ >
241
+ {local.children}
242
+ </span>
243
+ );
244
+ }
245
+
246
+ /**
247
+ * The divider between two steps. By default it is a chevron that flips in right-to-left layouts;
248
+ * pass children to show your own divider, such as `/`.
249
+ *
250
+ * Keyboard and ARIA: the separator is `role="presentation"` and `aria-hidden="true"`, so assistive
251
+ * technology skips it.
252
+ *
253
+ * @param props - The separator props: `children` and the `li` attributes.
254
+ * @returns The separator element.
255
+ * @example
256
+ * ```tsx
257
+ * import {
258
+ * Breadcrumb,
259
+ * BreadcrumbItem,
260
+ * BreadcrumbLink,
261
+ * BreadcrumbList,
262
+ * BreadcrumbPage,
263
+ * BreadcrumbSeparator,
264
+ * } from "@/components/ui/breadcrumb";
265
+ *
266
+ * export function SlashTrail() {
267
+ * return (
268
+ * <Breadcrumb>
269
+ * <BreadcrumbList>
270
+ * <BreadcrumbItem>
271
+ * <BreadcrumbLink href="/">Home</BreadcrumbLink>
272
+ * </BreadcrumbItem>
273
+ * <BreadcrumbSeparator>/</BreadcrumbSeparator>
274
+ * <BreadcrumbItem>
275
+ * <BreadcrumbPage>Docs</BreadcrumbPage>
276
+ * </BreadcrumbItem>
277
+ * </BreadcrumbList>
278
+ * </Breadcrumb>
279
+ * );
280
+ * }
281
+ * ```
282
+ * @since 1.0.0
283
+ * @see {@link BreadcrumbList}
284
+ */
285
+ function BreadcrumbSeparator(props: ComponentProps<"li">): JSX.Element {
286
+ const [local, rest] = splitProps(props, ["class", "children"]);
287
+ return (
288
+ <li
289
+ data-slot="breadcrumb-separator"
290
+ role="presentation"
291
+ aria-hidden="true"
292
+ class={cn("[&>svg]:size-3.5", local.class)}
293
+ {...rest}
294
+ >
295
+ {local.children ?? <ChevronRightIcon class="rtl:rotate-180" />}
296
+ </li>
297
+ );
298
+ }
299
+
300
+ /**
301
+ * Stands for collapsed steps of a long trail. Put a menu trigger around it to reveal the hidden
302
+ * steps. It is an item of the list, so it goes inside a {@link BreadcrumbItem}.
303
+ *
304
+ * Keyboard and ARIA: the ellipsis is `role="presentation"` and `aria-hidden="true"`, so assistive
305
+ * technology does not announce the collapsed steps. It has no keys of its own.
306
+ *
307
+ * @param props - The ellipsis props: the `span` attributes.
308
+ * @returns The ellipsis element.
309
+ * @example
310
+ * ```tsx
311
+ * import {
312
+ * BreadcrumbEllipsis,
313
+ * BreadcrumbItem,
314
+ * BreadcrumbList,
315
+ * } from "@/components/ui/breadcrumb";
316
+ *
317
+ * export function Collapsed() {
318
+ * return (
319
+ * <BreadcrumbList>
320
+ * <BreadcrumbItem>
321
+ * <BreadcrumbEllipsis />
322
+ * </BreadcrumbItem>
323
+ * </BreadcrumbList>
324
+ * );
325
+ * }
326
+ * ```
327
+ * @since 1.0.0
328
+ * @see {@link BreadcrumbItem}
329
+ */
330
+ function BreadcrumbEllipsis(props: ComponentProps<"span">): JSX.Element {
331
+ const [local, rest] = splitProps(props, ["class"]);
332
+ const t = useUiStrings();
333
+ return (
334
+ <span
335
+ data-slot="breadcrumb-ellipsis"
336
+ role="presentation"
337
+ class={cn("flex size-9 items-center justify-center", local.class)}
338
+ {...rest}
339
+ >
340
+ <EllipsisIcon class="size-4" aria-hidden="true" />
341
+ <span class="sr-only">{t("breadcrumb.more", "More")}</span>
342
+ </span>
343
+ );
344
+ }
345
+
346
+ export {
347
+ Breadcrumb,
348
+ BreadcrumbEllipsis,
349
+ BreadcrumbItem,
350
+ BreadcrumbLink,
351
+ BreadcrumbList,
352
+ BreadcrumbPage,
353
+ BreadcrumbSeparator,
354
+ };
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "Breadcrumb",
3
+ "description": "Displays the path to the current resource using a hierarchy of links."
4
+ }
@@ -0,0 +1,260 @@
1
+ import { splitProps } from "@shadow-js/core";
2
+ import { Polymorphic, type PolymorphicProps, type PolymorphicTarget } from "@shadow-js/primitives";
3
+ import type { ComponentProps, JSX } from "@shadow-js/web";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+ import { type ClassValue, cn } from "@/lib/utils";
6
+
7
+ /**
8
+ * The class list of a bubble's frame for each `variant` and `align`. The look of the text surface is on
9
+ * {@link BubbleContent}, which reads the variant from this element; this list is the box around it.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * import { bubbleVariants } from "@/components/ui/bubble";
14
+ *
15
+ * export function Frame() {
16
+ * return <div class={bubbleVariants({ align: "end" })}>Custom row</div>;
17
+ * }
18
+ * ```
19
+ * @since 1.0.0
20
+ * @see {@link Bubble}
21
+ */
22
+ const bubbleVariants = cva("group/bubble relative flex min-w-0 flex-col", {
23
+ variants: {
24
+ variant: {
25
+ default: "",
26
+ secondary: "",
27
+ muted: "",
28
+ tinted: "",
29
+ outline: "",
30
+ ghost: "w-full max-w-full",
31
+ destructive: "",
32
+ },
33
+ align: {
34
+ start: "me-auto items-start",
35
+ end: "ms-auto items-end",
36
+ },
37
+ },
38
+ compoundVariants: [
39
+ {
40
+ variant: ["default", "secondary", "muted", "tinted", "outline", "destructive"],
41
+ class: "w-fit max-w-[85%]",
42
+ },
43
+ ],
44
+ defaultVariants: { variant: "default", align: "start" },
45
+ });
46
+
47
+ /**
48
+ * Props of {@link Bubble}: the attributes of a `div` plus `variant` (`"default"`, `"secondary"`,
49
+ * `"muted"`, `"tinted"`, `"outline"`, `"ghost"` or `"destructive"`) and `align` (`"start"` or `"end"`).
50
+ * @since 1.0.0
51
+ * @see {@link Bubble}
52
+ */
53
+ export type BubbleProps = ComponentProps<"div"> & VariantProps<typeof bubbleVariants>;
54
+
55
+ /**
56
+ * A framed message in a conversation: a {@link BubbleContent} with an optional {@link BubbleReactions}.
57
+ * `variant` picks the surface and `align` the side. A `ghost` bubble has no frame and uses the full width,
58
+ * for a reply that reads as a document. In a chat, set `align` on a {@link Message} instead, which aligns the bubbles inside it.
59
+ *
60
+ * Keyboard and ARIA: a bubble has no role and no keys. Color is never the only signal: tell sender and meaning
61
+ * apart by alignment, a name or an icon too.
62
+ *
63
+ * Compared with shadcn/ui: `class` replaces `className`, and every part has a `data-slot`.
64
+ *
65
+ * @param props - The bubble props: `variant`, `align`, `children` and the `div` attributes.
66
+ * @returns The bubble.
67
+ * @example
68
+ * ```tsx
69
+ * import { Bubble, BubbleContent } from "@/components/ui/bubble";
70
+ *
71
+ * export function Question() {
72
+ * return (
73
+ * <Bubble align="end" variant="muted">
74
+ * <BubbleContent>How do I add a dialog?</BubbleContent>
75
+ * </Bubble>
76
+ * );
77
+ * }
78
+ * ```
79
+ * @since 1.0.0
80
+ * @see {@link BubbleContent}
81
+ * @see {@link BubbleReactions}
82
+ * @see {@link BubbleGroup}
83
+ */
84
+ function Bubble(props: BubbleProps): JSX.Element {
85
+ const [local, rest] = splitProps(props, ["class", "variant", "align"]);
86
+ return (
87
+ <div
88
+ data-slot="bubble"
89
+ data-variant={local.variant ?? "default"}
90
+ data-align={local.align ?? "start"}
91
+ class={cn(bubbleVariants({ variant: local.variant, align: local.align }), local.class)}
92
+ {...rest}
93
+ />
94
+ );
95
+ }
96
+
97
+ /** The props as the implementation sees them. */
98
+ type Loose = {
99
+ readonly as?: PolymorphicTarget;
100
+ readonly class?: ClassValue;
101
+ readonly [prop: string]: unknown;
102
+ };
103
+
104
+ /**
105
+ * Props of {@link BubbleContent}: every attribute of the chosen element. The `as` prop picks the element
106
+ * (a `div` by default); pass `"a"` or `"button"` to make the bubble a link or a button.
107
+ * @since 1.0.0
108
+ * @see {@link BubbleContent}
109
+ */
110
+ export type BubbleContentProps<T extends PolymorphicTarget = "div"> = PolymorphicProps<
111
+ "div",
112
+ object,
113
+ T
114
+ >;
115
+
116
+ /**
117
+ * The text surface of a {@link Bubble}: the rounded, colored box that holds the message. It takes its
118
+ * colors from the bubble's `variant`. Render it as a link or a button with `as` for a bubble that does something.
119
+ *
120
+ * Keyboard and ARIA: no role of its own. A clickable bubble must be a real `button` or `a` (with `as`), so it takes
121
+ * focus and the native keys; a `div` with a click handler is not reachable by keyboard.
122
+ *
123
+ * @param props - The content props: `as`, `children` and the attributes of the element.
124
+ * @returns The content element.
125
+ * @example
126
+ * ```tsx
127
+ * import { Bubble, BubbleContent } from "@/components/ui/bubble";
128
+ *
129
+ * export function Suggestion() {
130
+ * return (
131
+ * <Bubble variant="outline">
132
+ * <BubbleContent as="button" type="button">
133
+ * Summarize this page
134
+ * </BubbleContent>
135
+ * </Bubble>
136
+ * );
137
+ * }
138
+ * ```
139
+ * @since 1.0.0
140
+ * @see {@link Bubble}
141
+ */
142
+ function BubbleContent<T extends PolymorphicTarget = "div">(
143
+ props: BubbleContentProps<T>,
144
+ ): JSX.Element {
145
+ const [local, rest] = splitProps(props as unknown as Loose, ["as", "class"]);
146
+ return (
147
+ <Polymorphic
148
+ as={local.as ?? "div"}
149
+ data-slot="bubble-content"
150
+ class={cn(
151
+ "min-w-0 rounded-2xl px-3.5 py-2 text-start text-sm leading-relaxed text-pretty break-words outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 [a&]:transition-colors [button&]:transition-colors motion-reduce:transition-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-transparent",
152
+ "group-data-[variant=default]/bubble:bg-primary group-data-[variant=default]/bubble:text-primary-foreground [a&]:group-data-[variant=default]/bubble:hover:bg-primary/90 [button&]:group-data-[variant=default]/bubble:hover:bg-primary/90",
153
+ "group-data-[variant=secondary]/bubble:bg-secondary group-data-[variant=secondary]/bubble:text-secondary-foreground",
154
+ "group-data-[variant=muted]/bubble:bg-muted group-data-[variant=muted]/bubble:text-foreground",
155
+ "group-data-[variant=tinted]/bubble:bg-primary/10 group-data-[variant=tinted]/bubble:text-foreground",
156
+ "group-data-[variant=outline]/bubble:border group-data-[variant=outline]/bubble:bg-background group-data-[variant=outline]/bubble:text-foreground [a&]:group-data-[variant=outline]/bubble:hover:bg-accent [button&]:group-data-[variant=outline]/bubble:hover:bg-accent",
157
+ "group-data-[variant=ghost]/bubble:rounded-none group-data-[variant=ghost]/bubble:px-0 group-data-[variant=ghost]/bubble:py-0 group-data-[variant=ghost]/bubble:text-foreground",
158
+ "group-data-[variant=destructive]/bubble:bg-destructive/10 group-data-[variant=destructive]/bubble:text-destructive",
159
+ "forced-colors:border forced-colors:border-[CanvasText]",
160
+ local.class,
161
+ )}
162
+ {...rest}
163
+ />
164
+ );
165
+ }
166
+
167
+ const reactionsVariants = cva(
168
+ "absolute z-10 flex items-center gap-0.5 rounded-full border bg-background px-1.5 py-0.5 text-xs text-foreground shadow-xs [&_svg:not([class*='size-'])]:size-3.5",
169
+ {
170
+ variants: {
171
+ side: { top: "-top-3", bottom: "-bottom-3" },
172
+ align: { start: "start-3", end: "end-3" },
173
+ },
174
+ defaultVariants: { side: "bottom", align: "end" },
175
+ },
176
+ );
177
+
178
+ /**
179
+ * Props of {@link BubbleReactions}: the attributes of a `div` plus `side` (`"top"` or `"bottom"`, the
180
+ * default) and `align` (`"start"` or `"end"`, the default).
181
+ * @since 1.0.0
182
+ * @see {@link BubbleReactions}
183
+ */
184
+ export type BubbleReactionsProps = ComponentProps<"div"> & VariantProps<typeof reactionsVariants>;
185
+
186
+ /**
187
+ * A row of reactions that overlaps an edge of the bubble. Group them with `role="img"` and a descriptive
188
+ * `aria-label` ("2 people liked this") when they are only a summary, or make each one a real button with a
189
+ * label when they can be pressed. The bubble gains room under it for the overlap.
190
+ *
191
+ * Keyboard and ARIA: no role and no keys of its own; the buttons inside keep theirs.
192
+ *
193
+ * @param props - The reactions props: `side`, `align` and the `div` attributes.
194
+ * @returns The reactions row.
195
+ * @example
196
+ * ```tsx
197
+ * import { Bubble, BubbleContent, BubbleReactions } from "@/components/ui/bubble";
198
+ *
199
+ * export function Liked() {
200
+ * return (
201
+ * <Bubble variant="muted">
202
+ * <BubbleContent>Shipped!</BubbleContent>
203
+ * <BubbleReactions role="img" aria-label="2 people liked this">
204
+ * <span aria-hidden="true">👍 2</span>
205
+ * </BubbleReactions>
206
+ * </Bubble>
207
+ * );
208
+ * }
209
+ * ```
210
+ * @since 1.0.0
211
+ * @see {@link Bubble}
212
+ */
213
+ function BubbleReactions(props: BubbleReactionsProps): JSX.Element {
214
+ const [local, rest] = splitProps(props, ["class", "side", "align"]);
215
+ return (
216
+ <div
217
+ data-slot="bubble-reactions"
218
+ data-side={local.side ?? "bottom"}
219
+ data-align={local.align ?? "end"}
220
+ class={cn(reactionsVariants({ side: local.side, align: local.align }), local.class)}
221
+ {...rest}
222
+ />
223
+ );
224
+ }
225
+
226
+ /**
227
+ * Stacks consecutive bubbles from one sender with a small gap between them.
228
+ *
229
+ * Keyboard and ARIA: a plain `div`; it has no role and no keys.
230
+ *
231
+ * @param props - The `div` attributes and `class`.
232
+ * @returns The group.
233
+ * @example
234
+ * ```tsx
235
+ * import { Bubble, BubbleContent, BubbleGroup } from "@/components/ui/bubble";
236
+ *
237
+ * export function Burst() {
238
+ * return (
239
+ * <BubbleGroup>
240
+ * <Bubble variant="muted">
241
+ * <BubbleContent>First</BubbleContent>
242
+ * </Bubble>
243
+ * <Bubble variant="muted">
244
+ * <BubbleContent>Second</BubbleContent>
245
+ * </Bubble>
246
+ * </BubbleGroup>
247
+ * );
248
+ * }
249
+ * ```
250
+ * @since 1.0.0
251
+ * @see {@link Bubble}
252
+ */
253
+ function BubbleGroup(props: ComponentProps<"div">): JSX.Element {
254
+ const [local, rest] = splitProps(props, ["class"]);
255
+ return (
256
+ <div data-slot="bubble-group" class={cn("flex w-full flex-col gap-1", local.class)} {...rest} />
257
+ );
258
+ }
259
+
260
+ export { Bubble, BubbleContent, BubbleGroup, BubbleReactions, bubbleVariants };
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "Bubble",
3
+ "description": "A framed message bubble for chat text, short structured output, quoted replies, suggestions and reactions."
4
+ }