Files
stack/apps/web/src/components/ui/dialog.tsx
Jason Woltje 6a5a4e4de8 feat(web): add credential management UI pages and components
Add credentials settings page, audit log page, CRUD dialog components
(create, view, edit, rotate), credential card, dialog UI component,
and API client for the M7-CredentialSecurity feature.

Refs #346

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 09:42:41 -06:00

129 lines
3.6 KiB
TypeScript

import * as React from "react";
import { X } from "lucide-react";
export interface DialogProps {
open?: boolean;
onOpenChange?: (open: boolean) => void;
children?: React.ReactNode;
}
export interface DialogTriggerProps {
children?: React.ReactNode;
asChild?: boolean;
}
export interface DialogContentProps {
children?: React.ReactNode;
className?: string;
}
export interface DialogHeaderProps {
children?: React.ReactNode;
className?: string;
}
export interface DialogFooterProps {
children?: React.ReactNode;
className?: string;
}
export interface DialogTitleProps {
children?: React.ReactNode;
className?: string;
}
export interface DialogDescriptionProps {
children?: React.ReactNode;
className?: string;
}
const DialogContext = React.createContext<{
open?: boolean;
onOpenChange?: (open: boolean) => void;
}>({});
export function Dialog({ open, onOpenChange, children }: DialogProps): React.JSX.Element {
const contextValue: { open?: boolean; onOpenChange?: (open: boolean) => void } = {};
if (open !== undefined) {
contextValue.open = open;
}
if (onOpenChange !== undefined) {
contextValue.onOpenChange = onOpenChange;
}
return <DialogContext.Provider value={contextValue}>{children}</DialogContext.Provider>;
}
export function DialogTrigger({ children, asChild }: DialogTriggerProps): React.JSX.Element {
const { onOpenChange } = React.useContext(DialogContext);
if (asChild && React.isValidElement(children)) {
return React.cloneElement(children, {
onClick: () => onOpenChange?.(true),
} as React.HTMLAttributes<HTMLElement>);
}
return <div onClick={() => onOpenChange?.(true)}>{children}</div>;
}
export function DialogContent({
children,
className = "",
}: DialogContentProps): React.JSX.Element | null {
const { open, onOpenChange } = React.useContext(DialogContext);
if (!open) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Overlay */}
<div className="fixed inset-0 bg-black/50" onClick={() => onOpenChange?.(false)} />
{/* Dialog Content */}
<div
className={`relative z-50 w-full max-w-lg rounded-lg bg-white p-6 shadow-lg ${className}`}
>
{/* Close Button */}
<button
onClick={() => onOpenChange?.(false)}
className="absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100 transition-opacity"
>
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</button>
{children}
</div>
</div>
);
}
export function DialogHeader({ children, className = "" }: DialogHeaderProps): React.JSX.Element {
return <div className={`mb-4 ${className}`}>{children}</div>;
}
export function DialogFooter({ children, className = "" }: DialogFooterProps): React.JSX.Element {
return <div className={`mt-4 flex justify-end gap-2 ${className}`}>{children}</div>;
}
export function DialogTitle({ children, className = "" }: DialogTitleProps): React.JSX.Element {
return <h2 className={`text-lg font-semibold ${className}`}>{children}</h2>;
}
export function DialogDescription({
children,
className = "",
}: DialogDescriptionProps): React.JSX.Element {
return <p className={`text-sm text-gray-600 ${className}`}>{children}</p>;
}
export const DialogPortal = ({ children }: { children: React.ReactNode }): React.JSX.Element => (
<>{children}</>
);
export const DialogOverlay = ({ children }: { children: React.ReactNode }): React.JSX.Element => (
<>{children}</>
);
export const DialogClose = ({ children }: { children: React.ReactNode }): React.JSX.Element => (
<>{children}</>
);