mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Use a task modal instead of a drawer (#103)
This commit is contained in:
parent
7c1abba38b
commit
849dccb3e9
8 changed files with 329 additions and 281 deletions
|
|
@ -19,17 +19,17 @@ export const Messages = ({ messages }: MessagesProps) => {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-6">
|
||||
{conversation.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={cn(
|
||||
'flex flex-col gap-2 rounded-lg p-3',
|
||||
'flex flex-col gap-3 rounded-lg p-4',
|
||||
message.role === 'user' ? 'bg-primary/10' : 'bg-secondary/10',
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-row items-center justify-between gap-1 text-xs font-medium text-muted-foreground">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex flex-row items-center justify-between gap-2 text-xs font-medium text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<div>{message.name}</div>
|
||||
<div>·</div>
|
||||
<div>{message.timestamp}</div>
|
||||
|
|
@ -40,7 +40,7 @@ export const Messages = ({ messages }: MessagesProps) => {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm markdown-prose">
|
||||
<div className="text-sm leading-relaxed markdown-prose">
|
||||
<ReactMarkdown>{message.text}</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,114 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
import type { TaskWithUser } from '@/actions/analytics';
|
||||
import { getMessages } from '@/actions/analytics';
|
||||
import { canShareTask } from '@/actions/taskSharing';
|
||||
import { useOrganizationSettings } from '@/hooks/useOrganizationSettings';
|
||||
import { formatCurrency, formatNumber } from '@/lib/formatters';
|
||||
import { generateFallbackTitle } from '@/lib/task-utils';
|
||||
import { QueryKey } from '@/types/react-query';
|
||||
import { Drawer, DrawerContent, Button } from '@/components/ui';
|
||||
import { ShareButton } from '@/components/task-sharing/ShareButton';
|
||||
|
||||
import { Status } from './Status';
|
||||
import { Messages } from './Messages';
|
||||
|
||||
type TaskDrawerProps = {
|
||||
task: TaskWithUser;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const TaskDrawer = ({ task, onClose }: TaskDrawerProps) => {
|
||||
const { data: messages } = useQuery({
|
||||
queryKey: ['messages', task.taskId],
|
||||
queryFn: () => getMessages(task.taskId),
|
||||
});
|
||||
|
||||
const { data: orgSettings } = useOrganizationSettings();
|
||||
|
||||
const { data: sharePermission } = useQuery({
|
||||
queryKey: [QueryKey.CanShareTask, task.taskId],
|
||||
queryFn: () => canShareTask(task.taskId),
|
||||
enabled: !!task.taskId,
|
||||
});
|
||||
|
||||
const isTaskSharingEnabled =
|
||||
orgSettings?.cloudSettings?.enableTaskSharing ?? false;
|
||||
|
||||
const canUserShareThisTask = sharePermission?.canShare ?? false;
|
||||
|
||||
return (
|
||||
<Drawer open={true} onOpenChange={onClose} direction="right">
|
||||
<DrawerContent className="flex flex-col h-full">
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="p-4">
|
||||
<div className="flex justify-end gap-2 mb-4">
|
||||
{isTaskSharingEnabled && canUserShareThisTask && (
|
||||
<ShareButton task={task} />
|
||||
)}
|
||||
<Button variant="ghost" size="sm" onClick={onClose}>
|
||||
<X className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<h2 className="text-foreground font-semibold line-clamp-2 mb-4">
|
||||
{task.title || generateFallbackTitle(task)}
|
||||
</h2>
|
||||
<div className="mb-6 space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Developer</span>
|
||||
<span className="max-w-64 truncate">{task.user.name}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Provider</span>
|
||||
<span className="max-w-64 truncate font-mono">
|
||||
{task.provider}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Model</span>
|
||||
<span className="max-w-64 truncate font-mono">
|
||||
{task.model}
|
||||
</span>
|
||||
</div>
|
||||
{task.mode && (
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Mode</span>
|
||||
<span className="max-w-64 truncate">{task.mode}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Tokens</span>
|
||||
<span className="max-w-64 truncate font-mono">
|
||||
{formatNumber(task.tokens)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Cost</span>
|
||||
<span className="max-w-64 truncate font-mono">
|
||||
{formatCurrency(task.cost)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Date</span>
|
||||
<span className="max-w-64 truncate">
|
||||
{new Date(task.timestamp * 1000).toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Status</span>
|
||||
<Status completed={task.completed} />
|
||||
</div>
|
||||
</div>
|
||||
{typeof messages !== 'undefined' && messages.length > 0 && (
|
||||
<>
|
||||
<h3 className="mb-2 text-lg font-medium">Conversation</h3>
|
||||
<Messages messages={messages} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
57
src/app/(authenticated)/usage/TaskModal.tsx
Normal file
57
src/app/(authenticated)/usage/TaskModal.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import type { TaskWithUser } from '@/actions/analytics';
|
||||
import { getMessages } from '@/actions/analytics';
|
||||
import { canShareTask } from '@/actions/taskSharing';
|
||||
import { useOrganizationSettings } from '@/hooks/useOrganizationSettings';
|
||||
import { QueryKey } from '@/types/react-query';
|
||||
import { Dialog, DialogContentLarge } from '@/components/ui';
|
||||
import { ShareButton } from '@/components/task-sharing/ShareButton';
|
||||
import { TaskDetails } from '@/components/task-sharing/TaskDetails';
|
||||
|
||||
type TaskModalProps = {
|
||||
task: TaskWithUser;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const TaskModal = ({ task, open, onClose }: TaskModalProps) => {
|
||||
const { data: messages = [] } = useQuery({
|
||||
queryKey: ['messages', task.taskId],
|
||||
queryFn: () => getMessages(task.taskId),
|
||||
enabled: open && !!task.taskId,
|
||||
});
|
||||
|
||||
const { data: orgSettings } = useOrganizationSettings();
|
||||
|
||||
const { data: sharePermission } = useQuery({
|
||||
queryKey: [QueryKey.CanShareTask, task.taskId],
|
||||
queryFn: () => canShareTask(task.taskId),
|
||||
enabled: open && !!task.taskId,
|
||||
});
|
||||
|
||||
const isTaskSharingEnabled =
|
||||
orgSettings?.cloudSettings?.enableTaskSharing ?? false;
|
||||
|
||||
const canUserShareThisTask = sharePermission?.canShare ?? false;
|
||||
|
||||
const headerActions = (
|
||||
<>
|
||||
{isTaskSharingEnabled && canUserShareThisTask && (
|
||||
<ShareButton task={task} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContentLarge>
|
||||
<TaskDetails
|
||||
task={task}
|
||||
messages={messages}
|
||||
headerActions={headerActions}
|
||||
/>
|
||||
</DialogContentLarge>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
@ -12,7 +12,7 @@ import { type Filter, type ViewMode, viewModes } from './types';
|
|||
import { Developers } from './Developers';
|
||||
import { Models } from './Models';
|
||||
import { Tasks } from './Tasks';
|
||||
import { TaskDrawer } from './TaskDrawer';
|
||||
import { TaskModal } from './TaskModal';
|
||||
|
||||
type UsageProps = {
|
||||
userRole?: 'admin' | 'member';
|
||||
|
|
@ -89,7 +89,9 @@ export const Usage = ({ userRole = 'admin', currentUserId }: UsageProps) => {
|
|||
<Models onFilter={onFilter} />
|
||||
)}
|
||||
</div>
|
||||
{task && <TaskDrawer task={task} onClose={() => setTask(null)} />}
|
||||
{task && (
|
||||
<TaskModal task={task} open={!!task} onClose={() => setTask(null)} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
import type { TaskWithUser, Message } from '@/actions/analytics';
|
||||
import type { SharedByUser } from '@/types/task-sharing';
|
||||
import { formatCurrency, formatNumber } from '@/lib/formatters';
|
||||
import { generateFallbackTitle } from '@/lib/task-utils';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui';
|
||||
import { Status } from '@/app/(authenticated)/usage/Status';
|
||||
import { Messages } from '@/app/(authenticated)/usage/Messages';
|
||||
import { TaskDetails } from './TaskDetails';
|
||||
|
||||
type SharedTaskViewProps = {
|
||||
task: TaskWithUser;
|
||||
|
|
@ -19,75 +15,13 @@ export const SharedTaskView = ({
|
|||
sharedBy,
|
||||
sharedAt,
|
||||
}: SharedTaskViewProps) => {
|
||||
const taskTitle = task.title || generateFallbackTitle(task);
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
{/* Task Header */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-xl line-clamp-2 leading-tight">
|
||||
{taskTitle}
|
||||
</CardTitle>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Shared by {sharedBy.name} • {sharedAt.toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Status completed={task.completed} />
|
||||
<span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full">
|
||||
Shared
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 text-sm">
|
||||
<div>
|
||||
<p className="text-muted-foreground">Developer</p>
|
||||
<p className="font-mono">{task.user.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground">Model</p>
|
||||
<p className="font-mono">{task.model}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground">Provider</p>
|
||||
<p className="font-mono">{task.provider}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground">Tokens</p>
|
||||
<p className="font-mono">{formatNumber(task.tokens)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground">Cost</p>
|
||||
<p className="font-mono">{formatCurrency(task.cost)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Conversation */}
|
||||
{messages.length > 0 ? (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Conversation</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Messages messages={messages} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<Card>
|
||||
<CardContent className="text-center py-12">
|
||||
<p className="text-muted-foreground">
|
||||
No conversation messages are available for this task.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
<TaskDetails
|
||||
task={task}
|
||||
messages={messages}
|
||||
sharedBy={sharedBy}
|
||||
sharedAt={sharedAt}
|
||||
showSharedInfo={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
116
src/components/task-sharing/TaskDetails.tsx
Normal file
116
src/components/task-sharing/TaskDetails.tsx
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import type { TaskWithUser, Message } from '@/actions/analytics';
|
||||
import type { SharedByUser } from '@/types/task-sharing';
|
||||
import { formatCurrency, formatNumber } from '@/lib/formatters';
|
||||
import { generateFallbackTitle } from '@/lib/task-utils';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui';
|
||||
import { Status } from '@/app/(authenticated)/usage/Status';
|
||||
import { Messages } from '@/app/(authenticated)/usage/Messages';
|
||||
|
||||
type TaskDetailsProps = {
|
||||
task: TaskWithUser;
|
||||
messages: Message[];
|
||||
sharedBy?: SharedByUser;
|
||||
sharedAt?: Date;
|
||||
showSharedInfo?: boolean;
|
||||
headerActions?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const TaskDetails = ({
|
||||
task,
|
||||
messages,
|
||||
sharedBy,
|
||||
sharedAt,
|
||||
showSharedInfo = false,
|
||||
headerActions,
|
||||
}: TaskDetailsProps) => {
|
||||
const taskTitle = task.title || generateFallbackTitle(task);
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
{/* Task Header */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1 min-w-0">
|
||||
<CardTitle className="text-xl line-clamp-2 leading-tight">
|
||||
{taskTitle}
|
||||
</CardTitle>
|
||||
{showSharedInfo && sharedBy && sharedAt && (
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Shared by {sharedBy.name} • {sharedAt.toLocaleDateString()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-4">
|
||||
<Status completed={task.completed} />
|
||||
{showSharedInfo && (
|
||||
<span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full">
|
||||
Shared
|
||||
</span>
|
||||
)}
|
||||
{headerActions}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 text-sm">
|
||||
<div>
|
||||
<p className="text-muted-foreground">Developer</p>
|
||||
<p className="font-mono truncate">{task.user.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground">Model</p>
|
||||
<p className="font-mono truncate">{task.model}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground">Provider</p>
|
||||
<p className="font-mono truncate">{task.provider}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground">Tokens</p>
|
||||
<p className="font-mono">{formatNumber(task.tokens)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-muted-foreground">Cost</p>
|
||||
<p className="font-mono">{formatCurrency(task.cost)}</p>
|
||||
</div>
|
||||
</div>
|
||||
{task.mode && (
|
||||
<div className="mt-4 pt-4 border-t">
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span className="text-muted-foreground">Mode</span>
|
||||
<span className="font-mono">{task.mode}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-4 pt-4 border-t">
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span className="text-muted-foreground">Date</span>
|
||||
<span>{new Date(task.timestamp * 1000).toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Conversation */}
|
||||
{messages.length > 0 ? (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Conversation</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Messages messages={messages} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<Card>
|
||||
<CardContent className="text-center py-12">
|
||||
<p className="text-muted-foreground">
|
||||
No conversation messages are available for this task.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
export * from './ShareButton';
|
||||
export * from './SharedTaskView';
|
||||
export * from './TaskDetails';
|
||||
|
|
|
|||
|
|
@ -1,110 +1,162 @@
|
|||
"use client"
|
||||
'use client';
|
||||
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { XIcon } from "lucide-react"
|
||||
import * as React from 'react';
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { XIcon } from 'lucide-react';
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
||||
function Dialog({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
||||
}
|
||||
|
||||
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
||||
function DialogTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
||||
function DialogPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
||||
}
|
||||
|
||||
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
||||
function DialogClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
||||
}
|
||||
|
||||
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
||||
return (
|
||||
<DialogPrimitive.Overlay
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
function DialogOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
||||
return (
|
||||
<DialogPrimitive.Overlay
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogContent({ className, children, ...props }: React.ComponentProps<typeof DialogPrimitive.Content>) {
|
||||
return (
|
||||
<DialogPortal data-slot="dialog-portal">
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
"bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
|
||||
<XIcon />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
)
|
||||
function DialogContent({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
|
||||
return (
|
||||
<DialogPortal data-slot="dialog-portal">
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
'bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
|
||||
<XIcon />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-header"
|
||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
function DialogContentLarge({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
|
||||
return (
|
||||
<DialogPortal data-slot="dialog-portal">
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
'bg-background 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 fixed top-[50%] left-[50%] z-50 flex flex-col w-full max-w-[calc(100%-2rem)] max-h-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] rounded-lg border shadow-lg duration-200 sm:max-w-4xl',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 z-10 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
|
||||
<XIcon />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
<div className="flex-1 overflow-y-auto p-6">{children}</div>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-footer"
|
||||
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-header"
|
||||
className={cn('flex flex-col gap-2 text-center sm:text-left', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
data-slot="dialog-title"
|
||||
className={cn("text-lg leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-footer"
|
||||
className={cn(
|
||||
'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||
return (
|
||||
<DialogPrimitive.Description
|
||||
data-slot="dialog-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
function DialogTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
data-slot="dialog-title"
|
||||
className={cn('text-lg leading-none font-semibold', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||
return (
|
||||
<DialogPrimitive.Description
|
||||
data-slot="dialog-description"
|
||||
className={cn('text-muted-foreground text-sm', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
}
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogContentLarge,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue