{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "console-preferences", "title": "Console Preferences and About", "description": "A DaisyUI appearance settings page with paired themes, searchable catalog, component previews, display preferences, persistence, and About", "dependencies": ["daisyui", "lucide-react"], "files": [ { "path": "registry/default/blocks/console-preferences/console-theme.tsx", "content": "'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport {\n\tassertConsoleThemeOptions,\n\ttype ConsoleThemeColorScheme,\n\ttype ConsoleThemeOption,\n\tdefaultConsoleThemeOptions,\n} from './console-theme-catalog';\n\nexport type { ConsoleThemeColorScheme, ConsoleThemeOption } from './console-theme-catalog';\nexport { defaultConsoleThemeOptions } from './console-theme-catalog';\n\nexport const DEFAULT_CONSOLE_DISPLAY_SETTINGS_KEY = 'console.display-settings.v1';\nexport const CONSOLE_DISPLAY_SETTINGS_EVENT = 'wener:console-display-settings';\n\nexport type ConsoleDisplayDensity = 'comfortable' | 'compact';\nexport type ConsoleDisplayRadius = 'square' | 'soft' | 'rounded';\nexport type ConsoleMotionPreference = 'system' | 'reduced' | 'full';\nexport type ConsoleThemeMode = 'system' | ConsoleThemeColorScheme;\n\nexport type ConsoleDisplaySettings = {\n\tthemeMode: ConsoleThemeMode;\n\tlightTheme: string;\n\tdarkTheme: string;\n\tdensity: ConsoleDisplayDensity;\n\tradius: ConsoleDisplayRadius;\n\tmotion: ConsoleMotionPreference;\n};\n\nexport type ConsoleDisplaySettingsEventDetail = {\n\tstorageKey: string;\n\tsettings: ConsoleDisplaySettings;\n};\n\nexport type ResolvedConsoleTheme = {\n\ttheme: string;\n\tcolorScheme: ConsoleThemeColorScheme;\n\toption: ConsoleThemeOption;\n};\n\nexport const defaultConsoleDisplaySettings: ConsoleDisplaySettings = {\n\tthemeMode: 'system',\n\tlightTheme: 'wener',\n\tdarkTheme: 'dark',\n\tdensity: 'comfortable',\n\tradius: 'soft',\n\tmotion: 'system',\n};\n\nexport function getDefaultConsoleDisplaySettings(\n\tthemeOptions: readonly ConsoleThemeOption[] = defaultConsoleThemeOptions,\n): ConsoleDisplaySettings {\n\tconst defaults = getConsoleThemeDefaults(themeOptions);\n\treturn {\n\t\t...defaultConsoleDisplaySettings,\n\t\tlightTheme: defaults.light.value,\n\t\tdarkTheme: defaults.dark.value,\n\t};\n}\n\nexport type ConsoleThemeControllerProps = {\n\tstorageKey?: string;\n\tthemeOptions?: readonly ConsoleThemeOption[];\n};\n\nexport function ConsoleThemeController({\n\tstorageKey = DEFAULT_CONSOLE_DISPLAY_SETTINGS_KEY,\n\tthemeOptions = defaultConsoleThemeOptions,\n}: ConsoleThemeControllerProps) {\n\tassertConsoleThemeOptions(themeOptions);\n\tuseEffect(() => {\n\t\tconst colorMedia = window.matchMedia('(prefers-color-scheme: dark)');\n\t\tconst motionMedia = window.matchMedia('(prefers-reduced-motion: reduce)');\n\t\tconst apply = () => applyConsoleDisplaySettings(loadConsoleDisplaySettings(storageKey, themeOptions), themeOptions);\n\t\tconst onStorage = (event: StorageEvent) => {\n\t\t\tif (event.key === storageKey) apply();\n\t\t};\n\t\tconst onSettings = (event: Event) => {\n\t\t\tconst detail = (event as CustomEvent).detail;\n\t\t\tif (detail?.storageKey === storageKey) applyConsoleDisplaySettings(detail.settings, themeOptions);\n\t\t};\n\t\tapply();\n\t\tcolorMedia.addEventListener('change', apply);\n\t\tmotionMedia.addEventListener('change', apply);\n\t\twindow.addEventListener('storage', onStorage);\n\t\twindow.addEventListener(CONSOLE_DISPLAY_SETTINGS_EVENT, onSettings);\n\t\treturn () => {\n\t\t\tcolorMedia.removeEventListener('change', apply);\n\t\t\tmotionMedia.removeEventListener('change', apply);\n\t\t\twindow.removeEventListener('storage', onStorage);\n\t\t\twindow.removeEventListener(CONSOLE_DISPLAY_SETTINGS_EVENT, onSettings);\n\t\t};\n\t}, [storageKey, themeOptions]);\n\treturn null;\n}\n\nexport function useConsoleDisplaySettings({\n\tstorageKey = DEFAULT_CONSOLE_DISPLAY_SETTINGS_KEY,\n\tthemeOptions = defaultConsoleThemeOptions,\n}: ConsoleThemeControllerProps = {}) {\n\tconst [settings, setSettings] = useState(() =>\n\t\tgetDefaultConsoleDisplaySettings(themeOptions),\n\t);\n\tconst settingsRef = useRef(settings);\n\tconst systemDark = useSyncExternalStore(\n\t\tsubscribeConsoleSystemColorScheme,\n\t\tgetConsoleSystemPrefersDark,\n\t\tgetConsoleServerPrefersDark,\n\t);\n\tconst systemReducedMotion = useSyncExternalStore(\n\t\tsubscribeConsoleSystemReducedMotion,\n\t\tgetConsoleSystemPrefersReducedMotion,\n\t\tgetConsoleServerPrefersReducedMotion,\n\t);\n\n\tuseEffect(() => {\n\t\tconst sync = () => {\n\t\t\tconst next = loadConsoleDisplaySettings(storageKey, themeOptions);\n\t\t\tsettingsRef.current = next;\n\t\t\tsetSettings(next);\n\t\t\tapplyConsoleDisplaySettings(next, themeOptions);\n\t\t};\n\t\tconst onStorage = (event: StorageEvent) => {\n\t\t\tif (event.key === storageKey) sync();\n\t\t};\n\t\tconst onSettings = (event: Event) => {\n\t\t\tconst detail = (event as CustomEvent).detail;\n\t\t\tif (detail?.storageKey !== storageKey) return;\n\t\t\tconst next = normalizeConsoleDisplaySettings(detail.settings, themeOptions);\n\t\t\tsettingsRef.current = next;\n\t\t\tsetSettings(next);\n\t\t\tapplyConsoleDisplaySettings(next, themeOptions);\n\t\t};\n\t\tsync();\n\t\twindow.addEventListener('storage', onStorage);\n\t\twindow.addEventListener(CONSOLE_DISPLAY_SETTINGS_EVENT, onSettings);\n\t\treturn () => {\n\t\t\twindow.removeEventListener('storage', onStorage);\n\t\t\twindow.removeEventListener(CONSOLE_DISPLAY_SETTINGS_EVENT, onSettings);\n\t\t};\n\t}, [storageKey, themeOptions]);\n\n\tuseEffect(() => {\n\t\tapplyConsoleDisplaySettings(settingsRef.current, themeOptions);\n\t}, [systemDark, systemReducedMotion, themeOptions]);\n\n\tconst replace = useCallback(\n\t\t(next: ConsoleDisplaySettings) => {\n\t\t\tconst normalized = normalizeConsoleDisplaySettings(next, themeOptions);\n\t\t\tsettingsRef.current = normalized;\n\t\t\tsetSettings(normalized);\n\t\t\tsaveConsoleDisplaySettings(normalized, storageKey, themeOptions);\n\t\t\tapplyConsoleDisplaySettings(normalized, themeOptions);\n\t\t},\n\t\t[storageKey, themeOptions],\n\t);\n\n\tconst update = useCallback(\n\t\t(patch: Partial) =>\n\t\t\treplace(patchConsoleDisplaySettings(settingsRef.current, patch, themeOptions)),\n\t\t[replace, themeOptions],\n\t);\n\n\tconst reset = useCallback(() => replace(getDefaultConsoleDisplaySettings(themeOptions)), [replace, themeOptions]);\n\tconst resolvedTheme = useMemo(\n\t\t() => resolveConsoleTheme(settings, themeOptions, systemDark),\n\t\t[settings, systemDark, themeOptions],\n\t);\n\tconst motionReduced = resolveConsoleMotion(settings, systemReducedMotion);\n\treturn { settings, systemDark, systemReducedMotion, motionReduced, resolvedTheme, update, reset } as const;\n}\n\nexport function normalizeConsoleDisplaySettings(\n\tinput?: unknown,\n\tthemeOptions: readonly ConsoleThemeOption[] = defaultConsoleThemeOptions,\n): ConsoleDisplaySettings {\n\tconst defaults = getConsoleThemeDefaults(themeOptions);\n\tconst value = isRecord(input) ? input : {};\n\tconst legacyTheme = typeof value.theme === 'string' && value.theme ? value.theme : undefined;\n\tlet themeMode: ConsoleThemeMode =\n\t\tvalue.themeMode === 'light' || value.themeMode === 'dark' || value.themeMode === 'system'\n\t\t\t? value.themeMode\n\t\t\t: 'system';\n\tlet lightTheme = typeof value.lightTheme === 'string' && value.lightTheme ? value.lightTheme : undefined;\n\tlet darkTheme = typeof value.darkTheme === 'string' && value.darkTheme ? value.darkTheme : undefined;\n\n\tif (!('themeMode' in value) && legacyTheme) {\n\t\tif (legacyTheme === 'system') {\n\t\t\tthemeMode = 'system';\n\t\t} else {\n\t\t\tconst legacyOption =\n\t\t\t\tthemeOptions.find((option) => option.value === legacyTheme) ??\n\t\t\t\tdefaultConsoleThemeOptions.find((option) => option.value === legacyTheme);\n\t\t\tthemeMode = legacyOption?.colorScheme ?? 'light';\n\t\t\tif (themeMode === 'dark') darkTheme = legacyTheme;\n\t\t\telse lightTheme = legacyTheme;\n\t\t}\n\t}\n\n\treturn {\n\t\tthemeMode,\n\t\tlightTheme: normalizeThemeSlot(lightTheme, 'light', defaults.light, themeOptions),\n\t\tdarkTheme: normalizeThemeSlot(darkTheme, 'dark', defaults.dark, themeOptions),\n\t\tdensity: value.density === 'compact' ? 'compact' : 'comfortable',\n\t\tradius: value.radius === 'square' || value.radius === 'rounded' ? value.radius : 'soft',\n\t\tmotion:\n\t\t\tvalue.motion === 'reduced' || value.motion === 'full' || value.motion === 'system'\n\t\t\t\t? value.motion\n\t\t\t\t: 'reducedMotion' in value\n\t\t\t\t\t? value.reducedMotion === true\n\t\t\t\t\t\t? 'reduced'\n\t\t\t\t\t\t: 'full'\n\t\t\t\t\t: 'system',\n\t};\n}\n\nexport function patchConsoleDisplaySettings(\n\tsettings: ConsoleDisplaySettings,\n\tpatch: Partial,\n\tthemeOptions: readonly ConsoleThemeOption[] = defaultConsoleThemeOptions,\n) {\n\treturn normalizeConsoleDisplaySettings({ ...settings, ...patch }, themeOptions);\n}\n\nexport function resolveConsoleTheme(\n\tsettings: ConsoleDisplaySettings,\n\tthemeOptions: readonly ConsoleThemeOption[] = defaultConsoleThemeOptions,\n\tsystemDark = getConsoleSystemPrefersDark(),\n): ResolvedConsoleTheme {\n\tconst normalized = normalizeConsoleDisplaySettings(settings, themeOptions);\n\tconst colorScheme: ConsoleThemeColorScheme =\n\t\tnormalized.themeMode === 'system' ? (systemDark ? 'dark' : 'light') : normalized.themeMode;\n\tconst requestedTheme = colorScheme === 'dark' ? normalized.darkTheme : normalized.lightTheme;\n\tconst defaults = getConsoleThemeDefaults(themeOptions);\n\tconst fallback = colorScheme === 'dark' ? defaults.dark : defaults.light;\n\tconst listedOption = themeOptions.find((theme) => theme.value === requestedTheme);\n\tconst option =\n\t\tlistedOption?.colorScheme === colorScheme\n\t\t\t? listedOption\n\t\t\t: listedOption\n\t\t\t\t? fallback\n\t\t\t\t: ({\n\t\t\t\t\t\tvalue: requestedTheme,\n\t\t\t\t\t\tlabel: requestedTheme,\n\t\t\t\t\t\tcolorScheme,\n\t\t\t\t\t\tdescription: '自定义主题',\n\t\t\t\t\t} satisfies ConsoleThemeOption);\n\treturn { theme: option.value, colorScheme: option.colorScheme, option };\n}\n\nexport function getConsoleSystemPrefersDark() {\n\treturn getConsoleMediaPreference('(prefers-color-scheme: dark)');\n}\n\nexport function getConsoleSystemPrefersReducedMotion() {\n\treturn getConsoleMediaPreference('(prefers-reduced-motion: reduce)');\n}\n\nexport function resolveConsoleMotion(\n\tsettings: Pick,\n\tsystemReducedMotion = getConsoleSystemPrefersReducedMotion(),\n) {\n\treturn settings.motion === 'system' ? systemReducedMotion : settings.motion === 'reduced';\n}\n\nfunction getConsoleServerPrefersDark() {\n\treturn false;\n}\n\nfunction getConsoleServerPrefersReducedMotion() {\n\treturn false;\n}\n\nfunction subscribeConsoleSystemColorScheme(onChange: () => void) {\n\treturn subscribeConsoleMediaPreference('(prefers-color-scheme: dark)', onChange);\n}\n\nfunction subscribeConsoleSystemReducedMotion(onChange: () => void) {\n\treturn subscribeConsoleMediaPreference('(prefers-reduced-motion: reduce)', onChange);\n}\n\nfunction getConsoleMediaPreference(query: string) {\n\treturn typeof window !== 'undefined' && typeof window.matchMedia === 'function' && window.matchMedia(query).matches;\n}\n\nfunction subscribeConsoleMediaPreference(query: string, onChange: () => void) {\n\tif (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return () => undefined;\n\tconst media = window.matchMedia(query);\n\tmedia.addEventListener('change', onChange);\n\treturn () => media.removeEventListener('change', onChange);\n}\n\nexport function loadConsoleDisplaySettings(\n\tstorageKey = DEFAULT_CONSOLE_DISPLAY_SETTINGS_KEY,\n\tthemeOptions: readonly ConsoleThemeOption[] = defaultConsoleThemeOptions,\n): ConsoleDisplaySettings {\n\tconst defaults = getDefaultConsoleDisplaySettings(themeOptions);\n\tif (typeof window === 'undefined') return defaults;\n\ttry {\n\t\tconst raw = window.localStorage.getItem(storageKey);\n\t\tif (!raw) return defaults;\n\t\tconst parsed = JSON.parse(raw);\n\t\tconst normalized = normalizeConsoleDisplaySettings(parsed, themeOptions);\n\t\tconst canonical = JSON.stringify(normalized);\n\t\tif (!isCanonicalConsoleDisplaySettings(parsed) || JSON.stringify(parsed) !== canonical) {\n\t\t\ttry {\n\t\t\t\twindow.localStorage.setItem(storageKey, canonical);\n\t\t\t} catch {\n\t\t\t\t// Parsed settings remain usable when migration cannot be persisted.\n\t\t\t}\n\t\t}\n\t\treturn normalized;\n\t} catch {\n\t\treturn defaults;\n\t}\n}\n\nexport function saveConsoleDisplaySettings(\n\tsettings: ConsoleDisplaySettings,\n\tstorageKey = DEFAULT_CONSOLE_DISPLAY_SETTINGS_KEY,\n\tthemeOptions: readonly ConsoleThemeOption[] = defaultConsoleThemeOptions,\n) {\n\tif (typeof window === 'undefined') return;\n\tconst normalized = normalizeConsoleDisplaySettings(settings, themeOptions);\n\ttry {\n\t\twindow.localStorage.setItem(storageKey, JSON.stringify(normalized));\n\t} catch {\n\t\t// Keep the current document reactive when storage is unavailable.\n\t}\n\twindow.dispatchEvent(\n\t\tnew CustomEvent(CONSOLE_DISPLAY_SETTINGS_EVENT, {\n\t\t\tdetail: { storageKey, settings: normalized },\n\t\t}),\n\t);\n}\n\nexport function applyConsoleDisplaySettings(\n\tsettings: ConsoleDisplaySettings,\n\tthemeOptions: readonly ConsoleThemeOption[] = defaultConsoleThemeOptions,\n\troot: HTMLElement | undefined = typeof document === 'undefined' ? undefined : document.documentElement,\n): ResolvedConsoleTheme {\n\tconst normalized = normalizeConsoleDisplaySettings(settings, themeOptions);\n\tconst resolved = resolveConsoleTheme(normalized, themeOptions);\n\tif (!root) return resolved;\n\n\troot.dataset.theme = resolved.theme;\n\troot.dataset.themeMode = normalized.themeMode;\n\troot.dataset.themePreference = normalized.themeMode;\n\troot.dataset.lightTheme = normalized.lightTheme;\n\troot.dataset.darkTheme = normalized.darkTheme;\n\troot.dataset.colorMode = resolved.colorScheme;\n\troot.dataset.density = normalized.density;\n\troot.dataset.radius = normalized.radius;\n\troot.dataset.motionPreference = normalized.motion;\n\troot.dataset.motion = resolveConsoleMotion(normalized) ? 'reduced' : 'normal';\n\troot.classList.toggle('dark', resolved.colorScheme === 'dark');\n\troot.style.colorScheme = resolved.colorScheme;\n\treturn resolved;\n}\n\nfunction getConsoleThemeDefaults(themeOptions: readonly ConsoleThemeOption[]) {\n\tassertConsoleThemeOptions(themeOptions);\n\tconst light = themeOptions.find((theme) => theme.colorScheme === 'light');\n\tconst dark = themeOptions.find((theme) => theme.colorScheme === 'dark');\n\tif (!light || !dark) throw new Error('Console theme option validation failed.');\n\treturn { light, dark };\n}\n\nfunction normalizeThemeSlot(\n\tvalue: string | undefined,\n\tcolorScheme: ConsoleThemeColorScheme,\n\tfallback: ConsoleThemeOption,\n\tthemeOptions: readonly ConsoleThemeOption[],\n) {\n\tif (!value) return fallback.value;\n\tconst listedOption = themeOptions.find((theme) => theme.value === value);\n\tif (listedOption) return listedOption.colorScheme === colorScheme ? value : fallback.value;\n\tconst builtInOption = defaultConsoleThemeOptions.find((theme) => theme.value === value);\n\treturn builtInOption ? fallback.value : value;\n}\n\nfunction isCanonicalConsoleDisplaySettings(value: unknown): value is ConsoleDisplaySettings {\n\tif (!isRecord(value)) return false;\n\treturn (\n\t\t(value.themeMode === 'system' || value.themeMode === 'light' || value.themeMode === 'dark') &&\n\t\ttypeof value.lightTheme === 'string' &&\n\t\ttypeof value.darkTheme === 'string' &&\n\t\t(value.density === 'comfortable' || value.density === 'compact') &&\n\t\t(value.radius === 'square' || value.radius === 'soft' || value.radius === 'rounded') &&\n\t\t(value.motion === 'system' || value.motion === 'reduced' || value.motion === 'full')\n\t);\n}\n\nfunction isRecord(value: unknown): value is Record {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n", "type": "registry:component", "target": "@components/console-preferences/console-theme.tsx" }, { "path": "registry/default/blocks/console-preferences/console-theme-catalog.ts", "content": "export type ConsoleThemeColorScheme = 'light' | 'dark';\n\nexport type ConsoleThemeOption = {\n\tvalue: string;\n\tlabel: string;\n\tcolorScheme: ConsoleThemeColorScheme;\n\tdescription?: string;\n};\n\nexport const defaultConsoleThemeOptions = [\n\t{ value: 'wener', label: 'Wener', colorScheme: 'light', description: '清晰的青蓝工作界面' },\n\t{ value: 'light', label: '亮色', colorScheme: 'light' },\n\t{ value: 'dark', label: '暗色', colorScheme: 'dark' },\n\t{ value: 'cupcake', label: '杯子蛋糕', colorScheme: 'light' },\n\t{ value: 'bumblebee', label: '大黄蜂', colorScheme: 'light' },\n\t{ value: 'emerald', label: '绿宝石', colorScheme: 'light' },\n\t{ value: 'corporate', label: '企业', colorScheme: 'light' },\n\t{ value: 'synthwave', label: '合成波', colorScheme: 'dark' },\n\t{ value: 'retro', label: '复古', colorScheme: 'light' },\n\t{ value: 'cyberpunk', label: '赛博朋克', colorScheme: 'light' },\n\t{ value: 'valentine', label: '情人节', colorScheme: 'light' },\n\t{ value: 'halloween', label: '万圣节', colorScheme: 'dark' },\n\t{ value: 'garden', label: '花园', colorScheme: 'light' },\n\t{ value: 'forest', label: '森林', colorScheme: 'dark' },\n\t{ value: 'aqua', label: '水色', colorScheme: 'dark' },\n\t{ value: 'lofi', label: '低保真', colorScheme: 'light' },\n\t{ value: 'pastel', label: '粉彩', colorScheme: 'light' },\n\t{ value: 'fantasy', label: '幻想', colorScheme: 'light' },\n\t{ value: 'wireframe', label: '线框', colorScheme: 'light' },\n\t{ value: 'black', label: '黑色', colorScheme: 'dark' },\n\t{ value: 'luxury', label: '奢华', colorScheme: 'dark' },\n\t{ value: 'dracula', label: '德古拉', colorScheme: 'dark' },\n\t{ value: 'cmyk', label: 'CMYK', colorScheme: 'light' },\n\t{ value: 'autumn', label: '秋天', colorScheme: 'light' },\n\t{ value: 'business', label: '商务', colorScheme: 'dark' },\n\t{ value: 'acid', label: '酸性', colorScheme: 'light' },\n\t{ value: 'lemonade', label: '柠檬汽水', colorScheme: 'light' },\n\t{ value: 'night', label: '夜晚', colorScheme: 'dark' },\n\t{ value: 'coffee', label: '咖啡', colorScheme: 'dark' },\n\t{ value: 'winter', label: '冬天', colorScheme: 'light' },\n\t{ value: 'dim', label: '暗淡', colorScheme: 'dark' },\n\t{ value: 'nord', label: '北欧', colorScheme: 'light' },\n\t{ value: 'sunset', label: '日落', colorScheme: 'dark' },\n\t{ value: 'caramellatte', label: '焦糖拿铁', colorScheme: 'light' },\n\t{ value: 'abyss', label: '深渊', colorScheme: 'dark' },\n\t{ value: 'silk', label: '丝绸', colorScheme: 'light' },\n] as const satisfies readonly ConsoleThemeOption[];\n\nexport function getConsoleThemesByColorScheme(\n\tthemeOptions: readonly ConsoleThemeOption[],\n\tcolorScheme: ConsoleThemeColorScheme,\n) {\n\treturn themeOptions.filter((theme) => theme.colorScheme === colorScheme);\n}\n\nexport function filterConsoleThemeOptions(themeOptions: readonly ConsoleThemeOption[], query: string) {\n\tconst normalizedQuery = query.trim().toLocaleLowerCase();\n\tif (!normalizedQuery) return [...themeOptions];\n\treturn themeOptions.filter((theme) =>\n\t\t[theme.label, theme.value, theme.description]\n\t\t\t.filter((value): value is string => Boolean(value))\n\t\t\t.some((value) => value.toLocaleLowerCase().includes(normalizedQuery)),\n\t);\n}\n\nexport function assertConsoleThemeOptions(themeOptions: readonly ConsoleThemeOption[]) {\n\tconst values = new Set();\n\tfor (const theme of themeOptions) {\n\t\tif (values.has(theme.value)) {\n\t\t\tthrow new Error(`Console theme options must use unique values; found duplicate \"${theme.value}\".`);\n\t\t}\n\t\tvalues.add(theme.value);\n\t}\n\tif (!themeOptions.some((theme) => theme.colorScheme === 'light')) {\n\t\tthrow new Error('Console theme options must include at least one light theme.');\n\t}\n\tif (!themeOptions.some((theme) => theme.colorScheme === 'dark')) {\n\t\tthrow new Error('Console theme options must include at least one dark theme.');\n\t}\n}\n", "type": "registry:component", "target": "@components/console-preferences/console-theme-catalog.ts" }, { "path": "registry/default/blocks/console-preferences/console-theme-preview.tsx", "content": "'use client';\n\nimport { Bell, Check, Command, Database, LayoutGrid, Moon, PanelRight, Search, Settings, Sun } from 'lucide-react';\nimport type { ComponentPropsWithRef, CSSProperties } from 'react';\nimport { cn } from '@/lib/utils';\nimport type { ConsoleDisplayDensity, ConsoleDisplayRadius } from './console-theme';\nimport type { ConsoleThemeOption } from './console-theme-catalog';\n\ntype ConsoleThemePreviewStyle = CSSProperties & {\n\t'--radius-box'?: string;\n\t'--radius-field'?: string;\n\t'--radius-selector'?: string;\n};\n\nconst consoleThemePreviewRadiusStyle: Record = {\n\tsquare: {\n\t\t'--radius-box': '0.125rem',\n\t\t'--radius-field': '0.125rem',\n\t\t'--radius-selector': '0.125rem',\n\t},\n\tsoft: {\n\t\t'--radius-box': '0.5rem',\n\t\t'--radius-field': '0.25rem',\n\t\t'--radius-selector': '0.5rem',\n\t},\n\trounded: {\n\t\t'--radius-box': '0.75rem',\n\t\t'--radius-field': '0.5rem',\n\t\t'--radius-selector': '0.75rem',\n\t},\n};\n\nexport function getConsoleThemePreviewStyle(radius: ConsoleDisplayRadius, style?: CSSProperties) {\n\treturn { ...consoleThemePreviewRadiusStyle[radius], ...style } satisfies ConsoleThemePreviewStyle;\n}\n\nexport type ConsoleThemePreviewCardProps = Omit, 'onChange' | 'onSelect'> & {\n\ttheme: ConsoleThemeOption;\n\tselected?: boolean;\n\tname?: string;\n\tonThemeSelect?: (theme: ConsoleThemeOption) => void;\n};\n\nexport function ConsoleThemePreviewCard({\n\ttheme,\n\tselected,\n\tname = 'console-theme',\n\tonThemeSelect,\n\tclassName,\n\t...props\n}: ConsoleThemePreviewCardProps) {\n\treturn (\n\t\t\n\t\t\t onThemeSelect?.(theme)}\n\t\t\t/>\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{theme.label}\n\t\t\t\t\t{theme.value}\n\t\t\t\t\n\t\t\t\t{theme.colorScheme === 'dark' ? 'Dark' : 'Light'}\n\t\t\t\t{selected ? : null}\n\t\t\t\n\t\t\t