Files
stack/apps/web/src/components/ui/sheet.tsx
Jason Woltje 4792f7b70a
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
feat: MS23-P2-007 AuditLogDrawer + audit log endpoint
2026-03-07 14:40:06 -06:00

138 lines
3.5 KiB
TypeScript

import * as React from "react";
import { X } from "lucide-react";
export interface SheetProps {
open?: boolean;
onOpenChange?: (open: boolean) => void;
children?: React.ReactNode;
}
export interface SheetTriggerProps {
children?: React.ReactNode;
asChild?: boolean;
}
export interface SheetContentProps {
children?: React.ReactNode;
className?: string;
}
export interface SheetHeaderProps {
children?: React.ReactNode;
className?: string;
}
export interface SheetTitleProps {
children?: React.ReactNode;
className?: string;
}
export interface SheetDescriptionProps {
children?: React.ReactNode;
className?: string;
}
const SheetContext = React.createContext<{
open?: boolean;
onOpenChange?: (open: boolean) => void;
}>({});
export function Sheet({ open, onOpenChange, children }: SheetProps): React.JSX.Element {
const contextValue: { open?: boolean; onOpenChange?: (open: boolean) => void } = {};
if (open !== undefined) {
contextValue.open = open;
}
if (onOpenChange !== undefined) {
contextValue.onOpenChange = onOpenChange;
}
return <SheetContext.Provider value={contextValue}>{children}</SheetContext.Provider>;
}
export function SheetTrigger({ children, asChild }: SheetTriggerProps): React.JSX.Element {
const { onOpenChange } = React.useContext(SheetContext);
if (asChild && React.isValidElement(children)) {
return React.cloneElement(children, {
onClick: () => onOpenChange?.(true),
} as React.HTMLAttributes<HTMLElement>);
}
return (
<button type="button" onClick={() => onOpenChange?.(true)}>
{children}
</button>
);
}
export function SheetContent({
children,
className = "",
}: SheetContentProps): React.JSX.Element | null {
const { open, onOpenChange } = React.useContext(SheetContext);
React.useEffect(() => {
if (!open) {
return;
}
const onKeyDown = (event: KeyboardEvent): void => {
if (event.key === "Escape") {
onOpenChange?.(false);
}
};
window.addEventListener("keydown", onKeyDown);
return (): void => {
window.removeEventListener("keydown", onKeyDown);
};
}, [onOpenChange, open]);
if (!open) {
return null;
}
return (
<div className="fixed inset-0 z-50">
<button
type="button"
aria-label="Close sheet"
className="absolute inset-0 h-full w-full bg-black/50"
onClick={() => onOpenChange?.(false)}
/>
<div
className={`absolute inset-y-0 right-0 z-10 flex h-full w-full max-w-3xl flex-col border-l border-border bg-background p-6 shadow-xl ${className}`}
>
<button
type="button"
onClick={() => onOpenChange?.(false)}
className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100"
>
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</button>
{children}
</div>
</div>
);
}
export function SheetHeader({ children, className = "" }: SheetHeaderProps): React.JSX.Element {
return <div className={`space-y-1 pr-8 ${className}`}>{children}</div>;
}
export function SheetTitle({ children, className = "" }: SheetTitleProps): React.JSX.Element {
return <h2 className={`text-lg font-semibold ${className}`}>{children}</h2>;
}
export function SheetDescription({
children,
className = "",
}: SheetDescriptionProps): React.JSX.Element {
return <p className={`text-sm text-muted-foreground ${className}`}>{children}</p>;
}