返回 DeepSeek-Reasonix
ThemePreviewSurface.tsx
根目录 / desktop / frontend / src / components / ThemePreviewSurface.tsx
1 import { useMemo, type CSSProperties } from "react";
2 import { deriveCodeReadabilityPalette, type CodeReadabilityPalette } from "../lib/codeReadability";
3 import { themePackKind, type ThemePackView } from "../lib/themePack";
4 import { baseStyleForPreview, themePreviewPalette } from "../lib/themePreviewPalette";
5
6 export function themePreviewPaneAlpha(pack: ThemePackView | null, scene: "home" | "task"): number {
7 // Live pane transparency is guarded by data-theme-has-bg. Keep token-only
8 // and base previews opaque so the isolated preview matches the applied UI.
9 if (pack?.hasBackground !== true) return 1;
10 const paneAlpha = scene === "home"
11 ? pack.background?.paneOpacity ?? 0.50
12 : pack.taskBackground?.paneOpacity ?? pack.background?.paneOpacity ?? 0.68;
13 return Math.min(1, Math.max(0, paneAlpha));
14 }
15
16 export function themePreviewCodePalette(
17 pack: ThemePackView | null,
18 mode: "light" | "dark",
19 ): CodeReadabilityPalette {
20 const tokens = mode === "light" ? pack?.tokens?.light : pack?.tokens?.dark;
21 return deriveCodeReadabilityPalette(mode, baseStyleForPreview(pack), tokens || {});
22 }
23
24 /** Isolated mini Reasonix surface for gallery detail — does not touch :root. */
25 export function ThemePreviewSurface({
26 pack,
27 mode,
28 scene,
29 variant = "full",
30 }: {
31 pack: ThemePackView | null;
32 mode: "light" | "dark";
33 scene: "home" | "task";
34 variant?: "full" | "thumbnail";
35 }) {
36 const isBasePreview = Boolean(pack && themePackKind(pack) === "base");
37 const baseStyle = baseStyleForPreview(pack);
38 const style = useMemo(() => {
39 const palette = themePreviewPalette(pack, mode);
40 const tokens = mode === "light" ? pack?.tokens?.light : pack?.tokens?.dark;
41 const code = themePreviewCodePalette(pack, mode);
42 const chat = tokens?.chat || palette.bg;
43 const sceneBackground = scene === "task" ? pack?.taskBackground || pack?.background : pack?.background;
44 const focusX = sceneBackground?.focusX ?? 0.72;
45 const focusY = sceneBackground?.focusY ?? 0.45;
46 const opacity =
47 scene === "home"
48 ? pack?.background?.homeOpacity ?? 1
49 : pack?.taskBackground?.opacity ?? pack?.background?.taskOpacity ?? 0.28;
50 const overlay = scene === "task"
51 ? pack?.taskBackground?.overlayStrength ?? pack?.background?.overlayStrength ?? 0.62
52 : pack?.background?.overlayStrength ?? 0.62;
53 const boundedPaneAlpha = themePreviewPaneAlpha(pack, scene);
54 const paneCardOffset = scene === "home" ? 0.26 : 0.14;
55 return {
56 ["--tp-bg" as string]: palette.bg,
57 ["--tp-panel" as string]: palette.panel,
58 ["--tp-sidebar" as string]: palette.sidebar,
59 ["--tp-fg" as string]: palette.fg,
60 ["--tp-fg-dim" as string]: palette.fgDim,
61 ["--tp-accent" as string]: palette.accent,
62 ["--tp-accent-fg" as string]: palette.accentFg,
63 ["--tp-border" as string]: palette.border,
64 ["--tp-radius" as string]: palette.radius,
65 ["--tp-chat" as string]: chat,
66 ["--tp-code-bg" as string]: code.background,
67 ["--tp-code-border" as string]: code.border,
68 ["--tp-code-fg" as string]: code.foreground,
69 ["--tp-hl-keyword" as string]: code.keyword,
70 ["--tp-hl-string" as string]: code.string,
71 ["--tp-hl-number" as string]: code.number,
72 ["--tp-hl-comment" as string]: code.comment,
73 ["--tp-hl-func" as string]: code.function,
74 ["--tp-hl-type" as string]: code.type,
75 ["--tp-add-fg" as string]: code.addition,
76 ["--tp-del-fg" as string]: code.deletion,
77 ["--tp-code-add-bg" as string]: code.additionBackground,
78 ["--tp-code-del-bg" as string]: code.deletionBackground,
79 ["--tp-focus-x" as string]: `${focusX * 100}%`,
80 ["--tp-focus-y" as string]: `${focusY * 100}%`,
81 ["--tp-bg-opacity" as string]: String(opacity),
82 ["--tp-overlay" as string]: String(overlay),
83 ["--tp-pane-shell-pct" as string]: `${Math.min((boundedPaneAlpha + 0.08) * 100, 100)}%`,
84 ["--tp-pane-card-pct" as string]: `${Math.min((boundedPaneAlpha + paneCardOffset) * 100, 100)}%`,
85 } as CSSProperties;
86 }, [pack, mode, scene]);
87
88 const bgUrl = isBasePreview
89 ? ""
90 : scene === "task"
91 ? pack?.taskBackgroundUrl || pack?.previewUrl || pack?.backgroundUrl || ""
92 : pack?.previewUrl || pack?.backgroundUrl || "";
93
94 return (
95 <div
96 className="theme-preview-surface"
97 data-mode={mode}
98 data-scene={scene}
99 data-preview-kind={isBasePreview ? "base" : "pack"}
100 data-base-style={baseStyle}
101 data-variant={variant}
102 style={style}
103 >
104 {bgUrl ? (
105 <div
106 className="theme-preview-surface__bg"
107 style={{ backgroundImage: `url("${bgUrl}")` }}
108 aria-hidden="true"
109 />
110 ) : (
111 <div
112 className={`theme-preview-surface__bg ${isBasePreview ? "theme-preview-surface__bg--base" : "theme-preview-surface__bg--swatch"}`}
113 aria-hidden="true"
114 />
115 )}
116 <div className="theme-preview-surface__overlay" aria-hidden="true" />
117 <div className="theme-preview-surface__chrome">
118 <aside className="theme-preview-surface__side">
119 <div className="theme-preview-surface__logo">R</div>
120 <div className="theme-preview-surface__nav" />
121 <div className="theme-preview-surface__nav theme-preview-surface__nav--dim" />
122 <div className="theme-preview-surface__nav theme-preview-surface__nav--dim" />
123 </aside>
124 <main className="theme-preview-surface__main">
125 <div className="theme-preview-surface__card">
126 <div className="theme-preview-surface__title" />
127 <div className="theme-preview-surface__line" />
128 <div className="theme-preview-surface__line theme-preview-surface__line--short" />
129 <div className="theme-preview-surface__cta" />
130 </div>
131 {variant === "full" ? (
132 <div className="theme-preview-surface__code-island">
133 <div className="theme-preview-surface__code-label">CODE + DIFF</div>
134 <code className="theme-preview-surface__code-line">
135 <span className="theme-preview-surface__syntax--keyword">const</span>{" "}
136 <span className="theme-preview-surface__syntax--func">optimize</span>{" = "}
137 <span className="theme-preview-surface__syntax--type">async</span>{" ("}
138 <span className="theme-preview-surface__syntax--number">3</span>{") => "}
139 <span className="theme-preview-surface__syntax--string">&quot;clear&quot;</span>
140 <span className="theme-preview-surface__syntax--comment"> // readable</span>
141 </code>
142 <div className="theme-preview-surface__diff-line theme-preview-surface__diff-line--del">
143 <span>−</span><span>opacity: 0.32</span>
144 </div>
145 <div className="theme-preview-surface__diff-line theme-preview-surface__diff-line--add">
146 <span>+</span><span>codeBg: opaque</span>
147 </div>
148 </div>
149 ) : null}
150 </main>
151 </div>
152 </div>
153 );
154 }
155
155 lines Plain Text