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