diff --git a/frontend/src/components/files/DeleteFileConfirm.tsx b/frontend/src/components/files/DeleteFileConfirm.tsx index cbd827c6..111b7e05 100644 --- a/frontend/src/components/files/DeleteFileConfirm.tsx +++ b/frontend/src/components/files/DeleteFileConfirm.tsx @@ -1,13 +1,6 @@ import { useState, useEffect } from 'react'; -import { AlertTriangle, Loader2, Trash2 } from 'lucide-react'; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription, - DialogFooter, -} from '@/components/ui/dialog'; +import { AlertTriangle, Loader2 } from 'lucide-react'; +import { Modal, ModalDestructiveHeader, ModalBody, ModalFooter } from '@/components/ui/modal'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; @@ -75,25 +68,30 @@ export function DeleteFileConfirm({ const deleteLabel = notEmpty ? 'Delete all' : 'Delete'; + const titleNode = entryName ? ( + <> + Delete {entryName}? + > + ) : ( + 'Delete item?' + ); + return ( - - - - - - Delete {entryName ? `"${entryName}"` : 'item'}? - - - {notEmpty ? ( - - - This folder is not empty. Delete everything inside? - - ) : ( - 'This action cannot be undone.' - )} - - + + + + {notEmpty ? ( + + + This folder is not empty. Delete everything inside? + + ) : ( + This action cannot be undone. + )} {isProtected && ( @@ -116,8 +114,10 @@ export function DeleteFileConfirm({ )} - - + + Cancel + } + primary={ @@ -138,8 +140,8 @@ export function DeleteFileConfirm({ )} {deleteLabel} - - - + } + /> + ); } diff --git a/frontend/src/components/files/NewFolderDialog.tsx b/frontend/src/components/files/NewFolderDialog.tsx index 4202c708..cab33124 100644 --- a/frontend/src/components/files/NewFolderDialog.tsx +++ b/frontend/src/components/files/NewFolderDialog.tsx @@ -1,13 +1,6 @@ import { useState } from 'react'; -import { FolderPlus, Loader2 } from 'lucide-react'; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription, - DialogFooter, -} from '@/components/ui/dialog'; +import { Loader2 } from 'lucide-react'; +import { Modal, ModalHeader, ModalBody, ModalFooter } from '@/components/ui/modal'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; @@ -73,19 +66,16 @@ export function NewFolderDialog({ if (e.key === 'Enter') void handleCreate(); }; - return ( - - - - - - New Folder - - - Enter a name for the new folder. - - + const parentLabel = currentDir || stackName; + return ( + + + Folder name {validationError} )} - - + + Cancel + } + primary={ void handleCreate()} @@ -124,8 +119,8 @@ export function NewFolderDialog({ )} Create - - - + } + /> + ); } diff --git a/frontend/src/components/ui/modal.tsx b/frontend/src/components/ui/modal.tsx new file mode 100644 index 00000000..b6f41aee --- /dev/null +++ b/frontend/src/components/ui/modal.tsx @@ -0,0 +1,122 @@ +import * as React from 'react'; +import { cn } from '@/lib/utils'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogTitle, +} from '@/components/ui/dialog'; + +const KICKER_CLASS = 'font-mono text-[10px] uppercase tracking-[0.22em]'; + +type ModalSize = 'sm' | 'md' | 'lg' | 'xl'; + +const SIZE_CLASS: Record = { + sm: 'max-w-sm', + md: 'max-w-md', + lg: 'max-w-lg', + xl: 'max-w-xl w-[95vw]', +}; + +interface ModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + children: React.ReactNode; + size?: ModalSize; + className?: string; +} + +export function Modal({ open, onOpenChange, children, size = 'md', className }: ModalProps) { + return ( + + + {children} + + + ); +} + +interface ModalHeaderBaseProps { + kicker: string; + title: React.ReactNode; + description?: string; +} + +function HeaderShell({ + kicker, + title, + description, + railClassName, + kickerClassName, +}: ModalHeaderBaseProps & { railClassName: string; kickerClassName: string }) { + return ( + + + + {kicker} + + + {title} + + + {description ?? (typeof title === 'string' ? title : kicker)} + + + ); +} + +export function ModalHeader(props: ModalHeaderBaseProps) { + return ( + + ); +} + +export function ModalDestructiveHeader(props: ModalHeaderBaseProps) { + return ( + + ); +} + +export function ModalBody({ className, ...props }: React.HTMLAttributes) { + return ; +} + +interface ModalFooterProps { + primary: React.ReactNode; + secondary?: React.ReactNode; + hint?: React.ReactNode; + hintAccent?: React.ReactNode; +} + +export function ModalFooter({ primary, secondary, hint, hintAccent }: ModalFooterProps) { + return ( + + + {hint} + {hintAccent !== undefined && ( + + {hintAccent} + + )} + + + {secondary} + {primary} + + + ); +}
This action cannot be undone.