mirror of
https://github.com/temetro/temetro.git
synced 2026-07-26 11:58:14 +00:00
frontend: compose settings frames from CardFrame primitives
SettingsFrame used a hand-rolled <div className="p-5"> body and SettingsCard was a plain div (no data-slot=card), so the frame's card styling never applied. - Add CardFramePanel (data-slot="card-frame-panel") as the padded frame body, mirroring the other CardFrame* subcomponents. - SettingsFrame now composes CardFrameHeader + CardFramePanel (same p-5 padding as before) instead of a raw div. - SettingsCard renders a real COSS Card (data-slot=card). Since Card is flex flex-col, the horizontal-row call sites (ToggleRow, billing and preferences rows) now pass flex-row. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -176,7 +176,7 @@ export function SigningPanel() {
|
|||||||
description={t("settings.network.description")}
|
description={t("settings.network.description")}
|
||||||
title={t("settings.network.title")}
|
title={t("settings.network.title")}
|
||||||
>
|
>
|
||||||
<SettingsCard className="flex items-center justify-between gap-4 p-5">
|
<SettingsCard className="flex flex-row items-center justify-between gap-4 p-5">
|
||||||
<div className="min-w-0 space-y-0.5">
|
<div className="min-w-0 space-y-0.5">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<p className="text-sm font-medium">
|
<p className="text-sm font-medium">
|
||||||
@@ -251,7 +251,7 @@ export function SigningPanel() {
|
|||||||
description={t("settings.signing.backupDescription")}
|
description={t("settings.signing.backupDescription")}
|
||||||
title={t("settings.signing.backupTitle")}
|
title={t("settings.signing.backupTitle")}
|
||||||
>
|
>
|
||||||
<SettingsCard className="flex items-center justify-between gap-4 p-4">
|
<SettingsCard className="flex flex-row items-center justify-between gap-4 p-4">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<p className="text-sm font-medium">
|
<p className="text-sm font-medium">
|
||||||
{t("settings.signing.backupLabel")}
|
{t("settings.signing.backupLabel")}
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import { useState } from "react";
|
|||||||
import { Check, Copy } from "lucide-react";
|
import { Check, Copy } from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
import {
|
||||||
|
Card,
|
||||||
CardFrame,
|
CardFrame,
|
||||||
CardFrameAction,
|
CardFrameAction,
|
||||||
CardFrameDescription,
|
CardFrameDescription,
|
||||||
CardFrameHeader,
|
CardFrameHeader,
|
||||||
|
CardFramePanel,
|
||||||
CardFrameTitle,
|
CardFrameTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
@@ -43,7 +44,7 @@ export function SettingsFrame({
|
|||||||
) : null}
|
) : null}
|
||||||
{action ? <CardFrameAction>{action}</CardFrameAction> : null}
|
{action ? <CardFrameAction>{action}</CardFrameAction> : null}
|
||||||
</CardFrameHeader>
|
</CardFrameHeader>
|
||||||
<div className={cn("p-5", bodyClassName)}>{children}</div>
|
<CardFramePanel className={bodyClassName}>{children}</CardFramePanel>
|
||||||
</CardFrame>
|
</CardFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -74,6 +75,10 @@ export function SettingsSection({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A card surface used inside settings panels. Rendering a real COSS `Card` (with
|
||||||
|
// `data-slot="card"`) keeps settings cards consistent with the rest of the app
|
||||||
|
// and lets them pick up the frame's card treatment. `Card` is `flex flex-col`,
|
||||||
|
// so row layouts must pass `flex-row` in their className.
|
||||||
export function SettingsCard({
|
export function SettingsCard({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
@@ -81,11 +86,7 @@ export function SettingsCard({
|
|||||||
className?: string;
|
className?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return <Card className={className}>{children}</Card>;
|
||||||
<div className={cn("rounded-2xl border border-border bg-card/30", className)}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ToggleRow({
|
export function ToggleRow({
|
||||||
@@ -103,7 +104,7 @@ export function ToggleRow({
|
|||||||
onCheckedChange?: (checked: boolean) => void;
|
onCheckedChange?: (checked: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SettingsCard className="flex items-center justify-between gap-4 px-4 py-3.5">
|
<SettingsCard className="flex flex-row items-center justify-between gap-4 px-4 py-3.5">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<p className="text-sm font-medium">{title}</p>
|
<p className="text-sm font-medium">{title}</p>
|
||||||
{description ? (
|
{description ? (
|
||||||
|
|||||||
@@ -375,7 +375,7 @@ export function ProfilePanel() {
|
|||||||
description={t("settings.profile.dangerZoneDescription")}
|
description={t("settings.profile.dangerZoneDescription")}
|
||||||
title={t("settings.profile.dangerZone")}
|
title={t("settings.profile.dangerZone")}
|
||||||
>
|
>
|
||||||
<SettingsCard className="flex items-center justify-between gap-4 p-4">
|
<SettingsCard className="flex flex-row items-center justify-between gap-4 p-4">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<p className="text-sm font-medium">
|
<p className="text-sm font-medium">
|
||||||
{t("settings.profile.deleteAccount")}
|
{t("settings.profile.deleteAccount")}
|
||||||
|
|||||||
@@ -136,6 +136,26 @@ export function CardFrameFooter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The padded body of a frame: content that sits between the frame header and
|
||||||
|
// footer. Mirrors the other CardFrame* subcomponents (useRender + data-slot) so
|
||||||
|
// a frame can be composed entirely from primitives instead of a raw <div>.
|
||||||
|
export function CardFramePanel({
|
||||||
|
className,
|
||||||
|
render,
|
||||||
|
...props
|
||||||
|
}: useRender.ComponentProps<"div">): React.ReactElement {
|
||||||
|
const defaultProps = {
|
||||||
|
className: cn("p-5", className),
|
||||||
|
"data-slot": "card-frame-panel",
|
||||||
|
};
|
||||||
|
|
||||||
|
return useRender({
|
||||||
|
defaultTagName: "div",
|
||||||
|
props: mergeProps<"div">(defaultProps, props),
|
||||||
|
render,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function CardHeader({
|
export function CardHeader({
|
||||||
className,
|
className,
|
||||||
render,
|
render,
|
||||||
|
|||||||
Reference in New Issue
Block a user