@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.
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +598 -2
- package/dist/cli.mjs +2293 -0
- package/dist/dev/cli/schema.d.ts +73 -0
- package/dist/dev/index.d.ts +6 -0
- package/dist/dev/index.js +5 -0
- package/dist/dev/locales/ar.d.ts +141 -0
- package/dist/dev/locales/ar.js +128 -0
- package/dist/dev/locales/ar.js.map +1 -0
- package/dist/dev/locales/en.d.ts +141 -0
- package/dist/dev/locales/en.js +128 -0
- package/dist/dev/locales/en.js.map +1 -0
- package/dist/dev/registry/utils/utils.d.ts +29 -0
- package/dist/dev/registry/utils/utils.js +27 -0
- package/dist/dev/registry/utils/utils.js.map +1 -0
- package/dist/dev/theme/theme-logic.d.ts +187 -0
- package/dist/dev/theme/theme-logic.js +184 -0
- package/dist/dev/theme/theme-logic.js.map +1 -0
- package/dist/dev/theme/theme-provider.d.ts +90 -0
- package/dist/dev/theme/theme-provider.js +150 -0
- package/dist/dev/theme/theme-provider.js.map +1 -0
- package/dist/prod/index.js +1 -0
- package/dist/prod/locales/ar.js +2 -0
- package/dist/prod/locales/ar.js.map +1 -0
- package/dist/prod/locales/en.js +2 -0
- package/dist/prod/locales/en.js.map +1 -0
- package/dist/prod/registry/utils/utils.js +2 -0
- package/dist/prod/registry/utils/utils.js.map +1 -0
- package/dist/prod/theme/theme-logic.js +2 -0
- package/dist/prod/theme/theme-logic.js.map +1 -0
- package/dist/prod/theme/theme-provider.js +2 -0
- package/dist/prod/theme/theme-provider.js.map +1 -0
- package/package.json +102 -4
- package/registry/accordion/accordion.tsx +210 -0
- package/registry/accordion/item.json +4 -0
- package/registry/alert/alert.tsx +200 -0
- package/registry/alert/item.json +4 -0
- package/registry/alert-dialog/alert-dialog.tsx +530 -0
- package/registry/alert-dialog/item.json +4 -0
- package/registry/aspect-ratio/aspect-ratio.tsx +71 -0
- package/registry/aspect-ratio/item.json +4 -0
- package/registry/attachment/attachment.tsx +459 -0
- package/registry/attachment/item.json +4 -0
- package/registry/avatar/avatar.tsx +385 -0
- package/registry/avatar/item.json +4 -0
- package/registry/badge/badge.tsx +108 -0
- package/registry/badge/item.json +4 -0
- package/registry/breadcrumb/breadcrumb.tsx +354 -0
- package/registry/breadcrumb/item.json +4 -0
- package/registry/bubble/bubble.tsx +260 -0
- package/registry/bubble/item.json +4 -0
- package/registry/button/button.tsx +125 -0
- package/registry/button/item.json +4 -0
- package/registry/button-group/button-group.tsx +189 -0
- package/registry/button-group/item.json +4 -0
- package/registry/calendar/calendar.tsx +348 -0
- package/registry/calendar/item.json +4 -0
- package/registry/card/card.tsx +311 -0
- package/registry/card/item.json +4 -0
- package/registry/carousel/carousel.tsx +851 -0
- package/registry/carousel/item.json +4 -0
- package/registry/chart/chart.tsx +1956 -0
- package/registry/chart/item.json +4 -0
- package/registry/chart-scale/chart-scale.ts +670 -0
- package/registry/chart-scale/item.json +5 -0
- package/registry/checkbox/checkbox.tsx +79 -0
- package/registry/checkbox/item.json +4 -0
- package/registry/code-block/code-block.tsx +470 -0
- package/registry/code-block/item.json +4 -0
- package/registry/collapsible/collapsible.tsx +117 -0
- package/registry/collapsible/item.json +4 -0
- package/registry/combobox/combobox.tsx +977 -0
- package/registry/combobox/item.json +4 -0
- package/registry/command/command.tsx +757 -0
- package/registry/command/item.json +4 -0
- package/registry/context-menu/context-menu.tsx +697 -0
- package/registry/context-menu/item.json +4 -0
- package/registry/data-table/data-table.tsx +437 -0
- package/registry/data-table/item.json +4 -0
- package/registry/date-picker/date-picker.tsx +349 -0
- package/registry/date-picker/item.json +4 -0
- package/registry/dialog/dialog.tsx +412 -0
- package/registry/dialog/item.json +4 -0
- package/registry/direction/direction.tsx +88 -0
- package/registry/direction/item.json +4 -0
- package/registry/drawer/drawer.tsx +787 -0
- package/registry/drawer/item.json +4 -0
- package/registry/dropdown-menu/dropdown-menu.tsx +692 -0
- package/registry/dropdown-menu/item.json +4 -0
- package/registry/empty/empty.tsx +240 -0
- package/registry/empty/item.json +4 -0
- package/registry/field/field.tsx +624 -0
- package/registry/field/item.json +4 -0
- package/registry/form/form.tsx +1112 -0
- package/registry/form/item.json +4 -0
- package/registry/hover-card/hover-card.tsx +144 -0
- package/registry/hover-card/item.json +4 -0
- package/registry/icons/icons.tsx +1691 -0
- package/registry/icons/item.json +4 -0
- package/registry/input/input.tsx +43 -0
- package/registry/input/item.json +4 -0
- package/registry/input-group/input-group.tsx +315 -0
- package/registry/input-group/item.json +4 -0
- package/registry/input-otp/input-otp.tsx +329 -0
- package/registry/input-otp/item.json +4 -0
- package/registry/item/item.json +4 -0
- package/registry/item/item.tsx +439 -0
- package/registry/kbd/item.json +4 -0
- package/registry/kbd/kbd.tsx +65 -0
- package/registry/label/item.json +4 -0
- package/registry/label/label.tsx +60 -0
- package/registry/marker/item.json +4 -0
- package/registry/marker/marker.tsx +175 -0
- package/registry/menubar/item.json +4 -0
- package/registry/menubar/menubar.tsx +694 -0
- package/registry/message/item.json +4 -0
- package/registry/message/message.tsx +280 -0
- package/registry/message-scroller/item.json +4 -0
- package/registry/message-scroller/message-scroller.tsx +262 -0
- package/registry/native-select/item.json +4 -0
- package/registry/native-select/native-select.tsx +113 -0
- package/registry/navigation-menu/item.json +4 -0
- package/registry/navigation-menu/navigation-menu.tsx +362 -0
- package/registry/pagination/item.json +4 -0
- package/registry/pagination/pagination.tsx +460 -0
- package/registry/password-input/item.json +5 -0
- package/registry/password-input/password-input.tsx +494 -0
- package/registry/popover/item.json +4 -0
- package/registry/popover/popover.tsx +284 -0
- package/registry/progress/item.json +4 -0
- package/registry/progress/progress.tsx +190 -0
- package/registry/questionnaire/item.json +4 -0
- package/registry/questionnaire/questionnaire.tsx +569 -0
- package/registry/radio-group/item.json +4 -0
- package/registry/radio-group/radio-group.tsx +101 -0
- package/registry/resizable/item.json +4 -0
- package/registry/resizable/resizable.tsx +455 -0
- package/registry/scroll-area/item.json +4 -0
- package/registry/scroll-area/scroll-area.tsx +149 -0
- package/registry/select/item.json +4 -0
- package/registry/select/select.tsx +571 -0
- package/registry/separator/item.json +4 -0
- package/registry/separator/separator.tsx +52 -0
- package/registry/sheet/item.json +4 -0
- package/registry/sheet/sheet.tsx +476 -0
- package/registry/sidebar/item.json +4 -0
- package/registry/sidebar/sidebar.tsx +1870 -0
- package/registry/skeleton/item.json +4 -0
- package/registry/skeleton/skeleton.tsx +47 -0
- package/registry/slider/item.json +4 -0
- package/registry/slider/slider.tsx +149 -0
- package/registry/spinner/item.json +4 -0
- package/registry/spinner/spinner.tsx +51 -0
- package/registry/switch/item.json +4 -0
- package/registry/switch/switch.tsx +93 -0
- package/registry/table/item.json +4 -0
- package/registry/table/table.tsx +373 -0
- package/registry/tabs/item.json +4 -0
- package/registry/tabs/tabs.tsx +209 -0
- package/registry/textarea/item.json +4 -0
- package/registry/textarea/textarea.tsx +51 -0
- package/registry/toast/item.json +4 -0
- package/registry/toast/toast.tsx +949 -0
- package/registry/toggle/item.json +4 -0
- package/registry/toggle/toggle.tsx +110 -0
- package/registry/toggle-group/item.json +4 -0
- package/registry/toggle-group/toggle-group.tsx +189 -0
- package/registry/tooltip/item.json +4 -0
- package/registry/tooltip/tooltip.tsx +169 -0
- package/registry/trace/item.json +4 -0
- package/registry/trace/trace.tsx +356 -0
- package/registry/ui-strings/item.json +5 -0
- package/registry/ui-strings/ui-strings.tsx +454 -0
- package/registry/use-highlighted-code/item.json +5 -0
- package/registry/use-highlighted-code/use-highlighted-code.tsx +145 -0
- package/registry/use-mobile/item.json +5 -0
- package/registry/use-mobile/use-mobile.ts +46 -0
- package/registry/utils/item.json +5 -0
- package/registry/utils/utils.ts +38 -0
- package/registry.json +3274 -0
- package/styles/animations.css +321 -0
- package/styles/theme-core.css +55 -0
- package/styles/theme.css +12 -0
- package/styles/themes/gray.css +69 -0
- package/styles/themes/neutral.css +69 -0
- package/styles/themes/slate.css +69 -0
- package/styles/themes/stone.css +69 -0
- package/styles/themes/zinc.css +69 -0
- 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,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 };
|