Refactor frontend system primitives and surfaces

This commit is contained in:
MOH
2026-03-09 06:15:01 +01:00
parent 3e835ee620
commit a65037e3b3
22 changed files with 1363 additions and 106 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ export function FlashMessage({
return (
<p
className={cn(
"rounded-md border px-4 py-3 text-sm",
"rounded-nested border px-4 py-3 text-sm",
type === "success"
? "border-status-success/30 bg-status-success/10 text-status-success"
: "border-destructive/30 bg-destructive/10 text-destructive",
+11 -10
View File
@@ -8,6 +8,7 @@ import { useEffect, useId, useMemo, useRef, useState } from "react";
import type { MediaOption } from "@/lib/media";
import { cn } from "@/lib/utils";
import { AppCard } from "@/components/ui/app-card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -110,7 +111,7 @@ export function MediaFieldPicker({
}, [serializedValue]);
return (
<div className="space-y-3 rounded-surface border border-border/80 bg-card p-4 shadow-card">
<AppCard padding="sm" className="space-y-3">
<div className="space-y-2">
<Label className="sr-only">{title}</Label>
<div className="flex flex-wrap gap-2">
@@ -238,16 +239,16 @@ export function MediaFieldPicker({
})
}
className={cn(
"flex w-full items-center gap-3 rounded-md border px-3 py-2 text-left transition-colors",
"flex w-full items-center gap-3 rounded-nested border px-3 py-2 text-left transition-colors",
isActive
? "border-input bg-accent/40"
: "border-input bg-background hover:bg-accent/20",
)}
>
{option.url ? (
<img src={option.url} alt={option.label} className="h-12 w-12 rounded-md object-cover" />
<img src={option.url} alt={option.label} className="h-12 w-12 rounded-nested object-cover" />
) : (
<div className="h-12 w-12 rounded-md border border-border bg-background" />
<div className="h-12 w-12 rounded-nested border border-border bg-background" />
)}
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-foreground">{option.label}</p>
@@ -258,7 +259,7 @@ export function MediaFieldPicker({
);
})
) : (
<div className="rounded-md border border-dashed border-input px-3 py-4 text-sm text-muted-foreground">
<div className="rounded-nested border border-dashed border-input px-3 py-4 text-sm text-muted-foreground">
No media found.
</div>
)}
@@ -269,15 +270,15 @@ export function MediaFieldPicker({
{previewUrl ? (
value.kind === "IMAGE" ? (
<div className="overflow-hidden rounded-md border bg-card">
<AppCard className="overflow-hidden rounded-nested">
<img src={previewUrl} alt={value.label || title} className="h-40 w-full object-cover" />
</div>
</AppCard>
) : (
<div className="rounded-md border bg-card px-4 py-3 text-sm text-muted-foreground">
<AppCard className="rounded-nested px-4 py-3 text-sm text-muted-foreground">
{previewUrl}
</div>
</AppCard>
)
) : null}
</div>
</AppCard>
);
}
@@ -80,7 +80,7 @@ function CategoryLocaleFields({
const descriptionKey = `description${locale.key}` as const;
return (
<div key={`${idPrefix}-${locale.key}`} className="space-y-4 rounded-lg border border-input bg-background p-4">
<AppCard key={`${idPrefix}-${locale.key}`} level={2} padding="sm" className="space-y-4 rounded-nested">
<div className="space-y-1">
<p className="text-sm font-medium text-foreground">{locale.label}</p>
</div>
@@ -115,7 +115,7 @@ function CategoryLocaleFields({
/>
</div>
</div>
</div>
</AppCard>
);
})}
</div>
@@ -172,7 +172,7 @@ function EditCategoryDialog({
</div>
</div>
<label className="flex items-center gap-3 rounded-md border border-input bg-card px-4 py-3 text-sm">
<label className="flex items-center gap-3 rounded-nested border border-input bg-card px-4 py-3 text-sm">
<Checkbox name="isActive" defaultChecked={category.isActive} />
{copy.active}
</label>
@@ -234,21 +234,21 @@ export function PortfolioCategoriesManager({
return (
<div className="space-y-6">
<AppCard level={3} className="bg-secondary">
<AppCard level={3}>
<CardContent className="flex flex-col gap-6 p-6 lg:flex-row lg:items-end lg:justify-between lg:p-8">
<div className="grid gap-4 sm:grid-cols-3">
<div className="rounded-lg border border-input bg-background px-4 py-4">
<AppCard level={2} padding="sm">
<p className="text-xs uppercase tracking-[0.12em] text-muted-foreground">Total</p>
<p className="mt-2 text-3xl font-semibold text-foreground">{categories.length}</p>
</div>
<div className="rounded-lg border border-input bg-background px-4 py-4">
</AppCard>
<AppCard level={2} padding="sm">
<p className="text-xs uppercase tracking-[0.12em] text-muted-foreground">Active</p>
<p className="mt-2 text-3xl font-semibold text-foreground">{activeCount}</p>
</div>
<div className="rounded-lg border border-input bg-background px-4 py-4">
</AppCard>
<AppCard level={2} padding="sm">
<p className="text-xs uppercase tracking-[0.12em] text-muted-foreground">Assigned</p>
<p className="mt-2 text-3xl font-semibold text-foreground">{assignedProjects}</p>
</div>
</AppCard>
</div>
<Dialog open={createOpen} onOpenChange={setCreateOpen}>
@@ -284,7 +284,7 @@ export function PortfolioCategoriesManager({
</div>
</div>
<label className="flex items-center gap-3 rounded-md border border-input bg-card px-4 py-3 text-sm">
<label className="flex items-center gap-3 rounded-nested border border-input bg-card px-4 py-3 text-sm">
<Checkbox name="isActive" defaultChecked />
{copy.active}
</label>
@@ -301,7 +301,7 @@ export function PortfolioCategoriesManager({
</CardContent>
</AppCard>
<AppCard className="bg-secondary">
<AppCard level={3}>
<CardContent className="space-y-4 p-6">
<div className="flex items-center gap-3">
<Layers3 className="h-5 w-5 text-brand-primary" />
@@ -314,7 +314,7 @@ export function PortfolioCategoriesManager({
<Accordion type="single" collapsible className="space-y-3">
{categories.map((category) => (
<AccordionItem key={category.id} value={category.id}>
<AccordionTrigger className="bg-secondary hover:no-underline">
<AccordionTrigger className="bg-surface-2 hover:no-underline">
<div className="flex min-w-0 flex-1 flex-col gap-2 text-left lg:flex-row lg:items-center lg:justify-between">
<div className="space-y-1">
<div className="flex flex-wrap items-center gap-2">
@@ -354,11 +354,11 @@ export function PortfolioCategoriesManager({
<div className="space-y-3">
<div className="grid gap-3 md:grid-cols-3">
{locales.map((locale) => (
<div key={`${category.id}-${locale.key}`} className="rounded-lg border border-input bg-card p-4">
<AppCard key={`${category.id}-${locale.key}`} level={1} padding="sm" className="rounded-nested">
<p className="text-sm font-medium text-foreground">{locale.label}</p>
<p className="mt-3 text-sm text-foreground">{category.name[locale.lowerKey]}</p>
<p className="mt-2 text-sm text-muted-foreground">{category.description[locale.lowerKey]}</p>
</div>
</AppCard>
))}
</div>
</div>
+9 -9
View File
@@ -243,7 +243,7 @@ function PanelButton({
type="button"
onClick={onClick}
className={cn(
"flex w-full items-center justify-between rounded-lg border px-4 py-3 text-left transition-colors",
"flex w-full items-center justify-between rounded-nested border px-4 py-3 text-left transition-colors",
active
? "border-input bg-accent/40"
: "border-input bg-background hover:bg-accent/20",
@@ -265,17 +265,17 @@ function LocaleBlock({
renderField: (locale: (typeof localeFieldConfig)[number]) => React.ReactNode;
}) {
return (
<div className="space-y-3 rounded-lg border border-input bg-background p-4">
<AppCard level={2} padding="sm" className="space-y-3">
<p className="text-sm font-medium text-foreground">{title}</p>
<div className="grid gap-4 xl:grid-cols-3">
{localeFieldConfig.map((locale) => (
<div key={`${title}-${locale.key}`} className="space-y-2 rounded-lg border border-input bg-card p-4">
<AppCard key={`${title}-${locale.key}`} padding="sm" className="space-y-2 rounded-nested">
<p className="text-sm font-medium text-foreground">{locale.label}</p>
{renderField(locale)}
</div>
</AppCard>
))}
</div>
</div>
</AppCard>
);
}
@@ -540,12 +540,12 @@ export function PortfolioProjectForm({
/>
<div className="grid gap-4 md:grid-cols-2">
<label className="flex items-center gap-3 rounded-md border border-input bg-card px-4 py-3 text-sm">
<label className="flex items-center gap-3 rounded-nested border border-input bg-card px-4 py-3 text-sm">
<input type="checkbox" name="isFeatured" checked={projectState.isFeatured} onChange={(event) => setProjectField("isFeatured", event.target.checked)} />
Featured
</label>
<label className="flex items-center gap-3 rounded-md border border-input bg-card px-4 py-3 text-sm">
<label className="flex items-center gap-3 rounded-nested border border-input bg-card px-4 py-3 text-sm">
<input type="checkbox" name="isPublished" checked={projectState.isPublished} onChange={(event) => setProjectField("isPublished", event.target.checked)} />
Published
</label>
@@ -624,7 +624,7 @@ export function PortfolioProjectForm({
type="button"
onClick={() => setSelectedSectionIndex(index)}
className={cn(
"w-full rounded-lg border p-4 text-left transition-colors",
"w-full rounded-nested border p-4 text-left transition-colors",
selectedSectionIndex === index
? "border-input bg-accent/40"
: "border-input bg-background hover:bg-accent/20",
@@ -769,7 +769,7 @@ export function PortfolioProjectForm({
type="button"
onClick={() => setSelectedAssetIndex(index)}
className={cn(
"w-full rounded-lg border p-4 text-left transition-colors",
"w-full rounded-nested border p-4 text-left transition-colors",
selectedAssetIndex === index
? "border-input bg-accent/40"
: "border-input bg-background hover:bg-accent/20",
@@ -51,20 +51,20 @@ export function PortfolioProjectsOverview({
<AppCard level={3}>
<CardContent className="flex flex-col gap-6 p-6 lg:flex-row lg:items-end lg:justify-between lg:p-8">
<div className="grid gap-4 sm:grid-cols-3">
<div className="rounded-lg border border-input bg-background px-4 py-4">
<AppCard level={2} padding="sm">
<p className="text-xs uppercase tracking-[0.12em] text-muted-foreground">Projects</p>
<p className="mt-2 text-3xl font-semibold text-foreground">{projects.length}</p>
</div>
<div className="rounded-lg border border-input bg-background px-4 py-4">
</AppCard>
<AppCard level={2} padding="sm">
<p className="text-xs uppercase tracking-[0.12em] text-muted-foreground">Categories</p>
<p className="mt-2 text-3xl font-semibold text-foreground">{categories.length}</p>
</div>
<div className="rounded-lg border border-input bg-background px-4 py-4">
</AppCard>
<AppCard level={2} padding="sm">
<p className="text-xs uppercase tracking-[0.12em] text-muted-foreground">Published</p>
<p className="mt-2 text-3xl font-semibold text-foreground">
{projects.filter((project) => project.isPublished).length}
</p>
</div>
</AppCard>
</div>
<div className="flex flex-wrap gap-3">
+1 -1
View File
@@ -30,7 +30,7 @@ export function PortfolioSubnav({ active }: PortfolioSubnavProps) {
key={item.key}
href={item.href}
className={cn(
"rounded-md border px-4 py-2 text-sm transition-colors",
"rounded-nested border px-4 py-2 text-sm transition-colors",
active === item.key
? "border-input bg-primary text-primary-foreground"
: "border-input bg-background text-foreground/75 hover:bg-accent hover:text-accent-foreground",
+11 -11
View File
@@ -203,7 +203,7 @@ export function SiteSettingsForm({
</CardHeader>
<CardContent className="grid gap-4 lg:grid-cols-3">
{localeFields.map((locale) => (
<div key={locale.key} className="space-y-4 rounded-lg border bg-card p-4 shadow-sm">
<AppCard key={locale.key} padding="sm" className="space-y-4 rounded-nested">
<div>
<p className="text-sm font-medium text-foreground">{locale.label}</p>
<p className="mt-1 text-xs text-muted-foreground">Globale Standardwerte fuer diese Sprache.</p>
@@ -312,7 +312,7 @@ export function SiteSettingsForm({
/>
</div>
</div>
</div>
</AppCard>
))}
</CardContent>
</AppCard>
@@ -429,7 +429,7 @@ export function SiteSettingsForm({
: `${locale.sampleTitle} | ${localeSettings.siteName}`;
return (
<div key={locale.key} className="rounded-md border bg-card p-3">
<AppCard key={locale.key} className="rounded-nested p-3">
<p className="font-medium text-foreground">{locale.label}</p>
<p className="mt-2 text-xs text-muted-foreground">
Example page title: {locale.sampleTitle}
@@ -441,7 +441,7 @@ export function SiteSettingsForm({
<p className="mt-2 text-xs text-muted-foreground">
{localeSettings.subhead || "No subhead"}
</p>
</div>
</AppCard>
);
})}
</div>
@@ -451,7 +451,7 @@ export function SiteSettingsForm({
Header Logo Preview
</div>
<div className="grid gap-3">
<div className="rounded-md border bg-card p-4">
<AppCard padding="sm" className="rounded-nested">
<p className="mb-3 text-xs font-medium uppercase tracking-[0.2em] text-muted-foreground">
Light
</p>
@@ -466,8 +466,8 @@ export function SiteSettingsForm({
Default light logo will be used
</div>
)}
</div>
<div className="rounded-md border border-border/70 bg-slate-950 p-4">
</AppCard>
<div className="rounded-nested border border-border/70 bg-slate-950 p-4">
<p className="mb-3 text-xs font-medium uppercase tracking-[0.2em] text-white/55">
Dark
</p>
@@ -489,7 +489,7 @@ export function SiteSettingsForm({
<Search className="h-4 w-4 text-brand-primary" />
Search Preview
</div>
<div className="rounded-md border bg-card p-4">
<AppCard padding="sm" className="rounded-nested">
<div className="flex items-center gap-3">
{faviconPreviewUrl ? (
<img src={faviconPreviewUrl} alt="Favicon" className="h-5 w-5 rounded-sm" />
@@ -512,13 +512,13 @@ export function SiteSettingsForm({
<p className="mt-2 text-xs text-muted-foreground/80">
{settings.locales.de.subhead || "No subhead"}
</p>
</div>
</AppCard>
<div className="flex items-center gap-2 text-sm font-medium text-foreground">
<ImageIcon className="h-4 w-4 text-brand-secondary" />
Social Preview
</div>
<div className="overflow-hidden rounded-md border bg-card">
<AppCard className="overflow-hidden rounded-nested">
{defaultOgImagePreviewUrl ? (
<img src={defaultOgImagePreviewUrl} alt="Default OG" className="h-32 w-full object-cover" />
) : (
@@ -538,7 +538,7 @@ export function SiteSettingsForm({
{settings.locales.en.siteDescription || "No description"}
</p>
</div>
</div>
</AppCard>
</CardContent>
</AppCard>
</div>