Our Personal Data Server from scratch! tranquil.farm
atproto pds rust postgresql fun oauth

feat(frontend): add UI component test page #66

merged opened by oyster.cafe targeting main from refactor/extract-scoped-styles
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:3fwecdnvtcscjnrx2p4n7alz/sh.tangled.repo.pull/3mhdc5fbvkn22
+534
Diff #0
+534
frontend/src/routes/UiTest.svelte
··· 1 + <script lang="ts"> 2 + import { Button, Card, Input, Message, Page, Section } from '../components/ui' 3 + import Skeleton from '../components/Skeleton.svelte' 4 + import { toast } from '../lib/toast.svelte' 5 + import { getServerConfigState } from '../lib/serverConfig.svelte' 6 + import { _, locale, getSupportedLocales, localeNames, type SupportedLocale } from '../lib/i18n' 7 + 8 + let inputValue = $state('') 9 + let inputError = $state('') 10 + let inputDisabled = $state(false) 11 + 12 + const serverConfig = getServerConfigState() 13 + 14 + const LIGHT_ACCENT_DEFAULT = '#1a1d1d' 15 + const DARK_ACCENT_DEFAULT = '#e6e8e8' 16 + const LIGHT_SECONDARY_DEFAULT = '#1a1d1d' 17 + const DARK_SECONDARY_DEFAULT = '#e6e8e8' 18 + 19 + let accentLight = $state(LIGHT_ACCENT_DEFAULT) 20 + let accentDark = $state(DARK_ACCENT_DEFAULT) 21 + let secondaryLight = $state(LIGHT_SECONDARY_DEFAULT) 22 + let secondaryDark = $state(DARK_SECONDARY_DEFAULT) 23 + 24 + $effect(() => { 25 + accentLight = serverConfig.primaryColor || LIGHT_ACCENT_DEFAULT 26 + accentDark = serverConfig.primaryColorDark || DARK_ACCENT_DEFAULT 27 + secondaryLight = serverConfig.secondaryColor || LIGHT_SECONDARY_DEFAULT 28 + secondaryDark = serverConfig.secondaryColorDark || DARK_SECONDARY_DEFAULT 29 + }) 30 + 31 + const isDark = $derived( 32 + typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches 33 + ) 34 + 35 + function applyColor(prop: string, value: string): void { 36 + document.documentElement.style.setProperty(prop, value) 37 + } 38 + 39 + $effect(() => { 40 + applyColor('--accent', isDark ? accentDark : accentLight) 41 + }) 42 + 43 + $effect(() => { 44 + applyColor('--secondary', isDark ? secondaryDark : secondaryLight) 45 + }) 46 + </script> 47 + 48 + <Page title="UI Test" size="lg"> 49 + <Section title="Theme"> 50 + <div class="form-row"> 51 + <div class="field"> 52 + <label for="accent-light">Accent (light)</label> 53 + <div class="color-pair"> 54 + <input type="color" bind:value={accentLight} /> 55 + <input id="accent-light" type="text" class="mono" bind:value={accentLight} /> 56 + </div> 57 + </div> 58 + <div class="field"> 59 + <label for="accent-dark">Accent (dark)</label> 60 + <div class="color-pair"> 61 + <input type="color" bind:value={accentDark} /> 62 + <input id="accent-dark" type="text" class="mono" bind:value={accentDark} /> 63 + </div> 64 + </div> 65 + <div class="field"> 66 + <label for="secondary-light">Secondary (light)</label> 67 + <div class="color-pair"> 68 + <input type="color" bind:value={secondaryLight} /> 69 + <input id="secondary-light" type="text" class="mono" bind:value={secondaryLight} /> 70 + </div> 71 + </div> 72 + <div class="field"> 73 + <label for="secondary-dark">Secondary (dark)</label> 74 + <div class="color-pair"> 75 + <input type="color" bind:value={secondaryDark} /> 76 + <input id="secondary-dark" type="text" class="mono" bind:value={secondaryDark} /> 77 + </div> 78 + </div> 79 + <div class="field"> 80 + <label for="locale-picker">Locale</label> 81 + <select id="locale-picker" value={$locale} onchange={(e) => locale.set(e.currentTarget.value)}> 82 + {#each getSupportedLocales() as loc} 83 + <option value={loc}>{localeNames[loc]} ({loc})</option> 84 + {/each} 85 + </select> 86 + </div> 87 + </div> 88 + </Section> 89 + 90 + <Section title="Typography"> 91 + <p style="font-size: var(--text-4xl)">4xl (2.5rem)</p> 92 + <p style="font-size: var(--text-3xl)">3xl (2rem)</p> 93 + <p style="font-size: var(--text-2xl)">2xl (1.5rem)</p> 94 + <p style="font-size: var(--text-xl)">xl (1.25rem)</p> 95 + <p style="font-size: var(--text-lg)">lg (1.125rem)</p> 96 + <p style="font-size: var(--text-base)">base (1rem)</p> 97 + <p style="font-size: var(--text-sm)">sm (0.875rem)</p> 98 + <p style="font-size: var(--text-xs)">xs (0.75rem)</p> 99 + <hr /> 100 + <p style="font-weight: var(--font-normal)">Normal (400)</p> 101 + <p style="font-weight: var(--font-medium)">Medium (500)</p> 102 + <p style="font-weight: var(--font-semibold)">Semibold (600)</p> 103 + <p style="font-weight: var(--font-bold)">Bold (700)</p> 104 + <hr /> 105 + <code>Monospace text</code> 106 + <pre>Pre block 107 + indented</pre> 108 + </Section> 109 + 110 + <Section title="Colors"> 111 + <div class="form-row"> 112 + <div> 113 + <h4>Backgrounds</h4> 114 + <div class="swatch" style="background: var(--bg-primary)">bg-primary</div> 115 + <div class="swatch" style="background: var(--bg-secondary)">bg-secondary</div> 116 + <div class="swatch" style="background: var(--bg-tertiary)">bg-tertiary</div> 117 + <div class="swatch" style="background: var(--bg-card)">bg-card</div> 118 + <div class="swatch" style="background: var(--bg-input)">bg-input</div> 119 + </div> 120 + <div> 121 + <h4>Text</h4> 122 + <p style="color: var(--text-primary)">text-primary</p> 123 + <p style="color: var(--text-secondary)">text-secondary</p> 124 + <p style="color: var(--text-muted)">text-muted</p> 125 + <div class="swatch" style="background: var(--accent); color: var(--text-inverse)">text-inverse</div> 126 + </div> 127 + <div> 128 + <h4>Accent</h4> 129 + <div class="swatch" style="background: var(--accent); color: var(--text-inverse)">accent</div> 130 + <div class="swatch" style="background: var(--accent-hover); color: var(--text-inverse)">accent-hover</div> 131 + <div class="swatch" style="background: var(--accent-muted)">accent-muted</div> 132 + </div> 133 + <div> 134 + <h4>Status</h4> 135 + <div class="swatch" style="background: var(--success-bg); color: var(--success-text)">success</div> 136 + <div class="swatch" style="background: var(--error-bg); color: var(--error-text)">error</div> 137 + <div class="swatch" style="background: var(--warning-bg); color: var(--warning-text)">warning</div> 138 + </div> 139 + </div> 140 + </Section> 141 + 142 + <Section title="Spacing"> 143 + <div class="spacing-row"> 144 + {#each [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] as i} 145 + <div class="spacing-item"> 146 + <div class="spacing-box" style="width: var(--space-{i}); height: var(--space-{i})"></div> 147 + <span class="text-xs text-muted">--space-{i}</span> 148 + </div> 149 + {/each} 150 + </div> 151 + </Section> 152 + 153 + <Section title="Buttons"> 154 + <p> 155 + <Button variant="primary">{$_('common.save')}</Button> 156 + <Button variant="secondary">{$_('common.cancel')}</Button> 157 + <Button variant="tertiary">{$_('common.back')}</Button> 158 + <Button variant="danger">{$_('common.delete')}</Button> 159 + <Button variant="ghost">{$_('common.refresh')}</Button> 160 + </p> 161 + <p class="mt-5"> 162 + <Button size="sm">{$_('common.verify')}</Button> 163 + <Button size="md">{$_('common.continue')}</Button> 164 + <Button size="lg">{$_('common.signIn')}</Button> 165 + </p> 166 + <p class="mt-5"> 167 + <Button disabled>{$_('common.save')}</Button> 168 + <Button loading>{$_('common.saving')}</Button> 169 + <Button variant="secondary" disabled>{$_('common.cancel')}</Button> 170 + <Button variant="danger" loading>{$_('common.delete')}</Button> 171 + </p> 172 + <p class="mt-5"> 173 + <button class="danger-outline">{$_('common.revoke')}</button> 174 + <button class="link">{$_('login.forgotPassword')}</button> 175 + <button class="sm">{$_('common.done')}</button> 176 + </p> 177 + <div class="mt-5"> 178 + <Button fullWidth>{$_('common.signIn')}</Button> 179 + </div> 180 + </Section> 181 + 182 + <Section title="Inputs"> 183 + <div class="form-row"> 184 + <div class="field"> 185 + <Input label={$_('settings.newEmail')} placeholder={$_('settings.newEmailPlaceholder')} bind:value={inputValue} /> 186 + </div> 187 + <div class="field"> 188 + <Input label={$_('security.passkeyName')} placeholder={$_('security.passkeyNamePlaceholder')} hint={$_('appPasswords.createdMessage')} /> 189 + </div> 190 + </div> 191 + <div class="form-row"> 192 + <div class="field"> 193 + <Input label={$_('verification.codeLabel')} placeholder={$_('verification.codePlaceholder')} error={$_('common.error')} bind:value={inputError} /> 194 + </div> 195 + <div class="field"> 196 + <Input label={$_('settings.yourDomain')} placeholder={$_('settings.yourDomainPlaceholder')} disabled bind:value={inputDisabled} /> 197 + </div> 198 + </div> 199 + <div class="form-row mt-5"> 200 + <div class="field"> 201 + <label for="demo-select">{$_('settings.language')}</label> 202 + <select id="demo-select"> 203 + {#each getSupportedLocales() as loc} 204 + <option>{localeNames[loc]}</option> 205 + {/each} 206 + </select> 207 + </div> 208 + <div class="field"> 209 + <label for="demo-textarea">{$_('settings.exportData')}</label> 210 + <textarea id="demo-textarea" rows="3"></textarea> 211 + </div> 212 + </div> 213 + </Section> 214 + 215 + <Section title="Cards"> 216 + <Card> 217 + <h4>{$_('settings.exportData')}</h4> 218 + <p class="text-secondary text-sm">{$_('settings.downloadRepo')}</p> 219 + </Card> 220 + <div class="mt-4"> 221 + <Card variant="interactive"> 222 + <h4>{$_('sessions.session')}</h4> 223 + <p class="text-secondary text-sm">{$_('sessions.current')}</p> 224 + </Card> 225 + </div> 226 + <div class="mt-4"> 227 + <Card variant="danger"> 228 + <h4>{$_('security.removePassword')}</h4> 229 + <p class="text-secondary text-sm">{$_('security.removePasswordWarning')}</p> 230 + </Card> 231 + </div> 232 + </Section> 233 + 234 + <Section title="Sections"> 235 + <Section title="Default section" description="With a description"> 236 + <p>Section content</p> 237 + </Section> 238 + <div class="mt-5"> 239 + <Section title="Danger section" variant="danger"> 240 + <p>Destructive operations</p> 241 + </Section> 242 + </div> 243 + </Section> 244 + 245 + <Section title="Messages"> 246 + <Message variant="success">{$_('appPasswords.deleted')}</Message> 247 + <div class="mt-4"><Message variant="error">{$_('appPasswords.createFailed')}</Message></div> 248 + <div class="mt-4"><Message variant="warning">{$_('security.legacyLoginWarning')}</Message></div> 249 + <div class="mt-4"><Message variant="info">{$_('appPasswords.createdMessage')}</Message></div> 250 + </Section> 251 + 252 + <Section title="Badges"> 253 + <p> 254 + <span class="badge success">{$_('inviteCodes.available')}</span> 255 + <span class="badge warning">{$_('inviteCodes.spent')}</span> 256 + <span class="badge error">{$_('inviteCodes.disabled')}</span> 257 + <span class="badge accent">{$_('sessions.current')}</span> 258 + </p> 259 + </Section> 260 + 261 + <Section title="Toasts"> 262 + <p> 263 + <Button variant="secondary" onclick={() => toast.success($_('appPasswords.deleted'))}>{$_('appPasswords.deleted')}</Button> 264 + <Button variant="secondary" onclick={() => toast.error($_('appPasswords.createFailed'))}>{$_('appPasswords.createFailed')}</Button> 265 + <Button variant="secondary" onclick={() => toast.warning($_('security.disableTotpWarning'))}>{$_('security.disableTotpWarning')}</Button> 266 + <Button variant="secondary" onclick={() => toast.info($_('appPasswords.createdMessage'))}>{$_('appPasswords.createdMessage')}</Button> 267 + </p> 268 + </Section> 269 + 270 + <Section title="Skeleton loading"> 271 + <Skeleton variant="line" size="full" /> 272 + <Skeleton variant="line" size="medium" /> 273 + <Skeleton variant="line" size="short" /> 274 + <Skeleton variant="line" size="tiny" /> 275 + <div class="mt-5"> 276 + <Skeleton variant="line" lines={3} /> 277 + </div> 278 + <div class="mt-5"> 279 + <Skeleton variant="card" lines={2} /> 280 + </div> 281 + </Section> 282 + 283 + <Section title="Fieldset"> 284 + <fieldset> 285 + <legend>Account settings</legend> 286 + <div class="field"> 287 + <label for="demo-display-name">Display name</label> 288 + <input id="demo-display-name" type="text" placeholder="Name" /> 289 + </div> 290 + </fieldset> 291 + </Section> 292 + 293 + <Section title="Form layouts"> 294 + <h4>Two column</h4> 295 + <div class="form-row"> 296 + <div class="field"> 297 + <label for="demo-fname">First name</label> 298 + <input id="demo-fname" type="text" /> 299 + </div> 300 + <div class="field"> 301 + <label for="demo-lname">Last name</label> 302 + <input id="demo-lname" type="text" /> 303 + </div> 304 + </div> 305 + <h4 class="mt-5">Three column</h4> 306 + <div class="form-row thirds"> 307 + <div class="field"> 308 + <label for="demo-city">City</label> 309 + <input id="demo-city" type="text" /> 310 + </div> 311 + <div class="field"> 312 + <label for="demo-state">State</label> 313 + <input id="demo-state" type="text" /> 314 + </div> 315 + <div class="field"> 316 + <label for="demo-zip">ZIP</label> 317 + <input id="demo-zip" type="text" /> 318 + </div> 319 + </div> 320 + <h4 class="mt-5">Full width in row</h4> 321 + <div class="form-row"> 322 + <div class="field"> 323 + <label for="demo-handle">Handle</label> 324 + <input id="demo-handle" type="text" /> 325 + </div> 326 + <div class="field"> 327 + <label for="demo-domain">Domain</label> 328 + <select id="demo-domain"><option>example.com</option></select> 329 + </div> 330 + <div class="field full-width"> 331 + <label for="demo-bio">Bio</label> 332 + <textarea id="demo-bio" rows="2"></textarea> 333 + </div> 334 + </div> 335 + </Section> 336 + 337 + <Section title="Hints"> 338 + <div class="field"> 339 + <label for="demo-hint">With hints</label> 340 + <input id="demo-hint" type="text" /> 341 + <span class="hint">Default hint</span> 342 + </div> 343 + <div class="field"> 344 + <input type="text" /> 345 + <span class="hint warning">Warning hint</span> 346 + </div> 347 + <div class="field"> 348 + <input type="text" /> 349 + <span class="hint error">Error hint</span> 350 + </div> 351 + <div class="field"> 352 + <input type="text" /> 353 + <span class="hint success">Success hint</span> 354 + </div> 355 + </Section> 356 + 357 + <Section title="Radio group"> 358 + <div class="radio-group"> 359 + <label class="radio-label"> 360 + <input type="radio" name="demo-radio" checked /> 361 + <div class="radio-content"> 362 + <span>Option A</span> 363 + <span class="radio-hint">First choice</span> 364 + </div> 365 + </label> 366 + <label class="radio-label"> 367 + <input type="radio" name="demo-radio" /> 368 + <div class="radio-content"> 369 + <span>Option B</span> 370 + <span class="radio-hint">Second choice</span> 371 + </div> 372 + </label> 373 + <label class="radio-label disabled"> 374 + <input type="radio" name="demo-radio" disabled /> 375 + <div class="radio-content"> 376 + <span>Option C</span> 377 + <span class="radio-hint disabled-hint">Unavailable</span> 378 + </div> 379 + </label> 380 + </div> 381 + </Section> 382 + 383 + <Section title="Checkbox"> 384 + <label class="checkbox-label"> 385 + <input type="checkbox" /> 386 + <span>{$_('appPasswords.acknowledgeLabel')}</span> 387 + </label> 388 + </Section> 389 + 390 + <Section title="Warning box"> 391 + <div class="warning-box"> 392 + <strong>{$_('appPasswords.saveWarningTitle')}</strong> 393 + <p>{$_('appPasswords.saveWarningMessage')}</p> 394 + </div> 395 + </Section> 396 + 397 + <Section title="Split layout"> 398 + <div class="split-layout"> 399 + <Card> 400 + <h4>Main content</h4> 401 + <p class="text-secondary">Primary area with a form or data</p> 402 + <div class="field mt-5"> 403 + <label for="demo-example">Example field</label> 404 + <input id="demo-example" type="text" placeholder="Value" /> 405 + </div> 406 + </Card> 407 + <div class="info-panel"> 408 + <h3>Sidebar</h3> 409 + <p>Supplementary information placed alongside the main content area.</p> 410 + <ul class="info-list"> 411 + <li>Supports DID methods: did:web, did:plc</li> 412 + <li>Maximum blob size: 10MB</li> 413 + <li>Rate limit: 100 requests per minute</li> 414 + </ul> 415 + </div> 416 + </div> 417 + </Section> 418 + 419 + <Section title="Composite: card with form"> 420 + <Card> 421 + <h4>{$_('appPasswords.create')}</h4> 422 + <p class="text-secondary text-sm mb-5">{$_('appPasswords.permissions')}</p> 423 + <div class="field"> 424 + <Input label={$_('appPasswords.name')} placeholder={$_('appPasswords.namePlaceholder')} /> 425 + </div> 426 + <div class="mt-5" style="display: flex; gap: var(--space-3); justify-content: flex-end"> 427 + <Button variant="tertiary">{$_('common.cancel')}</Button> 428 + <Button>{$_('appPasswords.create')}</Button> 429 + </div> 430 + </Card> 431 + </Section> 432 + 433 + <Section title="Composite: section with actions"> 434 + <Section title={$_('security.passkeys')}> 435 + <div style="display: flex; justify-content: space-between; align-items: center"> 436 + <div> 437 + <strong>{$_('security.totp')}</strong> 438 + <p class="text-sm text-secondary">{$_('security.totpEnabled')}</p> 439 + </div> 440 + <Button variant="secondary" size="sm">{$_('security.disableTotp')}</Button> 441 + </div> 442 + <hr /> 443 + <div style="display: flex; justify-content: space-between; align-items: center"> 444 + <div> 445 + <strong>{$_('security.passkeys')}</strong> 446 + <p class="text-sm text-secondary">{$_('security.noPasskeys')}</p> 447 + </div> 448 + <Button variant="secondary" size="sm">{$_('security.addPasskey')}</Button> 449 + </div> 450 + </Section> 451 + </Section> 452 + 453 + <Section title="Composite: error state"> 454 + <div class="error-container"> 455 + <div class="error-icon">!</div> 456 + <h2>Authorization failed</h2> 457 + <p>The requested scope exceeds the granted permissions.</p> 458 + <Button variant="secondary">Back</Button> 459 + </div> 460 + </Section> 461 + 462 + <Section title="Item list"> 463 + <div class="item"> 464 + <div class="item-info"> 465 + <strong>Primary passkey</strong> 466 + <span class="text-sm text-secondary">Created 2024-01-15</span> 467 + </div> 468 + <div class="item-actions"> 469 + <button class="sm">Rename</button> 470 + <button class="sm danger-outline">Revoke</button> 471 + </div> 472 + </div> 473 + <div class="item"> 474 + <div class="item-info"> 475 + <strong>Backup passkey</strong> 476 + <span class="text-sm text-secondary">Created 2024-03-20</span> 477 + </div> 478 + <div class="item-actions"> 479 + <button class="sm">Rename</button> 480 + <button class="sm danger-outline">Revoke</button> 481 + </div> 482 + </div> 483 + <div class="item"> 484 + <div class="item-info"> 485 + <strong>Work laptop</strong> 486 + <span class="text-sm text-secondary">Created 2024-06-01</span> 487 + </div> 488 + <div class="item-actions"> 489 + <button class="sm danger-outline">Revoke</button> 490 + </div> 491 + </div> 492 + </Section> 493 + 494 + <Section title="Definition list"> 495 + <dl class="definition-list"> 496 + <dt>Handle</dt> 497 + <dd>@alice.example.com</dd> 498 + <dt>DID</dt> 499 + <dd class="mono">did:web:alice.example.com</dd> 500 + <dt>Email</dt> 501 + <dd>alice@example.com</dd> 502 + <dt>Created</dt> 503 + <dd>2024-01-15</dd> 504 + <dt>Status</dt> 505 + <dd><span class="badge success">Verified</span></dd> 506 + </dl> 507 + </Section> 508 + 509 + <Section title="Tabs"> 510 + <div class="tabs"> 511 + <button class="tab active">PDS handle</button> 512 + <button class="tab">Custom domain</button> 513 + </div> 514 + <p class="text-secondary">Tab content appears here</p> 515 + </Section> 516 + 517 + <Section title="Inline form"> 518 + <div class="inline-form"> 519 + <h4>Change password</h4> 520 + <div class="field"> 521 + <label for="demo-current-pw">Current password</label> 522 + <input id="demo-current-pw" type="password" /> 523 + </div> 524 + <div class="field"> 525 + <label for="demo-new-pw">New password</label> 526 + <input id="demo-new-pw" type="password" /> 527 + </div> 528 + <div style="display: flex; gap: var(--space-3); justify-content: flex-end"> 529 + <Button variant="secondary">Cancel</Button> 530 + <Button>Save</Button> 531 + </div> 532 + </div> 533 + </Section> 534 + </Page>

History

1 round 0 comments
sign up or login to add to the discussion
oyster.cafe submitted #0
1 commit
expand
feat(frontend): add UI component test page
expand 0 comments
pull request successfully merged