prototypey.org - atproto lexicon typescript toolkit - mirror https://github.com/tylersayshi/prototypey

dark theme for site

Tyler abb0a538 bfa4b4aa

+113 -37
+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 + 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 + }, []); 13 27 14 28 useEffect(() => { 15 29 if (!monaco) return; ··· 71 85 <div 72 86 style={{ 73 87 padding: "0.75rem 1rem", 74 - backgroundColor: "#f9fafb", 75 - borderBottom: "1px solid #e5e7eb", 88 + backgroundColor: "var(--color-bg-secondary)", 89 + borderBottom: "1px solid var(--color-border)", 76 90 fontSize: "0.875rem", 77 91 fontWeight: "600", 78 - color: "#374151", 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 - backgroundColor: "#f9fafb", 103 - borderBottom: "1px solid #e5e7eb", 116 + backgroundColor: "var(--color-bg-secondary)", 117 + borderBottom: "1px solid var(--color-border)", 104 118 fontSize: "0.875rem", 105 119 fontWeight: "600", 106 - color: "#374151", 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 - theme="vs-light" 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 - borderBottom: "1px solid #e5e7eb", 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 - <span style={{ color: "#6b7280" }}>at://</span>prototypey 19 + <span style={{ color: "var(--color-text-secondary)" }}> 20 + at:// 21 + </span> 22 + prototypey 20 23 </h1> 21 24 <p 22 25 style={{ 23 26 fontSize: "1.125rem", 24 - color: "#6b7280", 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 - color: "#111827", 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 - color: "#111827", 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 + 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 + 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 + 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 - backgroundColor: "#f9fafb", 18 - borderBottom: "1px solid #e5e7eb", 33 + backgroundColor: "var(--color-bg-secondary)", 34 + borderBottom: "1px solid var(--color-border)", 19 35 fontSize: "0.875rem", 20 36 fontWeight: "600", 21 - color: "#374151", 37 + color: "var(--color-text-secondary)", 22 38 }} 23 39 > 24 40 Output ··· 28 44 <div 29 45 style={{ 30 46 padding: "1rem", 31 - color: "#dc2626", 32 - backgroundColor: "#fef2f2", 47 + color: "var(--color-error)", 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 - theme="vs-light" 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 - // 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 - // 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 + const [theme, setTheme] = useState<"vs-light" | "vs-dark">( 30 + window.matchMedia("(prefers-color-scheme: dark)").matches 31 + ? "vs-dark" 32 + : "vs-light", 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 + 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(() => { 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 - borderRight: "1px solid #e5e7eb", 168 + borderRight: "1px solid var(--color-border)", 157 169 }} 158 170 > 159 171 <Editor ··· 179 191 > 180 192 <div 181 193 style={{ 182 - backgroundColor: "#f9fafb", 194 + backgroundColor: "var(--color-bg-secondary)", 183 195 padding: "1rem", 184 196 borderRadius: "0.5rem", 185 197 marginBottom: "1rem", 186 198 textAlign: "center", 187 - color: "#6b7280", 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 - <MobileStaticDemo code={code} json={output.json} /> 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 - function MobileStaticDemo({ code, json }: { code: string; json: string }) { 242 + function MobileStaticDemo({ 243 + code, 244 + json, 245 + theme, 246 + }: { 247 + code: string; 248 + json: string; 249 + theme: "vs-light" | "vs-dark"; 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 - backgroundColor: "#f9fafb", 255 - borderBottom: "1px solid #e5e7eb", 274 + backgroundColor: "var(--color-bg-secondary)", 275 + borderBottom: "1px solid var(--color-border)", 256 276 fontSize: "0.875rem", 257 277 fontWeight: "600", 258 - color: "#374151", 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 - border: "1px solid #e5e7eb", 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 - theme="vs-light" 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 - backgroundColor: "#f9fafb", 307 - borderBottom: "1px solid #e5e7eb", 326 + backgroundColor: "var(--color-bg-secondary)", 327 + borderBottom: "1px solid var(--color-border)", 308 328 fontSize: "0.875rem", 309 329 fontWeight: "600", 310 - color: "#374151", 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 - border: "1px solid #e5e7eb", 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 - theme="vs-light" 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 - color: #213547; 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 + 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 + } 19 42 } 20 43 21 44 body {