frontend: space separated panels in settings frames

Add an opt-in `separated` prop to SettingsFrame/SettingsSection that adds the
COSS "Separated Panels" gap-4 between sibling panels (matches CardFrame's
built-in -1rem clip vars so rounded corners clip across the gap). Turn it on
for the multi-toggle frames — AI availability and the two notification
sections — so their panels no longer stack flush. Joined divide-y lists stay
flush.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalid Abdi
2026-07-16 23:48:30 +03:00
parent 7312d15d67
commit 000591f87f
3 changed files with 22 additions and 2 deletions
@@ -211,6 +211,7 @@ export function AIPanel() {
{policy ? ( {policy ? (
<SettingsFrame <SettingsFrame
description={t("settings.ai.availability.description")} description={t("settings.ai.availability.description")}
separated
title={t("settings.ai.availability.title")} title={t("settings.ai.availability.title")}
> >
{isAdmin ? ( {isAdmin ? (
@@ -14,6 +14,7 @@ import {
CardFrameTitle, CardFrameTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { cn } from "@/lib/utils";
// A settings section rendered inside the COSS "frame" surface: a titled header // A settings section rendered inside the COSS "frame" surface: a titled header
// above one or more cards. // above one or more cards.
@@ -34,15 +35,23 @@ export function SettingsFrame({
action, action,
children, children,
className, className,
separated = false,
}: { }: {
title: string; title: string;
description?: string; description?: string;
action?: ReactNode; action?: ReactNode;
children: ReactNode; children: ReactNode;
className?: string; 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.
*/
separated?: boolean;
}) { }) {
return ( return (
<CardFrame className={className}> <CardFrame className={cn(separated && "gap-4", className)}>
<CardFrameHeader className="border-b border-border/60"> <CardFrameHeader className="border-b border-border/60">
<CardFrameTitle className="text-base">{title}</CardFrameTitle> <CardFrameTitle className="text-base">{title}</CardFrameTitle>
{description ? ( {description ? (
@@ -63,14 +72,22 @@ export function SettingsSection({
description, description,
action, action,
children, children,
separated = false,
}: { }: {
title: string; title: string;
description?: string; description?: string;
action?: ReactNode; action?: ReactNode;
children: ReactNode; children: ReactNode;
/** See {@link SettingsFrame}'s `separated` — spaces sibling panels apart. */
separated?: boolean;
}) { }) {
return ( return (
<SettingsFrame action={action} description={description} title={title}> <SettingsFrame
action={action}
description={description}
separated={separated}
title={title}
>
{children} {children}
</SettingsFrame> </SettingsFrame>
); );
@@ -339,6 +339,7 @@ export function ProfilePanel() {
<SettingsSection <SettingsSection
description={t("settings.profile.patientNotificationsDescription")} description={t("settings.profile.patientNotificationsDescription")}
separated
title={t("settings.profile.patientNotifications")} title={t("settings.profile.patientNotifications")}
> >
{patientNotifications.map((item) => ( {patientNotifications.map((item) => (
@@ -356,6 +357,7 @@ export function ProfilePanel() {
<SettingsSection <SettingsSection
description={t("settings.profile.accountNotificationsDescription")} description={t("settings.profile.accountNotificationsDescription")}
separated
title={t("settings.profile.accountNotifications")} title={t("settings.profile.accountNotifications")}
> >
{accountNotifications.map((item) => ( {accountNotifications.map((item) => (