@adea-ai/ui 0.10.8

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 (40) hide show
  1. package/README.md +5 -0
  2. package/components.json +19 -0
  3. package/package.json +56 -0
  4. package/src/components/account-drawer.tsx +133 -0
  5. package/src/components/on-screen-controls.tsx +156 -0
  6. package/src/components/prop-catalog.tsx +583 -0
  7. package/src/components/scene-settings.tsx +202 -0
  8. package/src/components/theme-provider.tsx +30 -0
  9. package/src/components/theme-toggle.tsx +73 -0
  10. package/src/components/ui/badge.tsx +49 -0
  11. package/src/components/ui/button.tsx +60 -0
  12. package/src/components/ui/card.tsx +18 -0
  13. package/src/components/ui/dialog.tsx +129 -0
  14. package/src/components/ui/drawer.tsx +209 -0
  15. package/src/components/ui/dropdown-menu.tsx +258 -0
  16. package/src/components/ui/empty.tsx +94 -0
  17. package/src/components/ui/field.tsx +224 -0
  18. package/src/components/ui/input.tsx +20 -0
  19. package/src/components/ui/label.tsx +20 -0
  20. package/src/components/ui/radio-group.tsx +38 -0
  21. package/src/components/ui/separator.tsx +21 -0
  22. package/src/components/ui/skeleton.tsx +13 -0
  23. package/src/components/ui/spinner.tsx +16 -0
  24. package/src/components/ui/switch.tsx +32 -0
  25. package/src/components/ui/tabs.tsx +75 -0
  26. package/src/components/ui/toggle-group.tsx +87 -0
  27. package/src/components/ui/toggle.tsx +45 -0
  28. package/src/components/ui/tooltip.tsx +56 -0
  29. package/src/components/version-dialog.tsx +366 -0
  30. package/src/components/workspace-brand.tsx +18 -0
  31. package/src/components/workspace-logo.tsx +17 -0
  32. package/src/index.tsx +55 -0
  33. package/src/lib/utils.ts +6 -0
  34. package/src/lib/version-notes.ts +30 -0
  35. package/src/styles/auth-shell.css +67 -0
  36. package/src/styles/conventional-workspace.css +2846 -0
  37. package/src/styles/globals.css +93 -0
  38. package/src/styles/theme.css +94 -0
  39. package/src/styles/workspace-shell.css +145 -0
  40. package/tsconfig.json +10 -0
