diff --git a/frontend/components/settings/settings-parts.tsx b/frontend/components/settings/settings-parts.tsx index 4b30423..0d5d06f 100644 --- a/frontend/components/settings/settings-parts.tsx +++ b/frontend/components/settings/settings-parts.tsx @@ -1,7 +1,7 @@ "use client"; import type { ReactNode } from "react"; -import { useState } from "react"; +import { createContext, useContext, useState } from "react"; import { Check, Copy } from "lucide-react"; import { useTranslation } from "react-i18next"; @@ -13,9 +13,15 @@ import { CardFrameHeader, CardFrameTitle, } from "@/components/ui/card"; +import { Frame, FramePanel } from "@/components/ui/frame"; import { Switch } from "@/components/ui/switch"; import { cn } from "@/lib/utils"; +// Whether the enclosing frame is a COSS "Separated Panels" tray. When true, +// SettingsCard/ToggleRow render as a FramePanel (a distinct bordered card on the +// muted tray) instead of a fused CardFrame Card. +const SeparatedFrameContext = createContext(false); + // A settings section rendered inside the COSS "frame" surface: a titled header // above one or more cards. // @@ -43,15 +49,36 @@ export function SettingsFrame({ children: ReactNode; className?: string; /** - * COSS "Separated Panels": add a 1rem gap so sibling panels read as distinct - * cards instead of one flush joined list. The `gap-4` matches CardFrame's - * built-in `--clip-top/--clip-bottom: -1rem`, which keeps each panel's rounded - * corners clipping correctly across the gap. Leave off for a joined list. + * COSS "Separated Panels": render a muted Frame tray whose children are + * distinct bordered FramePanels spaced apart (the real coss.com/ui/frame + * look), instead of the fused CardFrame surface. Leave off for a joined list. */ separated?: boolean; }) { + if (separated) { + return ( + +
+
+

{title}

+ {description ? ( +

{description}

+ ) : null} +
+ {action ?
{action}
: null} +
+ + {children} + + + ); + } + return ( - + {title} {description ? ( @@ -105,6 +132,16 @@ export function SettingsCard({ className?: string; children: ReactNode; }) { + const separated = useContext(SeparatedFrameContext); + if (separated) { + // A distinct panel on the Frame tray. `flex flex-col` mirrors Card's default + // layout; callers that need a row (ToggleRow) override with `flex-row`. + return ( + + {children} + + ); + } return {children}; } diff --git a/frontend/components/ui/frame.tsx b/frontend/components/ui/frame.tsx new file mode 100644 index 0000000..1af8ee9 --- /dev/null +++ b/frontend/components/ui/frame.tsx @@ -0,0 +1,94 @@ +import type React from "react"; + +import { cn } from "@/lib/utils"; + +// COSS "Frame" primitive (coss.com/ui/docs/components/frame). A Frame is a muted +// tray that holds one or more FramePanels; multiple panels are "Separated +// Panels" — spaced apart with `mt-1` by default (via the adjacent-sibling +// selector below), each a self-contained bordered card. This is the opposite of +// `CardFrame` in card.tsx, which fuses its cards into one flush surface. + +export function Frame({ + className, + ...props +}: React.ComponentProps<"div">): React.ReactElement { + return ( +
+ ); +} + +export function FramePanel({ + className, + ...props +}: React.ComponentProps<"div">): React.ReactElement { + return ( +
+ ); +} + +export function FrameHeader({ + className, + ...props +}: React.ComponentProps<"div">): React.ReactElement { + return ( +
+ ); +} + +export function FrameTitle({ + className, + ...props +}: React.ComponentProps<"div">): React.ReactElement { + return ( +
+ ); +} + +export function FrameDescription({ + className, + ...props +}: React.ComponentProps<"div">): React.ReactElement { + return ( +
+ ); +} + +export function FrameFooter({ + className, + ...props +}: React.ComponentProps<"div">): React.ReactElement { + return ( +
+ ); +}