feat(web): useLang + segmentClass/rowStateClass helpers; delete dead Card (#66)
This commit is contained in:
@@ -1,103 +0,0 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Card({
|
||||
className,
|
||||
size = "default",
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={cn(
|
||||
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-title"
|
||||
className={cn(
|
||||
"text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-description"
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-action"
|
||||
className={cn(
|
||||
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-content"
|
||||
className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
className={cn(
|
||||
"flex items-center rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/card:p-3",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardAction,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import { rowStateClass, segmentClass } from "./class-recipes";
|
||||
|
||||
test("segmentClass active uses the primary tokens + focus ring", () => {
|
||||
const cls = segmentClass(true, "px-2 py-1");
|
||||
expect(cls).toContain("bg-primary");
|
||||
expect(cls).toContain("text-primary-foreground");
|
||||
expect(cls).toContain("focus-visible:ring-ring/50");
|
||||
expect(cls).toContain("px-2");
|
||||
});
|
||||
|
||||
test("segmentClass inactive uses border, not the primary fill", () => {
|
||||
const cls = segmentClass(false);
|
||||
expect(cls).toContain("border");
|
||||
expect(cls).not.toContain("bg-primary");
|
||||
});
|
||||
|
||||
test("rowStateClass toggles selected vs idle-hover", () => {
|
||||
expect(rowStateClass(true)).toBe("bg-primary/10");
|
||||
expect(rowStateClass(false)).toBe("hover:bg-muted");
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { focusRing } from "./focus-ring";
|
||||
|
||||
/** Segmented-control / filter-pill item. Unifies the active/inactive token recipe +
|
||||
* focus ring; callers pass their contextual padding/size via `className`. */
|
||||
export function segmentClass(active: boolean, className?: string): string {
|
||||
return cn("rounded-md", focusRing, active ? "bg-primary text-primary-foreground" : "border", className);
|
||||
}
|
||||
|
||||
/** Selected vs idle row background for master-detail / list rows. */
|
||||
export function rowStateClass(active: boolean): string {
|
||||
return active ? "bg-primary/10" : "hover:bg-muted";
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
/** The instance's active UI language, narrowed to the two supported locales. */
|
||||
export function useLang(): "sv" | "en" {
|
||||
const { i18n } = useTranslation();
|
||||
return i18n.language.startsWith("sv") ? "sv" : "en";
|
||||
}
|
||||
Reference in New Issue
Block a user