Refactor frontend system primitives and surfaces
This commit is contained in:
@@ -22,7 +22,7 @@ export function DashboardCard({
|
||||
<CardDescription>{title}</CardDescription>
|
||||
<CardTitle className="text-3xl font-semibold tracking-tight">{value}</CardTitle>
|
||||
</div>
|
||||
<div className="rounded-md border border-border bg-muted/60 p-2 text-muted-foreground">
|
||||
<div className="rounded-nested border border-border bg-muted/60 p-2 text-muted-foreground">
|
||||
<Icon className="h-4 w-4" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
@@ -84,7 +84,7 @@ export function DashboardLayout({
|
||||
|
||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||
{Icon ? (
|
||||
<div className="hidden rounded-lg border border-border/70 bg-muted/50 p-2 text-muted-foreground sm:flex">
|
||||
<div className="hidden rounded-nested border border-border/70 bg-muted/50 p-2 text-muted-foreground sm:flex">
|
||||
<Icon className="h-4 w-4" />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -20,15 +20,15 @@ export function FloatingPreferences({
|
||||
return (
|
||||
<div className={cn("fixed inset-x-0 top-0 z-50 px-4 pt-4 sm:px-6", className)}>
|
||||
<div className="mx-auto flex w-full justify-center">
|
||||
<div className="inline-flex items-center justify-center gap-1 rounded-[var(--radius-pill)] border border-black/6 bg-white/24 p-1 shadow-[0_14px_34px_-24px_rgba(15,23,42,0.18)] backdrop-blur-[18px] dark:border-white/10 dark:bg-white/4 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)]">
|
||||
<div className="inline-flex items-center justify-center gap-1 rounded-pill border border-border/70 bg-background/80 p-1 shadow-panel backdrop-blur-chrome">
|
||||
<ThemeToggle
|
||||
ariaLabel={t("themeToggle")}
|
||||
variant="ghost"
|
||||
className="h-9 w-9 rounded-[var(--radius-pill)] border border-transparent bg-transparent text-[hsl(var(--hero-ink)/0.76)] hover:border-black/5 hover:bg-white/36 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:border-white/10 dark:hover:bg-white/10"
|
||||
className="h-9 w-9 rounded-pill border border-transparent bg-transparent text-foreground/80 hover:bg-accent hover:text-foreground"
|
||||
/>
|
||||
<LocaleToggle
|
||||
locale={locale}
|
||||
className="h-9 w-9 rounded-[var(--radius-pill)] border border-transparent bg-transparent p-0 text-[hsl(var(--hero-ink)/0.76)] hover:bg-white/36 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:bg-white/10"
|
||||
className="h-9 w-9 rounded-pill border border-transparent bg-transparent p-0 text-foreground/80 hover:bg-accent hover:text-foreground"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,18 +61,18 @@ function NavLinks({
|
||||
key={item.key}
|
||||
href={getLocalizedPath(locale, itemPath)}
|
||||
className={cn(
|
||||
"relative z-10 rounded-[var(--radius-pill)] transition-colors",
|
||||
"relative z-10 rounded-pill transition-colors",
|
||||
mobile ? "px-4 py-3 text-sm font-medium" : "px-3.5 py-2 text-sm font-medium xl:px-4",
|
||||
isActive
|
||||
? "text-primary-foreground"
|
||||
: "text-[hsl(var(--hero-ink)/0.62)] hover:text-[hsl(var(--hero-ink))] dark:text-muted-foreground dark:hover:text-foreground",
|
||||
: "text-foreground/70 hover:text-foreground",
|
||||
)}
|
||||
onClick={onNavigate}
|
||||
>
|
||||
{isActive ? (
|
||||
<motion.span
|
||||
layoutId="site-header-active-tab"
|
||||
className="absolute inset-0 -z-10 rounded-[var(--radius-pill)] bg-primary shadow-[0_14px_34px_-22px_hsl(var(--brand-primary)/0.65)]"
|
||||
className="absolute inset-0 -z-10 rounded-pill bg-primary shadow-xs"
|
||||
transition={{ type: "spring", stiffness: 380, damping: 30 }}
|
||||
/>
|
||||
) : null}
|
||||
@@ -96,11 +96,11 @@ export function SiteHeader({
|
||||
const t = useTranslations("navigation");
|
||||
const isArabic = locale === "ar";
|
||||
const desktopControlRailClassName =
|
||||
"inline-flex items-center gap-1 rounded-[var(--radius-pill)] border border-black/6 bg-white/24 p-1 shadow-[0_14px_34px_-24px_rgba(15,23,42,0.18)] backdrop-blur-[18px] dark:border-white/10 dark:bg-white/4 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)]";
|
||||
"inline-flex items-center gap-1 rounded-pill border border-border/70 bg-background/80 p-1 shadow-panel backdrop-blur-chrome";
|
||||
const desktopControlButtonClassName =
|
||||
"h-9 w-9 rounded-full border border-transparent p-0 text-[hsl(var(--hero-ink)/0.76)] hover:border-black/5 hover:bg-white/36 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:border-white/10 dark:hover:bg-white/10";
|
||||
"h-9 w-9 rounded-pill border border-transparent p-0 text-foreground/80 hover:bg-accent hover:text-foreground";
|
||||
const mobileControlButtonClassName =
|
||||
"h-10 w-10 rounded-full border border-black/6 bg-white/32 p-0 text-[hsl(var(--hero-ink)/0.8)] hover:bg-white/42 hover:text-[hsl(var(--hero-ink))] dark:border-white/10 dark:bg-white/6 dark:text-foreground dark:hover:bg-white/10";
|
||||
"h-10 w-10 rounded-pill border border-border/70 bg-background text-foreground/80 shadow-xs hover:bg-accent hover:text-foreground";
|
||||
|
||||
return (
|
||||
<header className="fixed inset-x-0 top-3 z-40 bg-transparent">
|
||||
@@ -109,7 +109,7 @@ export function SiteHeader({
|
||||
<div className="col-start-1 flex items-center justify-self-start">
|
||||
<Link
|
||||
href={getLocalizedPath(locale)}
|
||||
className="inline-flex items-center rounded-[var(--radius-pill)] px-1 py-1 sm:py-1.5 lg:py-2"
|
||||
className="inline-flex items-center rounded-pill px-1 py-1 sm:py-1.5 lg:py-2"
|
||||
>
|
||||
<SiteLogo
|
||||
lightLogoUrl={lightLogoUrl}
|
||||
@@ -127,7 +127,7 @@ export function SiteHeader({
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.28, ease: "easeOut" }}
|
||||
className={cn(
|
||||
"pointer-events-auto inline-flex items-center gap-1 rounded-[var(--radius-pill)] border border-black/6 bg-white/24 p-1.5 shadow-[0_14px_34px_-24px_rgba(15,23,42,0.18)] backdrop-blur-[18px] dark:border-white/10 dark:bg-white/4 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)]",
|
||||
"pointer-events-auto inline-flex items-center gap-1 rounded-pill border border-border/70 bg-background/80 p-1.5 shadow-panel backdrop-blur-chrome",
|
||||
isArabic ? "w-auto max-w-none" : "min-w-[27rem] max-w-[42rem]",
|
||||
)}
|
||||
>
|
||||
@@ -166,7 +166,7 @@ export function SiteHeader({
|
||||
onClick={() => setIsOpen((open) => !open)}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="col-start-3 ml-auto h-11 w-11 justify-self-end rounded-[var(--radius-pill)] border border-black/6 bg-white/24 text-[hsl(var(--hero-ink))] shadow-[0_14px_34px_-24px_rgba(15,23,42,0.18)] backdrop-blur-[18px] hover:bg-white/34 dark:border-white/10 dark:bg-white/4 dark:text-foreground dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)] dark:hover:bg-white/10 lg:hidden"
|
||||
className="col-start-3 ml-auto h-11 w-11 justify-self-end rounded-pill border border-border/70 bg-background/80 text-foreground shadow-panel backdrop-blur-chrome hover:bg-accent lg:hidden"
|
||||
aria-label={isOpen ? t("closeMenu") : t("openMenu")}
|
||||
>
|
||||
<motion.span
|
||||
@@ -195,7 +195,7 @@ export function SiteHeader({
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.98 }}
|
||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
||||
className="overflow-hidden rounded-surface border border-black/6 bg-[linear-gradient(180deg,rgba(255,255,255,0.46),rgba(255,255,255,0.24))] shadow-[inset_0_1px_0_rgba(255,255,255,0.54),0_16px_34px_-24px_rgba(15,23,42,0.24)] backdrop-blur-[20px] dark:border-white/12 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.09),rgba(255,255,255,0.03))] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.12),inset_0_-1px_0_rgba(255,255,255,0.03),0_20px_48px_-28px_rgba(0,0,0,0.72)]"
|
||||
className="overflow-hidden rounded-surface border border-border/70 bg-card/95 shadow-panel backdrop-blur-chrome"
|
||||
>
|
||||
<motion.nav
|
||||
initial="closed"
|
||||
@@ -235,8 +235,8 @@ export function SiteHeader({
|
||||
className={cn(
|
||||
"flex items-center justify-between rounded-nested px-4 py-3 text-sm font-medium transition-colors",
|
||||
isActive
|
||||
? "bg-white/72 text-[hsl(var(--hero-ink))] shadow-[0_10px_20px_-18px_rgba(15,23,42,0.24)] dark:bg-white/10 dark:text-foreground"
|
||||
: "text-[hsl(var(--hero-ink)/0.78)] hover:bg-white/40 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:bg-white/6",
|
||||
? "bg-accent text-foreground shadow-xs"
|
||||
: "text-foreground/80 hover:bg-accent/70 hover:text-foreground",
|
||||
)}
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
@@ -255,7 +255,7 @@ export function SiteHeader({
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 8 }}
|
||||
transition={{ duration: 0.18, ease: "easeOut", delay: 0.04 }}
|
||||
className="border-t border-black/6 bg-[linear-gradient(180deg,rgba(255,255,255,0.2),rgba(255,255,255,0.08))] p-2 dark:border-white/8 dark:bg-[linear-gradient(180deg,rgba(255,255,255,0.04),rgba(255,255,255,0.02))]"
|
||||
className="border-t border-border/70 bg-background/70 p-2"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<LocaleToggle
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -10,8 +10,8 @@ const appCardVariants = cva(
|
||||
variants: {
|
||||
level: {
|
||||
1: "border border-border/80 bg-card text-card-foreground shadow-card",
|
||||
2: "border border-border/80 bg-card text-card-foreground shadow-card",
|
||||
3: "border border-border/80 bg-card text-card-foreground shadow-panel",
|
||||
2: "border border-input bg-background text-foreground shadow-xs",
|
||||
3: "border border-border/80 bg-surface-2 text-foreground shadow-panel",
|
||||
inverse: "border-transparent bg-foreground text-background shadow-card",
|
||||
},
|
||||
padding: {
|
||||
|
||||
@@ -36,13 +36,13 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:w-full sm:rounded-lg",
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-surface border border-border/80 bg-card p-6 text-card-foreground shadow-panel duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:w-full",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2">
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 inline-flex h-8 w-8 items-center justify-center rounded-nested text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:pointer-events-none">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
@@ -56,7 +56,7 @@ const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
|
||||
<div className={cn("flex flex-col gap-1.5 text-center sm:text-left", className)} {...props} />
|
||||
);
|
||||
|
||||
const DialogFooter = ({
|
||||
|
||||
@@ -17,7 +17,7 @@ const SheetOverlay = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||
"fixed inset-0 z-50 bg-foreground/30 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -29,13 +29,13 @@ SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
||||
|
||||
const sheetVariants = {
|
||||
right:
|
||||
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right sm:max-w-sm",
|
||||
"inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm sm:rounded-l-surface data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right",
|
||||
left:
|
||||
"inset-y-0 left-0 h-full w-3/4 border-r data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left sm:max-w-sm",
|
||||
"inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm sm:rounded-r-surface data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left",
|
||||
top:
|
||||
"inset-x-0 top-0 border-b data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top",
|
||||
"inset-x-0 top-0 border-b rounded-b-surface data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top",
|
||||
bottom:
|
||||
"inset-x-0 bottom-0 border-t data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom",
|
||||
"inset-x-0 bottom-0 border-t rounded-t-surface data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom",
|
||||
};
|
||||
|
||||
type SheetContentProps = React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content> & {
|
||||
@@ -51,14 +51,14 @@ const SheetContent = React.forwardRef<
|
||||
<SheetPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||
"fixed z-50 gap-4 border-border/80 bg-card p-6 text-card-foreground shadow-panel transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||
sheetVariants[side],
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2">
|
||||
<SheetPrimitive.Close className="absolute right-4 top-4 inline-flex h-8 w-8 items-center justify-center rounded-nested text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:pointer-events-none">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
@@ -72,7 +72,7 @@ const SheetHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col space-y-2 text-left", className)} {...props} />
|
||||
<div className={cn("flex flex-col gap-2 text-left", className)} {...props} />
|
||||
);
|
||||
|
||||
const SheetTitle = React.forwardRef<
|
||||
|
||||
@@ -70,7 +70,7 @@ function TabsTrigger({
|
||||
type="button"
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center rounded-nested px-3 py-2 text-sm font-medium text-muted-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:pointer-events-none disabled:opacity-50",
|
||||
isActive && "bg-background text-foreground shadow-sm",
|
||||
isActive && "border border-border/80 bg-background text-foreground shadow-xs",
|
||||
className,
|
||||
)}
|
||||
onClick={(event) => {
|
||||
@@ -101,7 +101,7 @@ function TabsContent({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"mt-6 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
"mt-6 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -545,10 +545,10 @@ export function UiKitShowcase({ localeKey }: UiKitShowcaseProps) {
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{copy.workspaceMainText}</p>
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<div className="rounded-lg border border-input bg-background p-4 text-sm text-muted-foreground">
|
||||
<div className="rounded-nested border border-input bg-background p-4 text-sm text-muted-foreground">
|
||||
Step 1
|
||||
</div>
|
||||
<div className="rounded-lg border border-input bg-background p-4 text-sm text-muted-foreground">
|
||||
<div className="rounded-nested border border-input bg-background p-4 text-sm text-muted-foreground">
|
||||
Step 2
|
||||
</div>
|
||||
</div>
|
||||
@@ -562,7 +562,7 @@ export function UiKitShowcase({ localeKey }: UiKitShowcaseProps) {
|
||||
<div className="space-y-3">
|
||||
<Badge variant="success">Ready</Badge>
|
||||
<Badge variant="warning">Open</Badge>
|
||||
<div className="rounded-lg border border-input bg-background p-4 text-sm text-muted-foreground">
|
||||
<div className="rounded-nested border border-input bg-background p-4 text-sm text-muted-foreground">
|
||||
Checklist / status / focus details
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user