@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,412 @@
|
|
|
1
|
+
import { splitProps } from "@shadow-js/core";
|
|
2
|
+
import {
|
|
3
|
+
type DialogCloseProps,
|
|
4
|
+
type DialogContentProps,
|
|
5
|
+
type DialogDescriptionProps,
|
|
6
|
+
type DialogOverlayProps,
|
|
7
|
+
type DialogPortalProps,
|
|
8
|
+
Dialog as DialogPrimitive,
|
|
9
|
+
type DialogProps,
|
|
10
|
+
type DialogTitleProps,
|
|
11
|
+
type DialogTriggerProps,
|
|
12
|
+
type PolymorphicTarget,
|
|
13
|
+
} from "@shadow-js/primitives";
|
|
14
|
+
import type { ComponentProps, JSX } from "@shadow-js/web";
|
|
15
|
+
import { XIcon } from "@/components/ui/icons";
|
|
16
|
+
import { useUiStrings } from "@/lib/ui-strings";
|
|
17
|
+
import { cn } from "@/lib/utils";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A modal window over the page, for a task that needs an answer or an edit before the page continues.
|
|
21
|
+
* It is controlled with `open` and `onOpenChange`, or uncontrolled with `defaultOpen`. Its parts are
|
|
22
|
+
* {@link DialogTrigger}, {@link DialogContent} (with the overlay and the close button) and the header,
|
|
23
|
+
* title and description inside it.
|
|
24
|
+
*
|
|
25
|
+
* Keyboard and ARIA: Enter or Space on the trigger opens the dialog, which moves focus into it. Tab and
|
|
26
|
+
* Shift+Tab cycle inside it, and Escape closes it. Closing returns focus to the trigger. The content is
|
|
27
|
+
* a `role="dialog"` with `aria-modal="true"`, named by {@link DialogTitle} and described by
|
|
28
|
+
* {@link DialogDescription}, so render both. Without a title, pass `aria-label` on the content. The
|
|
29
|
+
* close button is a native button named "Close". A press on the overlay closes the dialog. In
|
|
30
|
+
* development, a dialog with no name logs a warning.
|
|
31
|
+
*
|
|
32
|
+
* shadcn/ui differences: `as` replaces `asChild` on {@link DialogTrigger} and {@link DialogClose}, and
|
|
33
|
+
* the prop is `class`, not `className`. Every part has a `data-slot`. The close button is part of
|
|
34
|
+
* {@link DialogContent} by default; pass `showCloseButton={false}` to remove it.
|
|
35
|
+
*
|
|
36
|
+
* @param props The dialog props: see {@link DialogProps}.
|
|
37
|
+
* @returns The dialog. It renders no element of its own.
|
|
38
|
+
* @example
|
|
39
|
+
* import { Button } from "@/components/ui/button";
|
|
40
|
+
* import {
|
|
41
|
+
* Dialog,
|
|
42
|
+
* DialogContent,
|
|
43
|
+
* DialogDescription,
|
|
44
|
+
* DialogHeader,
|
|
45
|
+
* DialogTitle,
|
|
46
|
+
* DialogTrigger,
|
|
47
|
+
* } from "@/components/ui/dialog";
|
|
48
|
+
*
|
|
49
|
+
* export function EditProfile() {
|
|
50
|
+
* return (
|
|
51
|
+
* <Dialog>
|
|
52
|
+
* <DialogTrigger as={Button} variant="outline">
|
|
53
|
+
* Edit profile
|
|
54
|
+
* </DialogTrigger>
|
|
55
|
+
* <DialogContent>
|
|
56
|
+
* <DialogHeader>
|
|
57
|
+
* <DialogTitle>Edit profile</DialogTitle>
|
|
58
|
+
* <DialogDescription>Make changes to your profile here.</DialogDescription>
|
|
59
|
+
* </DialogHeader>
|
|
60
|
+
* </DialogContent>
|
|
61
|
+
* </Dialog>
|
|
62
|
+
* );
|
|
63
|
+
* }
|
|
64
|
+
* @see {@link DialogContent}
|
|
65
|
+
* @see {@link DialogTrigger}
|
|
66
|
+
* @since 1.0.0
|
|
67
|
+
*/
|
|
68
|
+
function Dialog(props: DialogProps): JSX.Element {
|
|
69
|
+
return <DialogPrimitive {...props} />;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The button that opens the dialog. It is a native button by default; `as` renders another element,
|
|
74
|
+
* such as the shadcn `Button`. Enter and Space activate it.
|
|
75
|
+
*
|
|
76
|
+
* @param props The trigger props: the attributes of the rendered element, and `as`.
|
|
77
|
+
* @returns The trigger.
|
|
78
|
+
* @throws Error when rendered outside a {@link Dialog}.
|
|
79
|
+
* @example
|
|
80
|
+
* import { Button } from "@/components/ui/button";
|
|
81
|
+
* import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
|
82
|
+
*
|
|
83
|
+
* export function OpenDialog() {
|
|
84
|
+
* return (
|
|
85
|
+
* <Dialog>
|
|
86
|
+
* <DialogTrigger as={Button}>Open</DialogTrigger>
|
|
87
|
+
* <DialogContent>Dialog body.</DialogContent>
|
|
88
|
+
* </Dialog>
|
|
89
|
+
* );
|
|
90
|
+
* }
|
|
91
|
+
* @see {@link Dialog}
|
|
92
|
+
* @since 1.0.0
|
|
93
|
+
*/
|
|
94
|
+
function DialogTrigger<T extends PolymorphicTarget = "button">(
|
|
95
|
+
props: DialogTriggerProps<T>,
|
|
96
|
+
): JSX.Element {
|
|
97
|
+
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Renders the overlay and the content into `document.body`, or into the `container` you pass, while
|
|
102
|
+
* the dialog is open. {@link DialogContent} already portals itself, so use this only to choose another
|
|
103
|
+
* container.
|
|
104
|
+
*
|
|
105
|
+
* @param props The portal props: `container` and `forceMount`.
|
|
106
|
+
* @returns The portal, or nothing while the dialog is closed.
|
|
107
|
+
* @throws Error when rendered outside a {@link Dialog}.
|
|
108
|
+
* @example
|
|
109
|
+
* import { Dialog, DialogContent, DialogOverlay, DialogPortal, DialogTrigger } from "@/components/ui/dialog";
|
|
110
|
+
*
|
|
111
|
+
* export function Portaled() {
|
|
112
|
+
* return (
|
|
113
|
+
* <Dialog>
|
|
114
|
+
* <DialogTrigger>Open</DialogTrigger>
|
|
115
|
+
* <DialogPortal>
|
|
116
|
+
* <DialogOverlay />
|
|
117
|
+
* <DialogContent>Dialog body.</DialogContent>
|
|
118
|
+
* </DialogPortal>
|
|
119
|
+
* </Dialog>
|
|
120
|
+
* );
|
|
121
|
+
* }
|
|
122
|
+
* @see {@link DialogContent}
|
|
123
|
+
* @since 1.0.0
|
|
124
|
+
*/
|
|
125
|
+
function DialogPortal(props: DialogPortalProps): JSX.Element {
|
|
126
|
+
return <DialogPrimitive.Portal {...props} />;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A button that closes the dialog. It is a native button by default; `as` renders another element. Enter
|
|
131
|
+
* and Space activate it.
|
|
132
|
+
*
|
|
133
|
+
* @param props The close props: the attributes of the rendered element, and `as`.
|
|
134
|
+
* @returns The close button.
|
|
135
|
+
* @throws Error when rendered outside a {@link Dialog}.
|
|
136
|
+
* @example
|
|
137
|
+
* import { Button } from "@/components/ui/button";
|
|
138
|
+
* import { Dialog, DialogClose, DialogContent, DialogFooter, DialogTrigger } from "@/components/ui/dialog";
|
|
139
|
+
*
|
|
140
|
+
* export function Confirm() {
|
|
141
|
+
* return (
|
|
142
|
+
* <Dialog>
|
|
143
|
+
* <DialogTrigger>Delete</DialogTrigger>
|
|
144
|
+
* <DialogContent>
|
|
145
|
+
* <DialogFooter>
|
|
146
|
+
* <DialogClose as={Button} variant="outline">
|
|
147
|
+
* Cancel
|
|
148
|
+
* </DialogClose>
|
|
149
|
+
* </DialogFooter>
|
|
150
|
+
* </DialogContent>
|
|
151
|
+
* </Dialog>
|
|
152
|
+
* );
|
|
153
|
+
* }
|
|
154
|
+
* @see {@link Dialog}
|
|
155
|
+
* @since 1.0.0
|
|
156
|
+
*/
|
|
157
|
+
function DialogClose<T extends PolymorphicTarget = "button">(
|
|
158
|
+
props: DialogCloseProps<T>,
|
|
159
|
+
): JSX.Element {
|
|
160
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The dimmed backdrop behind a modal dialog. It is hidden from assistive technology, and a press on it
|
|
165
|
+
* closes the dialog. {@link DialogContent} renders one already.
|
|
166
|
+
*
|
|
167
|
+
* @param props The overlay props: `class`, which is merged with the backdrop classes.
|
|
168
|
+
* @returns The overlay.
|
|
169
|
+
* @throws Error when rendered outside a {@link Dialog}.
|
|
170
|
+
* @example
|
|
171
|
+
* import { Dialog, DialogContent, DialogOverlay, DialogPortal, DialogTrigger } from "@/components/ui/dialog";
|
|
172
|
+
*
|
|
173
|
+
* export function Dimmed() {
|
|
174
|
+
* return (
|
|
175
|
+
* <Dialog>
|
|
176
|
+
* <DialogTrigger>Open</DialogTrigger>
|
|
177
|
+
* <DialogPortal>
|
|
178
|
+
* <DialogOverlay class="bg-black/70" />
|
|
179
|
+
* <DialogContent>Dialog body.</DialogContent>
|
|
180
|
+
* </DialogPortal>
|
|
181
|
+
* </Dialog>
|
|
182
|
+
* );
|
|
183
|
+
* }
|
|
184
|
+
* @see {@link DialogContent}
|
|
185
|
+
* @since 1.0.0
|
|
186
|
+
*/
|
|
187
|
+
function DialogOverlay(props: DialogOverlayProps): JSX.Element {
|
|
188
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
189
|
+
return (
|
|
190
|
+
<DialogPrimitive.Overlay
|
|
191
|
+
data-slot="dialog-overlay"
|
|
192
|
+
class={cn(
|
|
193
|
+
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
194
|
+
local.class,
|
|
195
|
+
)}
|
|
196
|
+
{...rest}
|
|
197
|
+
/>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Own props of {@link DialogContent}: the parts the panel adds.
|
|
203
|
+
* @see {@link DialogContent}
|
|
204
|
+
* @since 1.0.0
|
|
205
|
+
*/
|
|
206
|
+
export interface DialogContentExtraProps {
|
|
207
|
+
/**
|
|
208
|
+
* Shows the close button in the top-right corner. The button is named "Close".
|
|
209
|
+
* @default true
|
|
210
|
+
*/
|
|
211
|
+
readonly showCloseButton?: boolean | undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The dialog panel: it renders the portal and the overlay, and the centered content with a close
|
|
216
|
+
* button in the corner unless `showCloseButton` is `false`. Put the {@link DialogHeader} (with
|
|
217
|
+
* {@link DialogTitle} and {@link DialogDescription}) and your content inside it.
|
|
218
|
+
*
|
|
219
|
+
* @param props The panel props: see {@link DialogContentProps} and {@link DialogContentExtraProps}.
|
|
220
|
+
* @returns The panel, rendered in a portal while the dialog is open.
|
|
221
|
+
* @throws Error when rendered outside a {@link Dialog}.
|
|
222
|
+
* @example
|
|
223
|
+
* import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
|
224
|
+
*
|
|
225
|
+
* export function Notice() {
|
|
226
|
+
* return (
|
|
227
|
+
* <Dialog>
|
|
228
|
+
* <DialogTrigger>Show notice</DialogTrigger>
|
|
229
|
+
* <DialogContent showCloseButton={false}>
|
|
230
|
+
* <DialogHeader>
|
|
231
|
+
* <DialogTitle>Saved</DialogTitle>
|
|
232
|
+
* <DialogDescription>Your changes are saved.</DialogDescription>
|
|
233
|
+
* </DialogHeader>
|
|
234
|
+
* </DialogContent>
|
|
235
|
+
* </Dialog>
|
|
236
|
+
* );
|
|
237
|
+
* }
|
|
238
|
+
* @see {@link Dialog}
|
|
239
|
+
* @see {@link DialogHeader}
|
|
240
|
+
* @since 1.0.0
|
|
241
|
+
*/
|
|
242
|
+
function DialogContent(props: DialogContentProps & DialogContentExtraProps): JSX.Element {
|
|
243
|
+
const [local, rest] = splitProps(props, ["class", "children", "showCloseButton"]);
|
|
244
|
+
const t = useUiStrings();
|
|
245
|
+
return (
|
|
246
|
+
<DialogPortal>
|
|
247
|
+
<DialogOverlay />
|
|
248
|
+
<DialogPrimitive.Content
|
|
249
|
+
data-slot="dialog-content"
|
|
250
|
+
class={cn(
|
|
251
|
+
"fixed start-1/2 top-1/2 z-50 grid max-h-[calc(100%-2rem)] w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 overflow-y-auto rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none 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 sm:max-w-lg rtl:translate-x-1/2",
|
|
252
|
+
local.class,
|
|
253
|
+
)}
|
|
254
|
+
{...rest}
|
|
255
|
+
>
|
|
256
|
+
{local.children}
|
|
257
|
+
{local.showCloseButton !== false && (
|
|
258
|
+
<DialogPrimitive.Close
|
|
259
|
+
data-slot="dialog-close"
|
|
260
|
+
class="absolute end-4 top-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 outline-none focus:outline-solid focus:outline-2 focus:outline-offset-2 focus:outline-transparent disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
|
261
|
+
>
|
|
262
|
+
<XIcon />
|
|
263
|
+
<span class="sr-only">{t("dialog.close", "Close")}</span>
|
|
264
|
+
</DialogPrimitive.Close>
|
|
265
|
+
)}
|
|
266
|
+
</DialogPrimitive.Content>
|
|
267
|
+
</DialogPortal>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* The layout of the title and description at the top of a dialog. It is a plain `div` with no role.
|
|
273
|
+
*
|
|
274
|
+
* @param props The header props: the attributes of the rendered `div`.
|
|
275
|
+
* @returns The header.
|
|
276
|
+
* @example
|
|
277
|
+
* import { DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
278
|
+
*
|
|
279
|
+
* export function Heading() {
|
|
280
|
+
* return (
|
|
281
|
+
* <DialogHeader>
|
|
282
|
+
* <DialogTitle>Edit profile</DialogTitle>
|
|
283
|
+
* <DialogDescription>Make changes here.</DialogDescription>
|
|
284
|
+
* </DialogHeader>
|
|
285
|
+
* );
|
|
286
|
+
* }
|
|
287
|
+
* @see {@link DialogTitle}
|
|
288
|
+
* @since 1.0.0
|
|
289
|
+
*/
|
|
290
|
+
function DialogHeader(props: ComponentProps<"div">): JSX.Element {
|
|
291
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
292
|
+
return (
|
|
293
|
+
<div
|
|
294
|
+
data-slot="dialog-header"
|
|
295
|
+
class={cn("flex flex-col gap-2 text-center sm:text-start", local.class)}
|
|
296
|
+
{...rest}
|
|
297
|
+
/>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* The layout of the action buttons at the bottom of a dialog: stacked on small screens, and aligned to
|
|
303
|
+
* the end on larger ones. It is a plain `div` with no role.
|
|
304
|
+
*
|
|
305
|
+
* @param props The footer props: the attributes of the rendered `div`.
|
|
306
|
+
* @returns The footer.
|
|
307
|
+
* @example
|
|
308
|
+
* import { Button } from "@/components/ui/button";
|
|
309
|
+
* import { DialogFooter } from "@/components/ui/dialog";
|
|
310
|
+
*
|
|
311
|
+
* export function Actions() {
|
|
312
|
+
* return (
|
|
313
|
+
* <DialogFooter>
|
|
314
|
+
* <Button>Save</Button>
|
|
315
|
+
* </DialogFooter>
|
|
316
|
+
* );
|
|
317
|
+
* }
|
|
318
|
+
* @see {@link DialogContent}
|
|
319
|
+
* @since 1.0.0
|
|
320
|
+
*/
|
|
321
|
+
function DialogFooter(props: ComponentProps<"div">): JSX.Element {
|
|
322
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
323
|
+
return (
|
|
324
|
+
<div
|
|
325
|
+
data-slot="dialog-footer"
|
|
326
|
+
class={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", local.class)}
|
|
327
|
+
{...rest}
|
|
328
|
+
/>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* The accessible name of the dialog, and its visible heading. The dialog is labeled by it. Use
|
|
334
|
+
* `aria-label` on the content instead only when there is no visible heading. The title has no keys.
|
|
335
|
+
*
|
|
336
|
+
* @param props The title props: the attributes of the rendered `h2`.
|
|
337
|
+
* @returns The heading.
|
|
338
|
+
* @throws Error when rendered outside a {@link Dialog}.
|
|
339
|
+
* @example
|
|
340
|
+
* import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
|
341
|
+
*
|
|
342
|
+
* export function Titled() {
|
|
343
|
+
* return (
|
|
344
|
+
* <Dialog>
|
|
345
|
+
* <DialogTrigger>Edit profile</DialogTrigger>
|
|
346
|
+
* <DialogContent>
|
|
347
|
+
* <DialogTitle>Edit profile</DialogTitle>
|
|
348
|
+
* </DialogContent>
|
|
349
|
+
* </Dialog>
|
|
350
|
+
* );
|
|
351
|
+
* }
|
|
352
|
+
* @see {@link DialogDescription}
|
|
353
|
+
* @since 1.0.0
|
|
354
|
+
*/
|
|
355
|
+
function DialogTitle(props: DialogTitleProps): JSX.Element {
|
|
356
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
357
|
+
return (
|
|
358
|
+
<DialogPrimitive.Title
|
|
359
|
+
data-slot="dialog-title"
|
|
360
|
+
class={cn("text-lg leading-none font-semibold", local.class)}
|
|
361
|
+
{...rest}
|
|
362
|
+
/>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* The accessible description of the dialog: the text a screen reader reads after its name. It is a
|
|
368
|
+
* plain paragraph and has no keys.
|
|
369
|
+
*
|
|
370
|
+
* @param props The description props: the attributes of the rendered `p`.
|
|
371
|
+
* @returns The paragraph.
|
|
372
|
+
* @throws Error when rendered outside a {@link Dialog}.
|
|
373
|
+
* @example
|
|
374
|
+
* import { Dialog, DialogContent, DialogDescription, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
|
375
|
+
*
|
|
376
|
+
* export function Described() {
|
|
377
|
+
* return (
|
|
378
|
+
* <Dialog>
|
|
379
|
+
* <DialogTrigger>Delete</DialogTrigger>
|
|
380
|
+
* <DialogContent>
|
|
381
|
+
* <DialogTitle>Delete account</DialogTitle>
|
|
382
|
+
* <DialogDescription>This cannot be undone.</DialogDescription>
|
|
383
|
+
* </DialogContent>
|
|
384
|
+
* </Dialog>
|
|
385
|
+
* );
|
|
386
|
+
* }
|
|
387
|
+
* @see {@link DialogTitle}
|
|
388
|
+
* @since 1.0.0
|
|
389
|
+
*/
|
|
390
|
+
function DialogDescription(props: DialogDescriptionProps): JSX.Element {
|
|
391
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
392
|
+
return (
|
|
393
|
+
<DialogPrimitive.Description
|
|
394
|
+
data-slot="dialog-description"
|
|
395
|
+
class={cn("text-sm text-muted-foreground", local.class)}
|
|
396
|
+
{...rest}
|
|
397
|
+
/>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export {
|
|
402
|
+
Dialog,
|
|
403
|
+
DialogClose,
|
|
404
|
+
DialogContent,
|
|
405
|
+
DialogDescription,
|
|
406
|
+
DialogFooter,
|
|
407
|
+
DialogHeader,
|
|
408
|
+
DialogOverlay,
|
|
409
|
+
DialogPortal,
|
|
410
|
+
DialogTitle,
|
|
411
|
+
DialogTrigger,
|
|
412
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Accessor } from "@shadow-js/core";
|
|
2
|
+
import {
|
|
3
|
+
type Direction,
|
|
4
|
+
DirectionProvider as DirectionPrimitive,
|
|
5
|
+
useDirection as useDirectionPrimitive,
|
|
6
|
+
} from "@shadow-js/primitives";
|
|
7
|
+
import type { JSX, JsxChild } from "@shadow-js/web";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Props of {@link DirectionProvider}: the direction and the subtree. `direction` is shadcn's name for `dir`; when both
|
|
11
|
+
* are given, `dir` wins.
|
|
12
|
+
* @since 1.0.0
|
|
13
|
+
* @see {@link DirectionProvider}
|
|
14
|
+
*/
|
|
15
|
+
export interface DirectionProviderProps {
|
|
16
|
+
/** The reading direction for every component below: `"ltr"` or `"rtl"`. @default none */
|
|
17
|
+
readonly dir?: Direction | undefined;
|
|
18
|
+
/** The same, under the name shadcn uses. @default none */
|
|
19
|
+
readonly direction?: Direction | undefined;
|
|
20
|
+
/** The subtree that reads the direction. @default none */
|
|
21
|
+
readonly children?: JsxChild;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Sets the reading direction for the components below it: the arrow keys of tabs, menus, sliders and carousels,
|
|
26
|
+
* and the side popovers open on. It renders no element and writes no attribute, so also set `dir` on the document (`<html dir="rtl">`)
|
|
27
|
+
* or on the container: the provider tells the components, and the `dir` attribute tells the browser, which lays out the text.
|
|
28
|
+
* Without a provider each component reads the `dir` of the document.
|
|
29
|
+
*
|
|
30
|
+
* Keyboard and ARIA: none of its own. It changes which arrow key means "next" in right-to-left text.
|
|
31
|
+
*
|
|
32
|
+
* Compared with shadcn/ui: `dir` is the primitives' name and `direction` is accepted as shadcn's.
|
|
33
|
+
*
|
|
34
|
+
* @param props - The direction and the subtree.
|
|
35
|
+
* @returns The subtree.
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* import { DirectionProvider } from "@/components/ui/direction";
|
|
39
|
+
* import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
40
|
+
*
|
|
41
|
+
* export function RightToLeftTabs() {
|
|
42
|
+
* return (
|
|
43
|
+
* <DirectionProvider dir="rtl">
|
|
44
|
+
* <Tabs defaultValue="one" dir="rtl">
|
|
45
|
+
* <TabsList>
|
|
46
|
+
* <TabsTrigger value="one">One</TabsTrigger>
|
|
47
|
+
* </TabsList>
|
|
48
|
+
* </Tabs>
|
|
49
|
+
* </DirectionProvider>
|
|
50
|
+
* );
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
* @since 1.0.0
|
|
54
|
+
* @see {@link useDirection}
|
|
55
|
+
*/
|
|
56
|
+
function DirectionProvider(props: DirectionProviderProps): JSX.Element {
|
|
57
|
+
return (
|
|
58
|
+
<DirectionPrimitive dir={props.dir ?? props.direction ?? "ltr"}>
|
|
59
|
+
{props.children}
|
|
60
|
+
</DirectionPrimitive>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Reads the reading direction at this point of the tree: the nearest {@link DirectionProvider}, else the `dir` of the document, else
|
|
66
|
+
* `"ltr"`. Call it while a component sets up, like any hook; it returns an accessor, so a change of direction updates what reads it.
|
|
67
|
+
*
|
|
68
|
+
* Keyboard and ARIA: none of its own.
|
|
69
|
+
*
|
|
70
|
+
* @param local - An accessor of a direction the caller was given, which wins over the provider.
|
|
71
|
+
* @returns An accessor of `"ltr"` or `"rtl"`.
|
|
72
|
+
* @example
|
|
73
|
+
* ```tsx
|
|
74
|
+
* import { useDirection } from "@/components/ui/direction";
|
|
75
|
+
*
|
|
76
|
+
* export function Hint() {
|
|
77
|
+
* const dir = useDirection();
|
|
78
|
+
* return <span>{dir() === "rtl" ? "Press the left arrow" : "Press the right arrow"}</span>;
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
* @since 1.0.0
|
|
82
|
+
* @see {@link DirectionProvider}
|
|
83
|
+
*/
|
|
84
|
+
function useDirection(local?: () => Direction | undefined): Accessor<Direction> {
|
|
85
|
+
return useDirectionPrimitive(local);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { DirectionProvider, useDirection };
|