This commit includes the temporary Next.js build cache files, public brand logos, and project configuration added in this change.
185 lines
6.4 KiB
TypeScript
185 lines
6.4 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useTheme } from "next-themes";
|
|
import { THEMES } from "@/lib/themes";
|
|
import { useThemeId } from "@/lib/use-theme-id";
|
|
import { Check, Moon, Sun } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
/**
|
|
* ThemePicker — the only theme control in the app.
|
|
*
|
|
* One row: 4 base themes. Below: a single light/dark toggle. No custom
|
|
* theme builder, no standout vs standard split — every theme is a
|
|
* first-class base palette inspired by a real product surface.
|
|
*/
|
|
export function ThemePicker() {
|
|
const { theme: mode, setTheme: setMode } = useTheme();
|
|
const { themeId, setThemeId } = useThemeId();
|
|
const [mounted, setMounted] = useState(false);
|
|
|
|
useEffect(() => setMounted(true), []); // eslint-disable-line react-hooks/set-state-in-effect
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
{/* Base themes */}
|
|
<div className="space-y-3">
|
|
<div className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
|
|
Theme
|
|
</div>
|
|
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
{THEMES.map((t) => (
|
|
<ThemeCard
|
|
key={t.id}
|
|
theme={t}
|
|
active={mounted ? themeId === t.id : t.id === "wfdash"}
|
|
onSelect={() => setThemeId(t.id)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Light / dark toggle */}
|
|
<div className="space-y-3">
|
|
<div className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
|
|
Mode
|
|
</div>
|
|
<div className="inline-grid max-w-sm grid-cols-2 gap-2">
|
|
<button
|
|
type="button"
|
|
onClick={() => setMode("light")}
|
|
className={cn(
|
|
"flex items-center justify-center gap-2 rounded-lg border px-4 py-2.5 text-sm font-medium transition-colors",
|
|
mode === "light"
|
|
? "border-primary bg-primary text-primary-foreground"
|
|
: "border-border bg-card hover:bg-accent"
|
|
)}
|
|
>
|
|
<Sun className="h-4 w-4" />
|
|
Light
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => setMode("dark")}
|
|
className={cn(
|
|
"flex items-center justify-center gap-2 rounded-lg border px-4 py-2.5 text-sm font-medium transition-colors",
|
|
mode === "dark"
|
|
? "border-primary bg-primary text-primary-foreground"
|
|
: "border-border bg-card hover:bg-accent"
|
|
)}
|
|
>
|
|
<Moon className="h-4 w-4" />
|
|
Dark
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ThemeCard({
|
|
theme,
|
|
active,
|
|
onSelect,
|
|
}: {
|
|
theme: (typeof THEMES)[number];
|
|
active: boolean;
|
|
onSelect: () => void;
|
|
}) {
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={onSelect}
|
|
className={cn(
|
|
"group relative flex flex-col gap-2 rounded-xl border-2 bg-card p-3 text-left transition-all hover:shadow-md",
|
|
active ? "border-primary shadow-sm" : "border-border hover:border-primary/40"
|
|
)}
|
|
>
|
|
{/* Mini preview */}
|
|
<div
|
|
className="relative w-full overflow-hidden rounded-lg border border-border/60"
|
|
style={{ background: theme.surface, aspectRatio: "16 / 9" }}
|
|
>
|
|
{/* Fake window chrome */}
|
|
<div
|
|
className="flex items-center gap-1 border-b px-2 py-1.5"
|
|
style={{ borderColor: theme.surface === "#FFFFFF" ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.10)" }}
|
|
>
|
|
<span className="h-1.5 w-1.5 rounded-full" style={{ background: "#FF5F57" }} />
|
|
<span className="h-1.5 w-1.5 rounded-full" style={{ background: "#FEBC2E" }} />
|
|
<span className="h-1.5 w-1.5 rounded-full" style={{ background: "#28C840" }} />
|
|
</div>
|
|
{/* Fake content */}
|
|
<div className="grid grid-cols-3 gap-1.5 p-2">
|
|
<div
|
|
className="col-span-1 h-12 rounded"
|
|
style={{ background: theme.accent, opacity: 0.92 }}
|
|
/>
|
|
<div
|
|
className="col-span-1 h-12 rounded"
|
|
style={{ background: theme.ink, opacity: 0.10 }}
|
|
/>
|
|
<div
|
|
className="col-span-1 h-12 rounded"
|
|
style={{ background: theme.accent, opacity: 0.4 }}
|
|
/>
|
|
<div
|
|
className="col-span-2 h-6 rounded"
|
|
style={{ background: theme.ink, opacity: 0.14 }}
|
|
/>
|
|
<div
|
|
className="col-span-1 h-6 rounded"
|
|
style={{ background: theme.accent, opacity: 0.7 }}
|
|
/>
|
|
<div
|
|
className="col-span-3 h-2 rounded"
|
|
style={{ background: theme.ink, opacity: 0.18 }}
|
|
/>
|
|
<div
|
|
className="col-span-2 h-2 rounded"
|
|
style={{ background: theme.ink, opacity: 0.10 }}
|
|
/>
|
|
</div>
|
|
{active && (
|
|
<div className="absolute right-2 top-2 grid h-6 w-6 place-items-center rounded-full bg-primary text-primary-foreground shadow">
|
|
<Check className="h-3.5 w-3.5" />
|
|
</div>
|
|
)}
|
|
{/* Tri-color brand stripe — only the WFDASH card gets this. Sits
|
|
at the bottom of the preview so the picker advertises the
|
|
tri-color brand identity of the WFDASH theme. */}
|
|
{theme.id === "wfdash" && (
|
|
<div
|
|
aria-hidden="true"
|
|
className="absolute inset-x-0 bottom-0 h-1.5"
|
|
style={{
|
|
background:
|
|
"linear-gradient(90deg, #377DCF 0% 28%, #ED8C31 50%, #E73631 72% 100%)",
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
<div className="space-y-0.5">
|
|
<div className="flex items-center gap-2">
|
|
<div className="text-sm font-semibold">{theme.label}</div>
|
|
<span className="rounded-full bg-muted px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-widest text-muted-foreground">
|
|
{theme.reference}
|
|
</span>
|
|
{theme.id === "wfdash" && (
|
|
<span
|
|
className="rounded-full px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-widest text-white"
|
|
style={{
|
|
background:
|
|
"linear-gradient(90deg, #377DCF 0% 33%, #ED8C31 33% 66%, #E73631 66% 100%)",
|
|
}}
|
|
>
|
|
Default
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="text-xs leading-snug text-muted-foreground">{theme.tagline}</div>
|
|
</div>
|
|
</button>
|
|
);
|
|
}
|