tangled
alpha
login
or
join now
tylur.dev
/
prototypey
prototypey.org - atproto lexicon typescript toolkit - mirror https://github.com/tylersayshi/prototypey
1
fork
atom
overview
issues
pulls
pipelines
dark theme for site
Tyler
3 months ago
abb0a538
bfa4b4aa
+113
-37
5 changed files
expand all
collapse all
unified
split
packages
site
src
components
Editor.tsx
Header.tsx
OutputPanel.tsx
Playground.tsx
index.css
+21
-7
packages/site/src/components/Editor.tsx
···
10
10
export function Editor({ value, onChange, onReady }: EditorProps) {
11
11
const [isReady, setIsReady] = useState(false);
12
12
const monaco = useMonaco();
13
13
+
const [theme, setTheme] = useState<"vs-light" | "vs-dark">(
14
14
+
window.matchMedia("(prefers-color-scheme: dark)").matches
15
15
+
? "vs-dark"
16
16
+
: "vs-light",
17
17
+
);
18
18
+
19
19
+
useEffect(() => {
20
20
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
21
21
+
const handleChange = (e: MediaQueryListEvent) => {
22
22
+
setTheme(e.matches ? "vs-dark" : "vs-light");
23
23
+
};
24
24
+
mediaQuery.addEventListener("change", handleChange);
25
25
+
return () => mediaQuery.removeEventListener("change", handleChange);
26
26
+
}, []);
13
27
14
28
useEffect(() => {
15
29
if (!monaco) return;
···
71
85
<div
72
86
style={{
73
87
padding: "0.75rem 1rem",
74
74
-
backgroundColor: "#f9fafb",
75
75
-
borderBottom: "1px solid #e5e7eb",
88
88
+
backgroundColor: "var(--color-bg-secondary)",
89
89
+
borderBottom: "1px solid var(--color-border)",
76
90
fontSize: "0.875rem",
77
91
fontWeight: "600",
78
78
-
color: "#374151",
92
92
+
color: "var(--color-text-secondary)",
79
93
}}
80
94
>
81
95
Input
···
99
113
<div
100
114
style={{
101
115
padding: "0.75rem 1rem",
102
102
-
backgroundColor: "#f9fafb",
103
103
-
borderBottom: "1px solid #e5e7eb",
116
116
+
backgroundColor: "var(--color-bg-secondary)",
117
117
+
borderBottom: "1px solid var(--color-border)",
104
118
fontSize: "0.875rem",
105
119
fontWeight: "600",
106
106
-
color: "#374151",
120
120
+
color: "var(--color-text-secondary)",
107
121
}}
108
122
>
109
123
Input
···
115
129
path="file:///main.ts"
116
130
value={value}
117
131
onChange={(value) => onChange(value || "")}
118
118
-
theme="vs-light"
132
132
+
theme={theme}
119
133
options={{
120
134
minimap: { enabled: false },
121
135
fontSize: 14,
+8
-5
packages/site/src/components/Header.tsx
···
3
3
<header
4
4
style={{
5
5
padding: "2rem 2rem 1rem 2rem",
6
6
-
borderBottom: "1px solid #e5e7eb",
6
6
+
borderBottom: "1px solid var(--color-border)",
7
7
}}
8
8
>
9
9
<div style={{ maxWidth: "1400px", margin: "0 auto" }}>
···
16
16
marginBottom: "0.5rem",
17
17
}}
18
18
>
19
19
-
<span style={{ color: "#6b7280" }}>at://</span>prototypey
19
19
+
<span style={{ color: "var(--color-text-secondary)" }}>
20
20
+
at://
21
21
+
</span>
22
22
+
prototypey
20
23
</h1>
21
24
<p
22
25
style={{
23
26
fontSize: "1.125rem",
24
24
-
color: "#6b7280",
27
27
+
color: "var(--color-text-secondary)",
25
28
marginTop: "0.5rem",
26
29
}}
27
30
>
···
34
37
target="_blank"
35
38
rel="noopener noreferrer"
36
39
style={{
37
37
-
color: "#111827",
40
40
+
color: "var(--color-text-heading)",
38
41
textDecoration: "none",
39
42
fontSize: "1rem",
40
43
fontWeight: "600",
···
62
65
target="_blank"
63
66
rel="noopener noreferrer"
64
67
style={{
65
65
-
color: "#111827",
68
68
+
color: "var(--color-text-heading)",
66
69
textDecoration: "none",
67
70
fontSize: "1rem",
68
71
fontWeight: "600",
+22
-6
packages/site/src/components/OutputPanel.tsx
···
1
1
import MonacoEditor from "@monaco-editor/react";
2
2
+
import { useEffect, useState } from "react";
2
3
3
4
interface OutputPanelProps {
4
5
output: {
···
9
10
}
10
11
11
12
export function OutputPanel({ output }: OutputPanelProps) {
13
13
+
const [theme, setTheme] = useState<"vs-light" | "vs-dark">(
14
14
+
window.matchMedia("(prefers-color-scheme: dark)").matches
15
15
+
? "vs-dark"
16
16
+
: "vs-light",
17
17
+
);
18
18
+
19
19
+
useEffect(() => {
20
20
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
21
21
+
const handleChange = (e: MediaQueryListEvent) => {
22
22
+
setTheme(e.matches ? "vs-dark" : "vs-light");
23
23
+
};
24
24
+
mediaQuery.addEventListener("change", handleChange);
25
25
+
return () => mediaQuery.removeEventListener("change", handleChange);
26
26
+
}, []);
27
27
+
12
28
return (
13
29
<div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
14
30
<div
15
31
style={{
16
32
padding: "0.75rem 1rem",
17
17
-
backgroundColor: "#f9fafb",
18
18
-
borderBottom: "1px solid #e5e7eb",
33
33
+
backgroundColor: "var(--color-bg-secondary)",
34
34
+
borderBottom: "1px solid var(--color-border)",
19
35
fontSize: "0.875rem",
20
36
fontWeight: "600",
21
21
-
color: "#374151",
37
37
+
color: "var(--color-text-secondary)",
22
38
}}
23
39
>
24
40
Output
···
28
44
<div
29
45
style={{
30
46
padding: "1rem",
31
31
-
color: "#dc2626",
32
32
-
backgroundColor: "#fef2f2",
47
47
+
color: "var(--color-error)",
48
48
+
backgroundColor: "var(--color-error-bg)",
33
49
height: "100%",
34
50
overflow: "auto",
35
51
}}
···
41
57
height="100%"
42
58
defaultLanguage="json"
43
59
value={output.json}
44
44
-
theme="vs-light"
60
60
+
theme={theme}
45
61
options={{
46
62
readOnly: true,
47
63
minimap: { enabled: false },
+37
-17
packages/site/src/components/Playground.tsx
···
12
12
null;
13
13
14
14
export function Playground() {
15
15
-
// Use URL state with compression for code
16
15
const [compressedCode, setCompressedCode] = useQueryState(
17
16
"code",
18
17
parseAsString.withDefault(""),
19
18
);
20
19
21
21
-
// Decompress code from URL or use default
22
20
const initialCode =
23
21
compressedCode && compressedCode.trim() !== ""
24
22
? LZString.decompressFromEncodedURIComponent(compressedCode) ||
···
28
26
const [code, setCode] = useState(initialCode);
29
27
const [output, setOutput] = useState({ json: "", typeInfo: "", error: "" });
30
28
const [editorReady, setEditorReady] = useState(false);
29
29
+
const [theme, setTheme] = useState<"vs-light" | "vs-dark">(
30
30
+
window.matchMedia("(prefers-color-scheme: dark)").matches
31
31
+
? "vs-dark"
32
32
+
: "vs-light",
33
33
+
);
31
34
const monaco = useMonaco();
32
35
const tsWorkerRef =
33
36
useRef<Monaco.languages.typescript.TypeScriptWorker | null>(null);
···
44
47
};
45
48
46
49
useEffect(() => {
50
50
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
51
51
+
const handleChange = (e: MediaQueryListEvent) => {
52
52
+
setTheme(e.matches ? "vs-dark" : "vs-light");
53
53
+
};
54
54
+
mediaQuery.addEventListener("change", handleChange);
55
55
+
return () => mediaQuery.removeEventListener("change", handleChange);
56
56
+
}, []);
57
57
+
58
58
+
useEffect(() => {
47
59
if (monaco && editorReady && !tsWorkerRef.current && !tsWorkerInstance) {
48
60
const initWorker = async () => {
49
61
try {
···
153
165
style={{
154
166
flex: 1,
155
167
display: "flex",
156
156
-
borderRight: "1px solid #e5e7eb",
168
168
+
borderRight: "1px solid var(--color-border)",
157
169
}}
158
170
>
159
171
<Editor
···
179
191
>
180
192
<div
181
193
style={{
182
182
-
backgroundColor: "#f9fafb",
194
194
+
backgroundColor: "var(--color-bg-secondary)",
183
195
padding: "1rem",
184
196
borderRadius: "0.5rem",
185
197
marginBottom: "1rem",
186
198
textAlign: "center",
187
187
-
color: "#6b7280",
199
199
+
color: "var(--color-text-secondary)",
188
200
fontSize: "0.875rem",
189
201
}}
190
202
>
191
203
Playground available on desktop
192
204
</div>
193
205
194
194
-
<MobileStaticDemo code={code} json={output.json} />
206
206
+
<MobileStaticDemo code={code} json={output.json} theme={theme} />
195
207
</div>
196
208
</>
197
209
);
···
227
239
return indentedLines.join("\n");
228
240
}
229
241
230
230
-
function MobileStaticDemo({ code, json }: { code: string; json: string }) {
242
242
+
function MobileStaticDemo({
243
243
+
code,
244
244
+
json,
245
245
+
theme,
246
246
+
}: {
247
247
+
code: string;
248
248
+
json: string;
249
249
+
theme: "vs-light" | "vs-dark";
250
250
+
}) {
231
251
// Calculate line counts to size editors appropriately
232
252
const codeLines = code.split("\n").length;
233
253
const jsonLines = json.split("\n").length;
···
251
271
<div
252
272
style={{
253
273
padding: "0.75rem 1rem",
254
254
-
backgroundColor: "#f9fafb",
255
255
-
borderBottom: "1px solid #e5e7eb",
274
274
+
backgroundColor: "var(--color-bg-secondary)",
275
275
+
borderBottom: "1px solid var(--color-border)",
256
276
fontSize: "0.875rem",
257
277
fontWeight: "600",
258
258
-
color: "#374151",
278
278
+
color: "var(--color-text-secondary)",
259
279
borderTopLeftRadius: "0.5rem",
260
280
borderTopRightRadius: "0.5rem",
261
281
}}
···
264
284
</div>
265
285
<div
266
286
style={{
267
267
-
border: "1px solid #e5e7eb",
287
287
+
border: "1px solid var(--color-border)",
268
288
borderTop: "none",
269
289
borderBottomLeftRadius: "0.5rem",
270
290
borderBottomRightRadius: "0.5rem",
···
275
295
height={`${codeWrappedLines * 18 + 32}px`}
276
296
defaultLanguage="typescript"
277
297
value={code}
278
278
-
theme="vs-light"
298
298
+
theme={theme}
279
299
options={{
280
300
readOnly: true,
281
301
minimap: { enabled: false },
···
303
323
<div
304
324
style={{
305
325
padding: "0.75rem 1rem",
306
306
-
backgroundColor: "#f9fafb",
307
307
-
borderBottom: "1px solid #e5e7eb",
326
326
+
backgroundColor: "var(--color-bg-secondary)",
327
327
+
borderBottom: "1px solid var(--color-border)",
308
328
fontSize: "0.875rem",
309
329
fontWeight: "600",
310
310
-
color: "#374151",
330
330
+
color: "var(--color-text-secondary)",
311
331
borderTopLeftRadius: "0.5rem",
312
332
borderTopRightRadius: "0.5rem",
313
333
}}
···
316
336
</div>
317
337
<div
318
338
style={{
319
319
-
border: "1px solid #e5e7eb",
339
339
+
border: "1px solid var(--color-border)",
320
340
borderTop: "none",
321
341
borderBottomLeftRadius: "0.5rem",
322
342
borderBottomRightRadius: "0.5rem",
···
327
347
height={`${jsonWrappedLines * 18 + 32}px`}
328
348
defaultLanguage="json"
329
349
value={json}
330
330
-
theme="vs-light"
350
350
+
theme={theme}
331
351
options={{
332
352
readOnly: true,
333
353
minimap: { enabled: false },
+25
-2
packages/site/src/index.css
···
10
10
"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
11
11
line-height: 1.5;
12
12
font-weight: 400;
13
13
-
color: #213547;
14
14
-
background-color: #ffffff;
15
13
font-synthesis: none;
16
14
text-rendering: optimizeLegibility;
17
15
-webkit-font-smoothing: antialiased;
18
16
-moz-osx-font-smoothing: grayscale;
17
17
+
18
18
+
--color-text: #213547;
19
19
+
--color-text-secondary: #6b7280;
20
20
+
--color-text-heading: #111827;
21
21
+
--color-bg: #ffffff;
22
22
+
--color-bg-secondary: #f9fafb;
23
23
+
--color-border: #e5e7eb;
24
24
+
--color-error: #dc2626;
25
25
+
--color-error-bg: #fef2f2;
26
26
+
27
27
+
color: var(--color-text);
28
28
+
background-color: var(--color-bg);
29
29
+
}
30
30
+
31
31
+
@media (prefers-color-scheme: dark) {
32
32
+
:root {
33
33
+
--color-text: #e5e7eb;
34
34
+
--color-text-secondary: #9ca3af;
35
35
+
--color-text-heading: #f9fafb;
36
36
+
--color-bg: #111827;
37
37
+
--color-bg-secondary: #1f2937;
38
38
+
--color-border: #374151;
39
39
+
--color-error: #f87171;
40
40
+
--color-error-bg: #3f1f1f;
41
41
+
}
19
42
}
20
43
21
44
body {