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

Merge pull request #13 from tylersayshi/mobile

mobile

authored by tyler and committed by GitHub bfa4b4aa 360a92ac

+220 -27
+2 -10
packages/site/src/components/Header.tsx
··· 7 7 }} 8 8 > 9 9 <div style={{ maxWidth: "1400px", margin: "0 auto" }}> 10 - <div 11 - style={{ 12 - display: "flex", 13 - justifyContent: "space-between", 14 - alignItems: "flex-start", 15 - }} 16 - > 10 + <div className="header-content"> 17 11 <div> 18 12 <h1 19 13 style={{ ··· 34 28 Type-safe lexicon inference for ATProto schemas 35 29 </p> 36 30 </div> 37 - <div 38 - style={{ display: "flex", gap: "1.25rem", paddingTop: "0.5rem" }} 39 - > 31 + <div className="header-links"> 40 32 <a 41 33 href="https://github.com/tylersayshi/prototypey" 42 34 target="_blank"
+174 -17
packages/site/src/components/Playground.tsx
··· 6 6 import type * as Monaco from "monaco-editor"; 7 7 import { parseAsString, useQueryState } from "nuqs"; 8 8 import LZString from "lz-string"; 9 + import MonacoEditor from "@monaco-editor/react"; 9 10 10 11 let tsWorkerInstance: Monaco.languages.typescript.TypeScriptWorker | null = 11 12 null; ··· 139 140 }, [code, monaco]); 140 141 141 142 return ( 142 - <div 143 - style={{ 144 - flex: 1, 145 - display: "flex", 146 - overflow: "hidden", 147 - }} 148 - > 143 + <> 144 + {/* Desktop playground */} 149 145 <div 146 + className="desktop-only" 150 147 style={{ 151 148 flex: 1, 152 - display: "flex", 153 - borderRight: "1px solid #e5e7eb", 149 + overflow: "hidden", 154 150 }} 155 151 > 156 - <Editor 157 - value={code} 158 - onChange={handleCodeChange} 159 - onReady={handleEditorReady} 160 - /> 152 + <div 153 + style={{ 154 + flex: 1, 155 + display: "flex", 156 + borderRight: "1px solid #e5e7eb", 157 + }} 158 + > 159 + <Editor 160 + value={code} 161 + onChange={handleCodeChange} 162 + onReady={handleEditorReady} 163 + /> 164 + </div> 165 + <div style={{ flex: 1, display: "flex" }}> 166 + <OutputPanel output={output} /> 167 + </div> 161 168 </div> 162 - <div style={{ flex: 1, display: "flex" }}> 163 - <OutputPanel output={output} /> 169 + 170 + {/* Mobile static demo */} 171 + <div 172 + className="mobile-only" 173 + style={{ 174 + flex: 1, 175 + flexDirection: "column", 176 + overflow: "auto", 177 + padding: "1rem", 178 + }} 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} /> 164 195 </div> 165 - </div> 196 + </> 166 197 ); 167 198 } 168 199 ··· 194 225 } 195 226 196 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; 234 + 235 + const estimateWrappedLines = (text: string, maxCharsPerLine: number) => { 236 + return text.split("\n").reduce((total, line) => { 237 + const wrappedLines = Math.ceil( 238 + Math.max(1, line.length) / maxCharsPerLine, 239 + ); 240 + return total + wrappedLines; 241 + }, 0); 242 + }; 243 + 244 + const codeWrappedLines = estimateWrappedLines(code, 50); 245 + const jsonWrappedLines = estimateWrappedLines(json, 50); 246 + 247 + return ( 248 + <div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}> 249 + {/* You write section */} 250 + <div style={{ display: "flex", flexDirection: "column" }}> 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 + }} 262 + > 263 + You write 264 + </div> 265 + <div 266 + style={{ 267 + border: "1px solid #e5e7eb", 268 + borderTop: "none", 269 + borderBottomLeftRadius: "0.5rem", 270 + borderBottomRightRadius: "0.5rem", 271 + overflow: "hidden", 272 + }} 273 + > 274 + <MonacoEditor 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 }, 282 + fontSize: 12, 283 + lineNumbers: "on", 284 + renderLineHighlight: "none", 285 + scrollBeyondLastLine: false, 286 + padding: { top: 16, bottom: 16 }, 287 + scrollbar: { 288 + vertical: "hidden", 289 + horizontal: "hidden", 290 + verticalScrollbarSize: 0, 291 + horizontalScrollbarSize: 0, 292 + handleMouseWheel: false, 293 + }, 294 + wordWrap: "on", 295 + overviewRulerLanes: 0, 296 + }} 297 + /> 298 + </div> 299 + </div> 300 + 301 + {/* JSON generated section */} 302 + <div style={{ display: "flex", flexDirection: "column" }}> 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 + }} 314 + > 315 + JSON generated 316 + </div> 317 + <div 318 + style={{ 319 + border: "1px solid #e5e7eb", 320 + borderTop: "none", 321 + borderBottomLeftRadius: "0.5rem", 322 + borderBottomRightRadius: "0.5rem", 323 + overflow: "hidden", 324 + }} 325 + > 326 + <MonacoEditor 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 }, 334 + fontSize: 12, 335 + lineNumbers: "on", 336 + renderLineHighlight: "none", 337 + scrollBeyondLastLine: false, 338 + padding: { top: 16, bottom: 16 }, 339 + scrollbar: { 340 + vertical: "hidden", 341 + horizontal: "hidden", 342 + verticalScrollbarSize: 0, 343 + horizontalScrollbarSize: 0, 344 + handleMouseWheel: false, 345 + }, 346 + wordWrap: "on", 347 + overviewRulerLanes: 0, 348 + }} 349 + /> 350 + </div> 351 + </div> 352 + </div> 353 + ); 197 354 } 198 355 199 356 const DEFAULT_CODE = `import { lx, type Infer } from "prototypey";
+44
packages/site/src/index.css
··· 30 30 display: flex; 31 31 flex-direction: column; 32 32 } 33 + 34 + /* Desktop layout - default */ 35 + .header-content { 36 + display: flex; 37 + justify-content: space-between; 38 + align-items: flex-start; 39 + } 40 + 41 + .header-links { 42 + display: flex; 43 + gap: 1.25rem; 44 + padding-top: 0.5rem; 45 + } 46 + 47 + .mobile-only { 48 + display: none; 49 + } 50 + 51 + .desktop-only { 52 + display: flex; 53 + } 54 + 55 + /* Mobile layout */ 56 + @media (max-width: 768px) { 57 + .header-content { 58 + flex-direction: column; 59 + gap: 1rem; 60 + } 61 + 62 + .header-links { 63 + padding-top: 0; 64 + width: 100%; 65 + justify-content: flex-start; 66 + } 67 + 68 + .mobile-only { 69 + display: flex !important; 70 + flex-direction: column; 71 + } 72 + 73 + .desktop-only { 74 + display: none !important; 75 + } 76 + }