Skip to content

Commit 2e01852

Browse files
author
catlog22
committed
feat: enhance dialog and drawer components with new styles and functionality
- Updated Dialog component to support fullscreen mode and added a back button. - Introduced Drawer component for side navigation with customizable size and position. - Added DialogStyleContext for managing dialog style preferences including smart mode and drawer settings. - Implemented pending question service for managing persistent storage of pending questions. - Enhanced WebSocket handling to request pending questions upon frontend readiness. - Created dashboard launcher utility to manage the Dashboard server lifecycle.
1 parent 4f08524 commit 2e01852

8 files changed

Lines changed: 1100 additions & 46 deletions

File tree

ccw/frontend/src/components/ui/Dialog.tsx

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from "react";
22
import * as DialogPrimitive from "@radix-ui/react-dialog";
3-
import { X } from "lucide-react";
3+
import { ArrowLeft, X } from "lucide-react";
44
import { cn } from "@/lib/utils";
55

66
const Dialog = DialogPrimitive.Root;
@@ -28,26 +28,51 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
2828

2929
const DialogContent = React.forwardRef<
3030
React.ElementRef<typeof DialogPrimitive.Content>,
31-
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
32-
>(({ className, children, ...props }, ref) => (
33-
<DialogPortal>
34-
<DialogOverlay />
35-
<DialogPrimitive.Content
36-
ref={ref}
37-
className={cn(
38-
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-card p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
39-
className
40-
)}
41-
{...props}
42-
>
43-
{children}
44-
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
45-
<X className="h-4 w-4" />
46-
<span className="sr-only">Close</span>
47-
</DialogPrimitive.Close>
48-
</DialogPrimitive.Content>
49-
</DialogPortal>
50-
));
31+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
32+
fullscreen?: boolean;
33+
}
34+
>(({ className, children, fullscreen = false, ...props }, ref) => {
35+
if (fullscreen) {
36+
return (
37+
<DialogPortal>
38+
<DialogPrimitive.Content
39+
ref={ref}
40+
className={cn(
41+
"fixed inset-0 z-50 flex flex-col bg-card duration-300 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
42+
className
43+
)}
44+
{...props}
45+
>
46+
{children}
47+
<DialogPrimitive.Close className="absolute left-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
48+
<ArrowLeft className="h-5 w-5" />
49+
<span className="sr-only">Back</span>
50+
</DialogPrimitive.Close>
51+
</DialogPrimitive.Content>
52+
</DialogPortal>
53+
);
54+
}
55+
56+
return (
57+
<DialogPortal>
58+
<DialogOverlay />
59+
<DialogPrimitive.Content
60+
ref={ref}
61+
className={cn(
62+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-card p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
63+
className
64+
)}
65+
{...props}
66+
>
67+
{children}
68+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
69+
<X className="h-4 w-4" />
70+
<span className="sr-only">Close</span>
71+
</DialogPrimitive.Close>
72+
</DialogPrimitive.Content>
73+
</DialogPortal>
74+
);
75+
});
5176
DialogContent.displayName = DialogPrimitive.Content.displayName;
5277

