mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 07:36:38 +08:00
7668e24f4b
* docs: add design spec for iOS 26 Liquid Glass UI redesign * docs: add implementation plan for iOS 26 Liquid Glass UI redesign * feat(ui): add liquid glass CSS variables and update root layouts * feat(ui): update Card and Modal components to iOS 26 Liquid Glass style * feat(ui): redesign dashboard page to Liquid Glass style * feat(ui): redesign node management page cards and charts * feat(ui): redesign monitor page to Liquid Glass style * feat(ui): redesign group and panel-sharing cards to Liquid Glass * feat(ui): redesign settings and user components to Liquid Glass * feat(ui): redesign profile and auth pages to Liquid Glass * fix(ui): revert to LineChart to fix compilation issues * refactor(ui): extract dashboard user header and cleanup admin layout * feat(ui): refine monitor page hero metrics and fix build * fix(ui): real data for monitor hero metrics and card padding adjustments * fix(ui): apply global card padding and fix index layouts * feat(ui): implement liquid-glass-react for all Card components * fix(ui): resolve liquid-glass-react rendering issues causing invisible text and broken layout * fix(ui): native CSS liquid-glass effect replacing NPM package * fix(ui): cleanup duplicate closing tags and complete liquid glass tables * fix(ui): apply perfect rounded corners to list views and table headers in non-compact mode * fix(ui): apply liquid glass styling to grouped rules view in forward page * fix(ui): re-implement liquid-glass natively to prevent text distortion and remove package dependency * fix(ui): remove mouse distortion and preserve proper liquid glass texture * fix(ui): remove svg filter causing text distortion and replace with pure css liquid glass * fix(ui): apply properly scoped border radius to table headers * fix(ui): remove hardcoded bg-default-50/60 from TableHeader * fix(ui): apply global liquid glass styling and transparent overlays to all modals and dialogs * fix(ui): remove absolute/relative class conflicts hiding Dialog modales and implement pure transparent layout * feat(ui): apply global liquid glass styling to cards, buttons, and diagnosis modals
219 lines
4.7 KiB
TypeScript
219 lines
4.7 KiB
TypeScript
import * as React from "react";
|
|
|
|
import {
|
|
Dialog,
|
|
DialogContent as BaseDialogContent,
|
|
} from "@/components/ui/dialog";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface DisclosureOptions {
|
|
isOpen?: boolean;
|
|
}
|
|
|
|
type OpenChangeHandler = (open?: boolean) => void;
|
|
|
|
export function useDisclosure(options: DisclosureOptions = {}) {
|
|
const [isOpen, setIsOpen] = React.useState(Boolean(options.isOpen));
|
|
|
|
const onOpen = React.useCallback(() => {
|
|
setIsOpen(true);
|
|
}, []);
|
|
|
|
const onClose = React.useCallback(() => {
|
|
setIsOpen(false);
|
|
}, []);
|
|
|
|
const onOpenChange = React.useCallback<OpenChangeHandler>((open) => {
|
|
setIsOpen((prev) => (typeof open === "boolean" ? open : !prev));
|
|
}, []);
|
|
|
|
return {
|
|
isOpen,
|
|
onClose,
|
|
onOpen,
|
|
onOpenChange,
|
|
};
|
|
}
|
|
|
|
interface ModalContextValue {
|
|
classNames?: Record<string, string>;
|
|
onClose: () => void;
|
|
scrollBehavior?: "inside" | "outside";
|
|
size?: ModalSize;
|
|
}
|
|
|
|
const ModalContext = React.createContext<ModalContextValue | null>(null);
|
|
|
|
function useModalContext() {
|
|
return React.useContext(ModalContext);
|
|
}
|
|
|
|
type ModalSize = "sm" | "md" | "lg" | "xl" | "2xl" | "4xl" | "full";
|
|
|
|
function mapSize(size: ModalSize | undefined) {
|
|
if (size === "sm") {
|
|
return "max-w-md";
|
|
}
|
|
if (size === "lg") {
|
|
return "max-w-2xl";
|
|
}
|
|
if (size === "xl") {
|
|
return "max-w-3xl";
|
|
}
|
|
if (size === "2xl") {
|
|
return "max-w-5xl";
|
|
}
|
|
if (size === "4xl") {
|
|
return "max-w-6xl";
|
|
}
|
|
if (size === "full") {
|
|
return "w-[95vw] max-w-[95vw] h-[95vh] max-h-[95vh]";
|
|
}
|
|
|
|
return "max-w-lg";
|
|
}
|
|
|
|
export interface ModalProps {
|
|
backdrop?: "blur" | "opaque" | "transparent";
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
classNames?: Record<string, string>;
|
|
isDismissable?: boolean;
|
|
isOpen?: boolean;
|
|
onClose?: () => void;
|
|
onOpenChange?: (open: boolean) => void;
|
|
placement?: "center" | "top" | "bottom";
|
|
scrollBehavior?: "inside" | "outside";
|
|
size?: ModalSize;
|
|
}
|
|
|
|
export function Modal({
|
|
children,
|
|
classNames,
|
|
isOpen = false,
|
|
onClose,
|
|
onOpenChange,
|
|
scrollBehavior,
|
|
size,
|
|
}: ModalProps) {
|
|
const handleOpenChange = (open: boolean) => {
|
|
onOpenChange?.(open);
|
|
if (!open) {
|
|
onClose?.();
|
|
}
|
|
};
|
|
|
|
const contextValue = {
|
|
classNames,
|
|
onClose: () => {
|
|
handleOpenChange(false);
|
|
},
|
|
scrollBehavior,
|
|
size,
|
|
};
|
|
|
|
return (
|
|
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
|
|
<ModalContext.Provider value={contextValue}>
|
|
{children}
|
|
</ModalContext.Provider>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
interface ModalContentProps
|
|
extends Omit<React.ComponentProps<"div">, "children"> {
|
|
children: React.ReactNode | ((onClose: () => void) => React.ReactNode);
|
|
scrollBehavior?: "inside" | "outside";
|
|
size?: ModalSize;
|
|
}
|
|
|
|
export function ModalContent({
|
|
children,
|
|
className,
|
|
scrollBehavior,
|
|
size,
|
|
...props
|
|
}: ModalContentProps) {
|
|
const context = useModalContext();
|
|
const resolvedScrollBehavior = scrollBehavior ?? context?.scrollBehavior;
|
|
const resolvedSize = size ?? context?.size;
|
|
const renderedChildren =
|
|
typeof children === "function"
|
|
? children(() => context?.onClose())
|
|
: children;
|
|
|
|
return (
|
|
<BaseDialogContent
|
|
className={cn(
|
|
mapSize(resolvedSize),
|
|
context?.classNames?.base,
|
|
resolvedScrollBehavior === "outside"
|
|
? "max-h-[90vh] overflow-y-auto scrollbar-hide"
|
|
: "",
|
|
resolvedScrollBehavior === "inside"
|
|
? "max-h-[90vh] flex flex-col overflow-hidden [&_[data-slot=modal-body]]:min-h-0 [&_[data-slot=modal-body]]:flex-1 [&_[data-slot=modal-body]]:overflow-y-auto [&_[data-slot=modal-body]]:scrollbar-hide"
|
|
: "",
|
|
className,
|
|
)}
|
|
showCloseButton={false}
|
|
{...props}
|
|
>
|
|
{renderedChildren}
|
|
</BaseDialogContent>
|
|
);
|
|
}
|
|
|
|
export function ModalHeader({
|
|
className,
|
|
...props
|
|
}: React.ComponentProps<"div">) {
|
|
const context = useModalContext();
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"text-lg font-semibold",
|
|
context?.classNames?.header,
|
|
className,
|
|
)}
|
|
data-slot="modal-header"
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export function ModalBody({
|
|
className,
|
|
...props
|
|
}: React.ComponentProps<"div">) {
|
|
const context = useModalContext();
|
|
|
|
return (
|
|
<div
|
|
className={cn("space-y-4", context?.classNames?.body, className)}
|
|
data-slot="modal-body"
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export function ModalFooter({
|
|
className,
|
|
...props
|
|
}: React.ComponentProps<"div">) {
|
|
const context = useModalContext();
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"mt-4 flex flex-wrap justify-end gap-2",
|
|
context?.classNames?.footer,
|
|
className,
|
|
)}
|
|
data-slot="modal-footer"
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|