Change tabs list page

This commit is contained in:
2025-12-21 22:11:29 +01:00
parent 48114f391a
commit 784153e9f6
7 changed files with 264 additions and 12 deletions

View File

@ -18,6 +18,7 @@ type TagRow = {
name: string;
slug: string;
parent: { id: string; name: string } | null;
isParent: boolean;
showOnAnimalPage: boolean;
aliases: { alias: string }[];
categories: { id: string; name: string }[];
@ -59,7 +60,7 @@ function Chips({
);
}
export default function TagTable({ tags }: { tags: TagRow[] }) {
export default function TagTableAnimal({ tags }: { tags: TagRow[] }) {
const handleDelete = (id: string) => {
deleteTag(id);
};
@ -103,7 +104,7 @@ export default function TagTable({ tags }: { tags: TagRow[] }) {
{t.parent.name}
</Link>
) : (
<span className="text-muted-foreground"></span>
<span className="text-muted-foreground">{t.isParent ? "Parent" : "—"}</span>
)}
</TableCell>

View File

@ -0,0 +1,100 @@
"use client";
import type { TagRow } from "@/components/tags/TagTabs";
import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { PencilIcon } from "lucide-react";
import Link from "next/link";
function Chips({
values,
empty = "—",
max = 4,
mono = false,
}: {
values: string[];
empty?: string;
max?: number;
mono?: boolean;
}) {
if (values.length === 0) return <span className="text-muted-foreground">{empty}</span>;
const shown = values.slice(0, max);
const extra = values.length - shown.length;
return (
<div className="flex flex-wrap gap-2">
{shown.map((v) => (
<span
key={v}
className={[
"rounded bg-muted px-2 py-1 text-xs",
mono ? "font-mono" : "",
].join(" ")}
title={v}
>
{v}
</span>
))}
{extra > 0 ? <span className="text-xs text-muted-foreground">+{extra} more</span> : null}
</div>
);
}
export default function TagTableMain({ tags }: { tags: TagRow[] }) {
return (
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[22%]">Name</TableHead>
<TableHead className="w-[16%]">Slug</TableHead>
<TableHead className="w-[26%]">Aliases</TableHead>
<TableHead className="w-[26%]">Categories</TableHead>
<TableHead className="w-[10%] text-right">Artworks</TableHead>
<TableHead className="w-[8%] text-right" />
</TableRow>
</TableHeader>
<TableBody>
{tags.map((t) => (
<TableRow key={t.id}>
<TableCell className="font-medium">{t.name}</TableCell>
<TableCell className="font-mono text-sm text-muted-foreground">
#{t.slug}
</TableCell>
<TableCell>
<Chips values={t.aliases.map((a) => a.alias)} mono max={5} />
</TableCell>
<TableCell>
<Chips values={t.categories.map((c) => c.name)} max={4} />
</TableCell>
<TableCell className="text-right tabular-nums">
{t._count.artworks}
</TableCell>
<TableCell className="text-right">
<Link href={`/tags/${t.id}`} aria-label={`Edit ${t.name}`}>
<Button size="icon" variant="secondary">
<PencilIcon className="h-4 w-4" />
</Button>
</Link>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
}

View File

@ -0,0 +1,66 @@
"use client";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import * as React from "react";
// import TagTableMain from "@/components/tags/TagTableMain";
// import TagTableAnimal from "@/components/tags/TagTableAnimal";
import { Badge } from "@/components/ui/badge";
import TagTableAnimal from "./TagTableAnimal";
import TagTableMain from "./TagTableMain";
export type TagRow = {
id: string;
name: string;
slug: string;
parent: { id: string; name: string } | null;
isParent: boolean;
showOnAnimalPage: boolean;
aliases: { alias: string }[];
categories: { id: string; name: string }[];
_count: { artworks: number };
};
function isAnimalStudiesTag(t: TagRow) {
// Recommended: primarily category-based; keep showOnAnimalPage as fallback.
const inCategory = t.categories.some((c) => c.name === "Animal Studies");
// return inCategory || t.showOnAnimalPage;
return inCategory;
}
export default function TagTabs({ tags }: { tags: TagRow[] }) {
const animal = React.useMemo(() => tags.filter(isAnimalStudiesTag), [tags]);
const normal = React.useMemo(
() => tags.filter((t) => !isAnimalStudiesTag(t)),
[tags, animal.length]
);
return (
<Tabs defaultValue="all" className="w-full">
<TabsList className="mb-4">
<TabsTrigger value="all" className="gap-2">
All tags <Badge variant="secondary">{tags.length}</Badge>
</TabsTrigger>
<TabsTrigger value="animal" className="gap-2">
Animal Studies <Badge variant="secondary">{animal.length}</Badge>
</TabsTrigger>
<TabsTrigger value="normal" className="gap-2">
Other <Badge variant="secondary">{normal.length}</Badge>
</TabsTrigger>
</TabsList>
<TabsContent value="all">
<TagTableMain tags={tags} />
</TabsContent>
<TabsContent value="animal">
<TagTableAnimal tags={animal} />
</TabsContent>
<TabsContent value="normal">
<TagTableMain tags={normal} />
</TabsContent>
</Tabs>
);
}

View File

@ -0,0 +1,66 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function TabsList({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
className
)}
{...props}
/>
)
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground 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-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
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 }