import { MoonIcon, SunIcon } from "lucide-react"; import type { CSSProperties } from "react"; import { STANDARD_THEME_PREVIEW_COLORS as SHARED_STANDARD_THEME_PREVIEW_COLORS, THEME_PREVIEW_RENDER_SPECS, } from "@t3tools/shared/themePreview"; import { cn } from "../../lib/utils"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { getThemeColorsForMode, getThemeModes, type ThemeAppearance, type ThemeDefinition, } from "../../themePalette"; const THEME_PREVIEW_ROLES = [ "sidebar", "canvas", "surface", "accentSurface", "accent", "messageSurface", "messageAction", ] as const; type ThemePreviewRole = (typeof THEME_PREVIEW_ROLES)[number]; type ThemeCardPreview = { mode: ThemeAppearance; colors: Readonly>; }; export type ThemeCardDefinition = { id: string; label: string; previews: ReadonlyArray; }; export type ThemeMode = ThemeAppearance | "system"; export type ThemeCardPreviewColors = ThemeCardPreview["colors"]; const STANDARD_THEME_PREVIEW_COLORS: Record< ThemeAppearance, Readonly> > = { light: { sidebar: "#fafafa", surface: "#ffffff", accentSurface: "#f4f4f5", messageSurface: "#e4e4e7", ...SHARED_STANDARD_THEME_PREVIEW_COLORS.light, }, dark: { sidebar: "#0f0f10", surface: "#121212", accentSurface: "#27272a", messageSurface: "#27272a", ...SHARED_STANDARD_THEME_PREVIEW_COLORS.dark, }, }; export const STANDARD_THEME_CARDS: ReadonlyArray = [ { id: "default", label: "T3 Code", previews: (["light", "dark"] as const).map((mode) => ({ mode, colors: STANDARD_THEME_PREVIEW_COLORS[mode], })), }, ]; export function previewColorsOf( card: ThemeCardDefinition, mode: ThemeAppearance, ): ThemeCardPreviewColors | null { return card.previews.find((preview) => preview.mode === mode)?.colors ?? null; } export function getThemeCardDefinition(theme: ThemeDefinition): ThemeCardDefinition { return { id: theme.id, label: theme.label, previews: getThemeModes(theme).map((mode) => { const colors = getThemeColorsForMode(theme, mode) ?? theme.colors; return { mode, colors: { sidebar: colors.sidebar, canvas: colors.canvas, surface: colors.surface, accentSurface: colors.accentSurface, accent: colors.accent, messageSurface: colors.messageSurface, messageAction: colors.messageAction, }, }; }), }; } // Interpolating in oklab keeps the glow falloff perceptually even (no gray // mid-tones or banding rings), and premultiplied alpha keeps the fade to // transparent clean. function getThemePreviewStyle( colors: ThemeCardPreviewColors, mode: ThemeAppearance, ): CSSProperties { const spec = THEME_PREVIEW_RENDER_SPECS[mode]; // The canvas carries the ball's light/dark identity, so it stays dominant: // a near-true base with a contained accent glow, instead of an accent wash // that makes both modes read alike. const modeBase = `color-mix(in oklab, ${colors.canvas} ${spec.baseWeight * 100}%, ${spec.baseTarget})`; const accentPosition = `${spec.accent.center[0] * 100}% ${spec.accent.center[1] * 100}%`; const actionPosition = `${spec.action.center[0] * 100}% ${spec.action.center[1] * 100}%`; return { backgroundColor: modeBase, backgroundImage: [ `radial-gradient(circle at ${accentPosition} in oklab, ${colors.accent} 0%, color-mix(in oklab, ${colors.accent} ${spec.accent.middleOpacity * 100}%, transparent) ${spec.accent.middleOffset * 100}%, transparent ${spec.accent.endOffset * 100}%)`, // The action color is a soft tint from the opposite corner, not a second // light source — two bright hotspots read as headlights. `radial-gradient(circle at ${actionPosition} in oklab, color-mix(in oklab, ${colors.messageAction} ${spec.action.startOpacity * 100}%, transparent) 0%, transparent ${spec.action.endOffset * 100}%)`, ].join(", "), }; } // The gradient halves of each ball can match the card surface, so every ball // carries a faint mode-appropriate inner ring to keep its silhouette legible. function themePreviewEdgeShadow(mode: ThemeAppearance): string { return mode === "dark" ? "inset 0 0 0 1px rgb(255 255 255 / 0.14), 0 1px 2px rgb(0 0 0 / 0.18)" : "inset 0 0 0 1px rgb(0 0 0 / 0.10), 0 1px 2px rgb(0 0 0 / 0.08)"; } export function ThemePreviewCircle({ colors, mode, }: { colors: ThemeCardPreviewColors; mode: ThemeAppearance; }) { return ( ); } /** * A theme card's light and dark balls. Clicking a ball assigns that theme to * that half of the appearance mix; assigned balls carry a ring and a sun or * moon badge. */ export function ThemePreviewCircles({ label, activeModes, onSelectMode, previews, }: { label: string; activeModes: ReadonlyArray; onSelectMode: (mode: ThemeMode) => void; previews: ThemeCardDefinition["previews"]; }) { return (
{previews.map((preview) => { const mode = preview.mode; const isPicked = activeModes.includes(mode); return ( { event.stopPropagation(); onSelectMode(mode); }} type="button" > {isPicked ? ( <> {mode === "light" ? ( ) : ( )} ) : null} } /> {mode === "light" ? "Use for light mode only" : "Use for dark mode only"} ); })}
); }