mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-26 06:11:54 +03:00
Some checks failed
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
* remove redundant per-view toasters in settings * add variants to standardize dialog footer button layouts * remove text-md this class name compiles to nothing in tailwind. we used to add it to prevent iOS from zooming when focusing on an input, but that is now solved via the viewport meta in index.html * make wizard footers consistent with dialog footers * consistent destructive button style remove text-white from individual buttons and add it to the variant
178 lines
5.6 KiB
TypeScript
178 lines
5.6 KiB
TypeScript
import * as React from "react";
|
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
import { X } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import { useHistoryBack } from "@/hooks/use-history-back";
|
|
|
|
// Enhanced Dialog with History Support
|
|
interface HistoryDialogProps extends DialogPrimitive.DialogProps {
|
|
enableHistoryBack?: boolean;
|
|
}
|
|
|
|
const Dialog = ({
|
|
enableHistoryBack = false,
|
|
open,
|
|
onOpenChange,
|
|
...props
|
|
}: HistoryDialogProps) => {
|
|
const [internalOpen, setInternalOpen] = React.useState(open || false);
|
|
|
|
// Sync internal state with controlled open prop
|
|
React.useEffect(() => {
|
|
if (open !== undefined) {
|
|
setInternalOpen(open);
|
|
}
|
|
}, [open]);
|
|
|
|
const handleOpenChange = React.useCallback(
|
|
(newOpen: boolean) => {
|
|
setInternalOpen(newOpen);
|
|
onOpenChange?.(newOpen);
|
|
},
|
|
[onOpenChange],
|
|
);
|
|
|
|
// Handle browser back button to close dialog
|
|
useHistoryBack({
|
|
enabled: enableHistoryBack,
|
|
open: internalOpen,
|
|
onClose: () => handleOpenChange(false),
|
|
});
|
|
|
|
return (
|
|
<DialogPrimitive.Root
|
|
{...props}
|
|
open={internalOpen}
|
|
onOpenChange={handleOpenChange}
|
|
/>
|
|
);
|
|
};
|
|
|
|
Dialog.displayName = "Dialog";
|
|
|
|
const DialogTrigger = DialogPrimitive.Trigger;
|
|
const DialogPortal = DialogPrimitive.Portal;
|
|
const DialogClose = DialogPrimitive.Close;
|
|
|
|
const DialogOverlay = React.forwardRef<
|
|
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
>(({ className, ...props }, ref) => (
|
|
<DialogPrimitive.Overlay
|
|
ref={ref}
|
|
className={cn(
|
|
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
className,
|
|
)}
|
|
{...props}
|
|
/>
|
|
));
|
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
|
|
const DialogContent = React.forwardRef<
|
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
>(({ className, children, ...props }, ref) => (
|
|
<DialogPortal>
|
|
<DialogOverlay />
|
|
<DialogPrimitive.Content
|
|
ref={ref}
|
|
className={cn(
|
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background 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",
|
|
className,
|
|
)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
|
|
<X className="h-4 w-4 text-secondary-foreground" />
|
|
<span className="sr-only">Close</span>
|
|
</DialogPrimitive.Close>
|
|
</DialogPrimitive.Content>
|
|
</DialogPortal>
|
|
));
|
|
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
|
|
|
const DialogHeader = ({
|
|
className,
|
|
...props
|
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
<div
|
|
className={cn(
|
|
"flex flex-col space-y-1.5 text-center sm:text-left",
|
|
className,
|
|
)}
|
|
{...props}
|
|
/>
|
|
);
|
|
DialogHeader.displayName = "DialogHeader";
|
|
|
|
const dialogFooterVariants = cva("flex flex-col-reverse gap-2 sm:flex-row", {
|
|
variants: {
|
|
variant: {
|
|
// 1-2 action buttons: full-width stacked on mobile, right-aligned auto on desktop.
|
|
// [&>button] only targets real button children, so non-button siblings are untouched.
|
|
actions: "sm:justify-end [&>button]:w-full sm:[&>button]:w-auto",
|
|
// context content (text/popover) alongside actions: space-between on desktop.
|
|
// flex-col (not -reverse) keeps the context above the buttons when stacked on mobile.
|
|
split: "flex-col sm:items-center sm:justify-between",
|
|
// alignment only; never touches children. Escape hatch for unusual content.
|
|
plain: "sm:justify-end",
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
variant: "actions",
|
|
},
|
|
});
|
|
|
|
const DialogFooter = ({
|
|
className,
|
|
variant,
|
|
...props
|
|
}: React.HTMLAttributes<HTMLDivElement> &
|
|
VariantProps<typeof dialogFooterVariants>) => (
|
|
<div
|
|
className={cn(dialogFooterVariants({ variant }), className)}
|
|
{...props}
|
|
/>
|
|
);
|
|
DialogFooter.displayName = "DialogFooter";
|
|
|
|
const DialogTitle = React.forwardRef<
|
|
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
>(({ className, ...props }, ref) => (
|
|
<DialogPrimitive.Title
|
|
ref={ref}
|
|
className={cn("text-lg font-semibold leading-none", className)}
|
|
{...props}
|
|
/>
|
|
));
|
|
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
|
|
|
const DialogDescription = React.forwardRef<
|
|
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
>(({ className, ...props }, ref) => (
|
|
<DialogPrimitive.Description
|
|
ref={ref}
|
|
className={cn("text-sm text-muted-foreground", className)}
|
|
{...props}
|
|
/>
|
|
));
|
|
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
|
|
|
export {
|
|
Dialog,
|
|
DialogPortal,
|
|
DialogOverlay,
|
|
DialogClose,
|
|
DialogTrigger,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogFooter,
|
|
DialogTitle,
|
|
DialogDescription,
|
|
};
|