5378
const DialogHeader = ({
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// ========================================
2+
// Drawer Component
3+
// ========================================
4+
// Side drawer for A2UI surfaces with slide animation
5+
// Supports left/right positioning and multiple sizes
6+
7+
import * as React from 'react';
8+
import * as DialogPrimitive from '@radix-ui/react-dialog';
9+
import { X } from 'lucide-react';
10+
import { cva, type VariantProps } from 'class-variance-authority';
11+
import { cn } from '@/lib/utils';
12+
13+
// ========== Variants ==========
14+
15+
const drawerVariants = cva(
16+
'fixed z-50 gap-4 bg-card p-6 shadow-lg border-border ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
17+
{
18+
variants: {
19+
side: {
20+
left: 'inset-y-0 left-0 h-full border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left',
21+
right: 'inset-y-0 right-0 h-full border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right',
22+
},
23+
size: {
24+
sm: 'w-80',
25+
md: 'w-96',
26+
lg: 'w-[540px]',
27+
xl: 'w-[720px]',
28+
full: 'w-full',
29+
},
30+
},
31+
defaultVariants: {
32+
side: 'right',
33+
size: 'md',
34+
},
35+
}
36+
);
37+
38+
// ========== Root Components ==========
39+
40+
const Drawer = DialogPrimitive.Root;
41+
const DrawerTrigger = DialogPrimitive.Trigger;
42+
const DrawerClose = DialogPrimitive.Close;
43+
const DrawerPortal = DialogPrimitive.Portal;
44+
45+
// ========== Overlay ==========
46+
47+
const DrawerOverlay = React.forwardRef<
48+
React.ElementRef<typeof DialogPrimitive.Overlay>,
49+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
50+
>(({ className, ...props }, ref) => (
51+
<DialogPrimitive.Overlay
52+
ref={ref}
53+
className={cn(
54+
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
55+
className
56+
)}
57+
{...props}
58+
/>
59+
));
60+
DrawerOverlay.displayName = DialogPrimitive.Overlay.displayName;
61+
62+
// ========== Content ==========
63+
64+
interface DrawerContentProps
65+
extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,
66+
VariantProps<typeof drawerVariants> {
67+
/** Whether to show the close button */
68+
showClose?: boolean;
69+
/** Whether clicking outside should close the drawer */
70+
closeOnOutsideClick?: boolean;
71+
}
72+
73+
const DrawerContent = React.forwardRef<
74+
React.ElementRef<typeof DialogPrimitive.Content>,
75+
DrawerContentProps
76+
>(
77+
(
78+
{ side = 'right', size = 'md', showClose = true, closeOnOutsideClick = true, className, children, ...props },
79+
ref
80+
) => (
81+
<DrawerPortal>
82+
<DrawerOverlay />
83+
<DialogPrimitive.Content
84+
ref={ref}
85+
className={cn(drawerVariants({ side, size }), className)}
86+
onInteractOutside={(e) => {
87+
if (!closeOnOutsideClick) {
88+
e.preventDefault();
89+
}
90+
}}
91+
{...props}
92+
>
93+
{showClose && (
94+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
95+
<X className="h-4 w-4" />
96+
<span className="sr-only">Close</span>
97+
</DialogPrimitive.Close>
98+
)}
99+
{children}
100+
</DialogPrimitive.Content>
101+
</DrawerPortal>
102+
)
103+
);
104+
DrawerContent.displayName = DialogPrimitive.Content.displayName;
105+
106+
// ========== Header ==========
107+
108+
const DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
109+
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
110+
);
111+
DrawerHeader.displayName = 'DrawerHeader';
112+
113+
// ========== Footer ==========
114+
115+
const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
116+
<div
117+
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
118+
{...props}
119+
/>
120+
);
121+
DrawerFooter.displayName = 'DrawerFooter';
122+
123+
// ========== Title ==========
124+
125+
const DrawerTitle = React.forwardRef<
126+
React.ElementRef<typeof DialogPrimitive.Title>,
127+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
128+
>(({ className, ...props }, ref) => (
129+
<DialogPrimitive.Title
130+
ref={ref}
131+
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
132+
{...props}
133+
/>
134+
));
135+
DrawerTitle.displayName = DialogPrimitive.Title.displayName;
136+
137+
// ========== Description ==========
138+
139+
const DrawerDescription = React.forwardRef<
140+
React.ElementRef<typeof DialogPrimitive.Description>,
141+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
142+
>(({ className, ...props }, ref) => (
143+
<DialogPrimitive.Description
144+
ref={ref}
145+
className={cn('text-sm text-muted-foreground', className)}
146+
{...props}
147+
/>
148+
));
149+
DrawerDescription.displayName = DialogPrimitive.Description.displayName;
150+
151+
// ========== Exports ==========
152+
153+
export {
154+
Drawer,
155+
DrawerPortal,
156+
DrawerOverlay,
157+
DrawerTrigger,
158+
DrawerClose,
159+
DrawerContent,
160+
DrawerHeader,
161+
DrawerFooter,
162+
DrawerTitle,
163+
DrawerDescription,
164+
type DrawerContentProps,
165+
};

0 commit comments

Comments
 (0)