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 export function Editor({ value, onChange, onReady }: EditorProps) { 11 const [isReady, setIsReady] = useState(false); 12 const monaco = useMonaco(); 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 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"; 2 3 interface OutputPanelProps { 4 output: { ··· 9 } 10 11 export function OutputPanel({ output }: OutputPanelProps) { 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); 31 const monaco = useMonaco(); 32 const tsWorkerRef = 33 useRef<Monaco.languages.typescript.TypeScriptWorker | null>(null); ··· 44 }; 45 46 useEffect(() => { 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 }) { 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() { 15 const [compressedCode, setCompressedCode] = useQueryState( 16 "code", 17 parseAsString.withDefault(""), 18 ); 19 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; 19 } 20 21 body {
··· 10 "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 11 line-height: 1.5; 12 font-weight: 400; 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 {