mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
The Appearance hub paints its modules as one divide-y ledger inside a settings card, and the card dropped its padding without dropping its gap. The gap survived between the rows as unclickable space the first row does not have: 24 px on desktop, 16 px on a phone, measured. Rows read a step apart from the top of their own list. - SettingsCard grows a `flush` variant that drops padding AND gap, naming the shape `<InsightSectionCard flush>` already names on the Insights side - the geometry sweep gains a fourth claim: in a divided list card nothing sits between two rows but the divider, and the first row starts at the card's inner edge
66 lines
2.4 KiB
TypeScript
66 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { ChevronRight } from "lucide-react";
|
|
|
|
import { useAuth } from "@/hooks/use-auth";
|
|
import { useMounted } from "@/hooks/use-mounted";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
import { SettingsCard } from "./settings-card";
|
|
import { LAYOUT_GROUPS } from "./layout-groups";
|
|
|
|
/**
|
|
* v1.25.11 (#148) — the "Appearance" hub (slug stays `layout`).
|
|
*
|
|
* The hub is a navigable index, NOT a stacked composition: it lists each
|
|
* module as a clickable row (title + description + chevron) that opens the
|
|
* module's own subpage at `/settings/layout/<id>`. Nothing is stacked inline;
|
|
* the per-module view/sort surfaces live on their subpages so the hub reads as
|
|
* a short, scannable directory. The visible page heading + subtitle
|
|
* ("Appearance" / "Personalize how …") are painted by `SettingsShell` from the
|
|
* `layout` slug; this body renders only the row list.
|
|
*
|
|
* Per-module gating fails OPEN (`!== false`): a missing key reads as enabled so
|
|
* a section never silently disappears. Groups with no `moduleGate` always
|
|
* render. The filter is gated on a post-mount flag (`useMounted()`) so SSR and
|
|
* the first client paint ALWAYS emit the same fail-open list — the same
|
|
* hydration-stability contract the shell nav uses, so the module filter can
|
|
* never produce a React #418 mismatch on this surface.
|
|
*/
|
|
export function LayoutSection() {
|
|
const { t } = useTranslations();
|
|
const { user } = useAuth();
|
|
|
|
const hydrated = useMounted();
|
|
const modules = user?.modules;
|
|
const visibleGroups = LAYOUT_GROUPS.filter(
|
|
(group) =>
|
|
!hydrated || !group.moduleGate || modules?.[group.moduleGate] !== false,
|
|
);
|
|
|
|
return (
|
|
<SettingsCard flush className="divide-border divide-y">
|
|
{visibleGroups.map((group) => (
|
|
<Link
|
|
key={group.id}
|
|
href={`/settings/layout/${group.id}`}
|
|
className="hover:bg-accent/50 flex items-center justify-between gap-4 p-4 transition-colors md:p-6"
|
|
>
|
|
<div className="min-w-0 space-y-0.5">
|
|
<p className="text-foreground text-sm font-medium">
|
|
{t(group.titleKey)}
|
|
</p>
|
|
<p className="text-muted-foreground text-sm">
|
|
{t(group.descriptionKey)}
|
|
</p>
|
|
</div>
|
|
<ChevronRight
|
|
className="text-muted-foreground h-5 w-5 shrink-0"
|
|
aria-hidden="true"
|
|
/>
|
|
</Link>
|
|
))}
|
|
</SettingsCard>
|
|
);
|
|
}
|