mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
Maintainer-voice sweep across `src/`. ~190 references to the maintainer by first name in code comments and JSDoc neutralised to "the maintainer", "the live tenant", "users", or "the user" depending on context. Three sed passes covering ~50 distinct patterns (`Marc reported`, `Marc wants`, `Marc kept`, `Marc rolled back`, etc.). Test fixtures (`displayName: "Marc B."`, `username: "marc"`, `<HeroStrip userName="Marc">`) kept as opaque test data per the brief. CHANGELOG normalisation. The v1.4.14 + v1.4.15 entries paired a German bullet with its English translation by historical design — the in-app copy is English-default and the user docs are English, so the mixed-language style was a holdover from the early-marathon notes. A one-off node script extracted the `_..._` English paragraph from each bilingual bullet and rewrote the entry as that text. ~44 bullets reduced; one Docs bullet that lost a leading word in the regex fixed by hand. CLAUDE.md → CONTRIBUTING-AI.md rename so the filename is no longer AI-vendor-specific. `CONTRIBUTING.md` reference updated. `AGENTS.md` stays in place for multi-agent compatibility (the conventions doc is shared with Codex / Cursor / etc.). Wave-4 state wrap. STATE.md now records C1–C5 + D + D2 as complete; v1421-backlog.md grew a "Closed in v1.4.22" header listing the 20 items the marathon landed; `.planning/phase-W4-report.md` documents the per-commit decisions, test deltas, and the 23 items deferred to v1.4.23 with reasoning. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
114 lines
5.2 KiB
TypeScript
114 lines
5.2 KiB
TypeScript
"use client";
|
|
|
|
import * as React from "react";
|
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
import { Tabs as TabsPrimitive } from "radix-ui";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
function Tabs({
|
|
className,
|
|
orientation = "horizontal",
|
|
...props
|
|
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
|
return (
|
|
<TabsPrimitive.Root
|
|
data-slot="tabs"
|
|
data-orientation={orientation}
|
|
orientation={orientation}
|
|
className={cn(
|
|
"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
|
|
className,
|
|
)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
// v1.4.16 Wave-C MED — horizontal tab strips overflow viewport on
|
|
// mobile when more than ~3 tabs render at typical label widths
|
|
// (`/insights`, `/admin`, `/settings/*`). Tab labels are i18n-driven
|
|
// so we cannot truncate them deterministically. Instead, the list
|
|
// becomes horizontally scrollable on mobile via `overflow-x-auto`
|
|
// plus `touch-pan-x` for the iOS PWA, and the wrapping tabs preserve
|
|
// `whitespace-nowrap` so labels never word-wrap mid-tab. Desktop
|
|
// looks identical because the inline-flex shrink-wraps inside the
|
|
// wider container.
|
|
//
|
|
// v1.4.19 A7 — `overflow-y-hidden` clamps the y-axis. Browsers couple
|
|
// the two overflow axes once one is `auto`: setting only
|
|
// `overflow-x: auto` silently flips `overflow-y` to `auto` as well.
|
|
// Combined with the fixed `h-9` strip and badge children that ride
|
|
// ~1 px taller than the strip on some glyph stacks, a tiny painted
|
|
// vertical scrollbar appeared on the right edge of short strips
|
|
// (e.g. the admin feedback `Open / Acknowledged / Resolved /
|
|
// Archived` tabs the maintainer reported in v1.4.19). Locking the y-axis hides
|
|
// that bar without affecting horizontal swipe behaviour.
|
|
// Padding sits at `p-1` (4 px) so the 3 px focus-visible ring
|
|
// (`focus-visible:ring-[3px]`) on triggers stays inside the
|
|
// `overflow-y-hidden` clamp without clipping the bottom of the ring.
|
|
const tabsListVariants = cva(
|
|
"rounded-lg p-1 group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit max-w-full items-center justify-center overflow-x-auto overflow-y-hidden touch-pan-x scrollbar-thin group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col group-data-[orientation=vertical]/tabs:overflow-x-visible group-data-[orientation=vertical]/tabs:overflow-y-visible",
|
|
{
|
|
variants: {
|
|
variant: {
|
|
default: "bg-muted",
|
|
line: "gap-1 bg-transparent",
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
variant: "default",
|
|
},
|
|
},
|
|
);
|
|
|
|
function TabsList({
|
|
className,
|
|
variant = "default",
|
|
...props
|
|
}: React.ComponentProps<typeof TabsPrimitive.List> &
|
|
VariantProps<typeof tabsListVariants>) {
|
|
return (
|
|
<TabsPrimitive.List
|
|
data-slot="tabs-list"
|
|
data-variant={variant}
|
|
className={cn(tabsListVariants({ variant }), className)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function TabsTrigger({
|
|
className,
|
|
...props
|
|
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
return (
|
|
<TabsPrimitive.Trigger
|
|
data-slot="tabs-trigger"
|
|
className={cn(
|
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/75 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
|
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground",
|
|
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
|
className,
|
|
)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function TabsContent({
|
|
className,
|
|
...props
|
|
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
return (
|
|
<TabsPrimitive.Content
|
|
data-slot="tabs-content"
|
|
className={cn("flex-1 outline-none", className)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
|