@@ -0,0 +1,202 @@
1
+ "use client";
2
+
3
+ import { createPortal } from "react-dom";
4
+ import { useEffect, useState, type ReactNode } from "react";
5
+ import { Box, Camera, Focus, Grid3X3, UserRoundPen } from "lucide-react";
6
+ import { AccountDrawer } from "./account-drawer";
7
+ import { Button } from "#components/ui/button";
8
+
9
+ export type SceneSettingsProps = {
10
+ cameraViewMode: "perspective" | "orthographic";
11
+ onCameraViewModeChange: (value: "perspective" | "orthographic") => void;
12
+ allowCameraViewModeChange?: boolean;
13
+ /** Standalone character selection and customization menu toggle. */
14
+ characterDesignerEnabled?: boolean;
15
+ onCharacterDesignerChange?: (value: boolean) => void;
16
+ /** DOM target for the character designer control in an app shell toolbar. */
17
+ characterDesignerTargetId?: string;
18
+ /** Open the scene-specific room layout and interior prop designer. */
19
+ onOpenRoomDesigner?: () => void;
20
+ /** DOM target for the account drawer trigger in an app shell toolbar. */
21
+ accountTargetId?: string;
22
+ accountLabel?: string;
23
+ accountAuthenticated?: boolean;
24
+ accountBusy?: boolean;
25
+ accountMusicControl?: ReactNode;
26
+ onAccountSignIn?: () => void;
27
+ onAccountSignOut?: () => void;
28
+ showAccountDrawer?: boolean;
29
+ /** DOM target for the compact camera controls in an app shell toolbar. */
30
+ cameraTargetId?: string;
31
+ /** DOM target for the room designer control in an app shell toolbar. */
32
+ roomDesignerTargetId?: string;
33
+ /** DOM target for the development scene editor control in an app shell toolbar. */
34
+ sceneEditorTargetId?: string;
35
+ /** Development-only scene editor toggle, available in perspective view. */
36
+ sceneEditorEnabled?: boolean;
37
+ onSceneEditorChange?: (value: boolean) => void;
38
+ };
39
+
40
+ function usePortalTarget(targetId?: string) {
41
+ const [target, setTarget] = useState<HTMLElement | null>(null);
42
+
43
+ useEffect(() => {
44
+ setTarget(targetId ? document.getElementById(targetId) : null);
45
+ }, [targetId]);
46
+
47
+ return target;
48
+ }
49
+
50
+ function renderInTarget(content: ReactNode, target: HTMLElement | null) {
51
+ return target ? createPortal(content, target) : content;
52
+ }
53
+
54
+ /** Compact scene controls shared by the HQ shell and other scene hosts. */
55
+ export function SceneSettings({
56
+ cameraViewMode,
57
+ onCameraViewModeChange,
58
+ allowCameraViewModeChange = true,
59
+ characterDesignerEnabled,
60
+ onCharacterDesignerChange,
61
+ characterDesignerTargetId,
62
+ onOpenRoomDesigner,
63
+ accountTargetId,
64
+ accountLabel,
65
+ accountAuthenticated,
66
+ accountBusy,
67
+ accountMusicControl,
68
+ onAccountSignIn,
69
+ onAccountSignOut,
70
+ showAccountDrawer = true,
71
+ cameraTargetId,
72
+ roomDesignerTargetId,
73
+ sceneEditorTargetId,
74
+ sceneEditorEnabled,
75
+ onSceneEditorChange,
76
+ }: SceneSettingsProps) {
77
+ const cameraTarget = usePortalTarget(cameraTargetId);
78
+ const characterDesignerTarget = usePortalTarget(characterDesignerTargetId);
79
+ const roomDesignerTarget = usePortalTarget(roomDesignerTargetId);
80
+ const sceneEditorTarget = usePortalTarget(sceneEditorTargetId);
81
+
82
+ const cameraControl = (
83
+ <div className="workspace-camera-control" role="group" aria-label="Camera view">
84
+ <Button
85
+ type="button"
86
+ size="sm"
87
+ variant={cameraViewMode === "perspective" ? "default" : "outline"}
88
+ className="workspace-camera-button"
89
+ aria-label="Perspective camera"
90
+ title="Perspective camera"
91
+ aria-pressed={cameraViewMode === "perspective"}
92
+ onClick={() => onCameraViewModeChange("perspective")}
93
+ >
94
+ <Camera aria-hidden="true" />
95
+ Perspective
96
+ </Button>
97
+ <Button
98
+ type="button"
99
+ size="sm"
100
+ variant={cameraViewMode === "orthographic" ? "default" : "outline"}
101
+ className="workspace-camera-button"
102
+ aria-label="Top-down camera"
103
+ title="Top-down camera"
104
+ aria-pressed={cameraViewMode === "orthographic"}
105
+ onClick={() => onCameraViewModeChange("orthographic")}
106
+ >
107
+ <Focus aria-hidden="true" />
108
+ Top-down
109
+ </Button>
110
+ </div>
111
+ );
112
+
113
+ const characterDesignerControl = (
114
+ <Button
115
+ type="button"
116
+ variant={characterDesignerEnabled ? "default" : "outline"}
117
+ size="sm"
118
+ aria-haspopup="dialog"
119
+ aria-label={characterDesignerEnabled ? "Close character designer" : "Open character designer"}
120
+ title={characterDesignerEnabled ? "Close character designer" : "Open character designer"}
121
+ aria-pressed={characterDesignerEnabled}
122
+ onClick={() => onCharacterDesignerChange?.(!characterDesignerEnabled)}
123
+ >
124
+ <UserRoundPen className="size-4" aria-hidden="true" />
125
+ <span className="workspace-character-designer-label">Character</span>
126
+ </Button>
127
+ );
128
+
129
+ const roomDesignerControl = (
130
+ <Button
131
+ type="button"
132
+ variant="outline"
133
+ size="sm"
134
+ aria-haspopup="dialog"
135
+ aria-label="Open room designer"
136
+ title="Open room designer"
137
+ onClick={onOpenRoomDesigner}
138
+ >
139
+ <Grid3X3 className="size-4" aria-hidden="true" />
140
+ <span className="workspace-room-designer-label">Room designer</span>
141
+ </Button>
142
+ );
143
+
144
+ const sceneEditorControl = (
145
+ <Button
146
+ type="button"
147
+ variant={sceneEditorEnabled ? "default" : "outline"}
148
+ size="sm"
149
+ aria-haspopup="dialog"
150
+ aria-label={sceneEditorEnabled ? "Close scene editor" : "Open scene editor"}
151
+ title={sceneEditorEnabled ? "Close scene editor" : "Open scene editor"}
152
+ aria-pressed={sceneEditorEnabled}
153
+ onClick={() => onSceneEditorChange?.(!sceneEditorEnabled)}
154
+ >
155
+ <Box className="size-4" aria-hidden="true" />
156
+ <span className="workspace-scene-editor-label">Scene editor</span>
157
+ </Button>
158
+ );
159
+
160
+ return (
161
+ <>
162
+ {showAccountDrawer ? (
163
+ <AccountDrawer
164
+ accountLabel={accountLabel}
165
+ authenticated={accountAuthenticated}
166
+ busy={accountBusy}
167
+ musicControl={accountMusicControl}
168
+ triggerTargetId={accountTargetId}
169
+ onSignIn={onAccountSignIn}
170
+ onSignOut={onAccountSignOut}
171
+ />
172
+ ) : null}
173
+ {allowCameraViewModeChange ? renderInTarget(cameraControl, cameraTarget) : null}
174
+ {characterDesignerEnabled != null &&
175
+ !characterDesignerEnabled &&
176
+ onCharacterDesignerChange ? (
177
+ characterDesignerTarget ? (
178
+ createPortal(characterDesignerControl, characterDesignerTarget)
179
+ ) : (
180
+ <div className="fixed right-4 top-16 z-40">{characterDesignerControl}</div>
181
+ )
182
+ ) : null}
183
+ {onOpenRoomDesigner && cameraViewMode === "orthographic" ? (
184
+ roomDesignerTarget ? (
185
+ createPortal(roomDesignerControl, roomDesignerTarget)
186
+ ) : (
187
+ <div className="fixed right-4 top-16 z-40">{roomDesignerControl}</div>
188
+ )
189
+ ) : null}
190
+ {sceneEditorEnabled != null &&
191
+ !sceneEditorEnabled &&
192
+ onSceneEditorChange &&
193
+ cameraViewMode === "perspective" ? (
194
+ sceneEditorTarget ? (
195
+ createPortal(sceneEditorControl, sceneEditorTarget)
196
+ ) : (
197
+ <div className="fixed right-4 top-16 z-40">{sceneEditorControl}</div>
198
+ )
199
+ ) : null}
200
+ </>
201
+ );
202
+ }
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ import { ThemeProvider as NextThemesProvider } from "next-themes";
4
+ import type { ComponentProps, ReactElement, ReactNode } from "react";
5
+
6
+ // Bun 1.4 can resolve next-themes against a separate React type instance. The
7
+ // runtime component is compatible, but TypeScript then drops `children` from
8
+ // its JSX props. Keep the shared provider's public contract stable by
9
+ // normalizing the third-party component's props at this boundary.
10
+ type CompatibleNextThemesProviderProps = ComponentProps<typeof NextThemesProvider> & {
11
+ children?: ReactNode;
12
+ };
13
+
14
+ const CompatibleNextThemesProvider = NextThemesProvider as unknown as (
15
+ props: CompatibleNextThemesProviderProps
16
+ ) => ReactElement;
17
+
18
+ /** Shared theme provider used by each Next app in the workspace. */
19
+ export function ThemeProvider({ children }: { children: ReactNode }) {
20
+ return (
21
+ <CompatibleNextThemesProvider
22
+ attribute="class"
23
+ defaultTheme="system"
24
+ enableSystem
25
+ disableTransitionOnChange
26
+ >
27
+ {children}
28
+ </CompatibleNextThemesProvider>
29
+ );
30
+ }
@@ -0,0 +1,73 @@
1
+ "use client";
2
+
3
+ import { useSyncExternalStore } from "react";
4
+ import { Monitor, Moon, Sun } from "lucide-react";
5
+ import { useTheme } from "next-themes";
6
+ import { Button } from "#components/ui/button";
7
+ import { cn } from "#lib/utils";
8
+
9
+ /**
10
+ * Compact sun/moon theme selector. Wired to the next-themes provider so the
11
+ * choice persists across the app (localStorage) and applies to every scene.
12
+ */
13
+ export function ThemeToggle({ className }: { className?: string }) {
14
+ const { theme, setTheme } = useTheme();
15
+ const mounted = useSyncExternalStore(
16
+ () => () => undefined,
17
+ () => true,
18
+ () => false
19
+ );
20
+
21
+ // next-themes resolves the saved/system theme after mount. Use the stable
22
+ // light presentation for both SSR and the first client render so the
23
+ // aria/class attributes cannot mismatch during hydration.
24
+ const selectedTheme = mounted ? theme : "system";
25
+
26
+ return (
27
+ <div
28
+ className={cn(
29
+ "inline-flex items-center gap-1.5 rounded-full border border-input bg-background p-1.5",
30
+ className
31
+ )}
32
+ role="radiogroup"
33
+ aria-label="Theme"
34
+ >
35
+ <Button
36
+ type="button"
37
+ role="radio"
38
+ aria-checked={selectedTheme === "system"}
39
+ aria-label="System theme"
40
+ variant={selectedTheme === "system" ? "default" : "ghost"}
41
+ size="icon-sm"
42
+ onClick={() => setTheme("system")}
43
+ className="rounded-full"
44
+ >
45
+ <Monitor className="size-4" aria-hidden="true" />
46
+ </Button>
47
+ <Button
48
+ type="button"
49
+ role="radio"
50
+ aria-checked={selectedTheme === "light"}
51
+ aria-label="Light theme"
52
+ variant={selectedTheme === "light" ? "default" : "ghost"}
53
+ size="icon-sm"
54
+ onClick={() => setTheme("light")}
55
+ className="rounded-full"
56
+ >
57
+ <Sun className="size-4" aria-hidden="true" />
58
+ </Button>
59
+ <Button
60
+ type="button"
61
+ role="radio"
62
+ aria-checked={selectedTheme === "dark"}
63
+ aria-label="Dark theme"
64
+ variant={selectedTheme === "dark" ? "default" : "ghost"}
65
+ size="icon-sm"
66
+ onClick={() => setTheme("dark")}
67
+ className="rounded-full"
68
+ >
69
+ <Moon className="size-4" aria-hidden="true" />
70
+ </Button>
71
+ </div>
72
+ );
73
+ }
@@ -0,0 +1,49 @@
1
+ import { mergeProps } from "@base-ui/react/merge-props";
2
+ import { useRender } from "@base-ui/react/use-render";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "#lib/utils";
6
+
7
+ const badgeVariants = cva(
8
+ "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
13
+ secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
14
+ destructive:
15
+ "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
16
+ outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
17
+ ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
18
+ link: "text-primary underline-offset-4 hover:underline",
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ variant: "default",
23
+ },
24
+ }
25
+ );
26
+
27
+ function Badge({
28
+ className,
29
+ variant = "default",
30
+ render,
31
+ ...props
32
+ }: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
33
+ return useRender({
34
+ defaultTagName: "span",
35
+ props: mergeProps<"span">(
36
+ {
37
+ className: cn(badgeVariants({ variant }), className),
38
+ },
39
+ props
40
+ ),
41
+ render,
42
+ state: {
43
+ slot: "badge",
44
+ variant,
45
+ },
46
+ });
47
+ }
48
+
49
+ export { Badge, badgeVariants };
@@ -0,0 +1,60 @@
1
+ import { Button as ButtonPrimitive } from "@base-ui/react/button";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "#lib/utils";
5
+
6
+ const buttonVariants = cva(
7
+ "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
12
+ outline:
13
+ "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
14
+ secondary:
15
+ "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
16
+ ghost:
17
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
18
+ destructive:
19
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
20
+ success:
21
+ "bg-[#1a7f37]/10 text-[#1a7f37] hover:bg-[#1a7f37]/20 focus-visible:border-[#1a7f37]/40 focus-visible:ring-[#1a7f37]/20 dark:bg-[#3fb950]/20 dark:text-[#7ee787] dark:hover:bg-[#3fb950]/30 dark:focus-visible:ring-[#3fb950]/40",
22
+ link: "text-primary underline-offset-4 hover:underline",
23
+ },
24
+ size: {
25
+ default:
26
+ "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
27
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
28
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
29
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
30
+ icon: "size-8",
31
+ "icon-xs":
32
+ "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
33
+ "icon-sm":
34
+ "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
35
+ "icon-lg": "size-9",
36
+ },
37
+ },
38
+ defaultVariants: {
39
+ variant: "default",
40
+ size: "default",
41
+ },
42
+ }
43
+ );
44
+
45
+ function Button({
46
+ className,
47
+ variant = "default",
48
+ size = "default",
49
+ ...props
50
+ }: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
51
+ return (
52
+ <ButtonPrimitive
53
+ data-slot="button"
54
+ className={cn(buttonVariants({ variant, size, className }))}
55
+ {...props}
56
+ />
57
+ );
58
+ }
59
+
60
+ export { Button, buttonVariants };
@@ -0,0 +1,18 @@
1
+ import type { HTMLAttributes } from "react";
2
+ import { cn } from "#lib/utils";
3
+
4
+ function Card({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
5
+ return (
6
+ <div
7
+ data-slot="card"
8
+ className={cn("rounded-xl border bg-card text-card-foreground shadow-sm", className)}
9
+ {...props}
10
+ />
11
+ );
12
+ }
13
+
14
+ function CardContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
15
+ return <div data-slot="card-content" className={cn("p-4", className)} {...props} />;
16
+ }
17
+
18
+ export { Card, CardContent };
@@ -0,0 +1,129 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
5
+ import { X } from "lucide-react";
6
+
7
+ import { cn } from "#lib/utils";
8
+
9
+ function Dialog({ ...props }: DialogPrimitive.Root.Props) {
10
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
11
+ }
12
+
13
+ function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
14
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
15
+ }
16
+
17
+ function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
18
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
19
+ }
20
+
21
+ function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
22
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
23
+ }
24
+
25
+ function DialogOverlay({ className, ...props }: DialogPrimitive.Backdrop.Props) {
26
+ return (
27
+ <DialogPrimitive.Backdrop
28
+ data-slot="dialog-overlay"
29
+ className={cn(
30
+ "fixed inset-0 z-[120] bg-slate-950/55 backdrop-blur-[2px] transition-opacity duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0",
31
+ className
32
+ )}
33
+ {...props}
34
+ />
35
+ );
36
+ }
37
+
38
+ function DialogContent({
39
+ className,
40
+ children,
41
+ showCloseButton = true,
42
+ ...props
43
+ }: DialogPrimitive.Popup.Props & { showCloseButton?: boolean }) {
44
+ return (
45
+ <DialogPortal>
46
+ <DialogOverlay />
47
+ <DialogPrimitive.Viewport
48
+ data-slot="dialog-viewport"
49
+ className="fixed inset-0 z-[120] flex items-center justify-center p-4"
50
+ >
51
+ <DialogPrimitive.Popup
52
+ data-slot="dialog-content"
53
+ className={cn(
54
+ "relative grid max-h-[min(44rem,calc(100dvh-2rem))] w-full max-w-2xl gap-0 overflow-hidden rounded-2xl border bg-card text-card-foreground shadow-2xl outline-none transition-[opacity,transform] duration-200 data-ending-style:scale-[0.98] data-ending-style:opacity-0 data-starting-style:scale-[0.98] data-starting-style:opacity-0",
55
+ className
56
+ )}
57
+ {...props}
58
+ >
59
+ {children}
60
+ {showCloseButton && (
61
+ <DialogPrimitive.Close
62
+ data-slot="dialog-close"
63
+ className="absolute right-4 top-4 inline-flex size-8 items-center justify-center rounded-lg text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50"
64
+ aria-label="Close dialog"
65
+ >
66
+ <X className="size-4" aria-hidden="true" />
67
+ </DialogPrimitive.Close>
68
+ )}
69
+ </DialogPrimitive.Popup>
70
+ </DialogPrimitive.Viewport>
71
+ </DialogPortal>
72
+ );
73
+ }
74
+
75
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
76
+ return (
77
+ <div
78
+ data-slot="dialog-header"
79
+ className={cn("flex flex-col gap-1.5 border-b px-6 py-5 pr-14", className)}
80
+ {...props}
81
+ />
82
+ );
83
+ }
84
+
85
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
86
+ return (
87
+ <div
88
+ data-slot="dialog-footer"
89
+ className={cn(
90
+ "flex flex-col-reverse gap-2 border-t px-6 py-4 sm:flex-row sm:justify-end",
91
+ className
92
+ )}
93
+ {...props}
94
+ />
95
+ );
96
+ }
97
+
98
+ function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
99
+ return (
100
+ <DialogPrimitive.Title
101
+ data-slot="dialog-title"
102
+ className={cn("text-base font-semibold tracking-tight", className)}
103
+ {...props}
104
+ />
105
+ );
106
+ }
107
+
108
+ function DialogDescription({ className, ...props }: DialogPrimitive.Description.Props) {
109
+ return (
110
+ <DialogPrimitive.Description
111
+ data-slot="dialog-description"
112
+ className={cn("text-sm leading-5 text-muted-foreground", className)}
113
+ {...props}
114
+ />
115
+ );
116
+ }
117
+
118
+ export {
119
+ Dialog,
120
+ DialogClose,
121
+ DialogContent,
122
+ DialogDescription,
123
+ DialogFooter,
124
+ DialogHeader,
125
+ DialogOverlay,
126
+ DialogPortal,
127
+ DialogTitle,
128
+ DialogTrigger,
129
+ };