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

refactor(frontend): extract UI primitive component styles #56

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/3mhdc5fbtkz22
+8 -608
Diff #0
-46
frontend/src/components/AccountTypeSwitcher.svelte
··· 38 38 </span> 39 39 {/if} 40 40 </div> 41 - 42 - <style> 43 - .account-type-switcher { 44 - display: flex; 45 - gap: var(--space-2); 46 - padding: var(--space-1); 47 - background: var(--bg-secondary); 48 - border-radius: var(--radius-lg); 49 - margin-bottom: var(--space-6); 50 - } 51 - 52 - .switcher-option { 53 - flex: 1; 54 - display: flex; 55 - align-items: center; 56 - justify-content: center; 57 - gap: var(--space-2); 58 - padding: var(--space-3) var(--space-4); 59 - border-radius: var(--radius-md); 60 - text-decoration: none; 61 - color: var(--text-secondary); 62 - font-weight: var(--font-medium); 63 - transition: all 0.15s ease; 64 - } 65 - 66 - .switcher-option:hover { 67 - color: var(--text-primary); 68 - background: var(--bg-tertiary); 69 - } 70 - 71 - .switcher-option.active { 72 - background: var(--bg-primary); 73 - color: var(--text-primary); 74 - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 75 - } 76 - 77 - .switcher-option.disabled { 78 - opacity: 0.4; 79 - cursor: not-allowed; 80 - } 81 - 82 - .switcher-option.disabled:hover { 83 - color: var(--text-secondary); 84 - background: transparent; 85 - } 86 - </style>
+1 -24
frontend/src/components/AuthenticatedRoute.svelte
··· 32 32 {#if auth.kind === 'authenticated'} 33 33 {@render children({ session: auth.session, client: createAuthenticatedClient(auth.session) })} 34 34 {:else} 35 - <div class="loading-container"><div class="loading-spinner"></div></div> 35 + <div class="loading-container"></div> 36 36 {/if} 37 - 38 - <style> 39 - .loading-container { 40 - display: flex; 41 - justify-content: center; 42 - align-items: center; 43 - min-height: 200px; 44 - padding: var(--space-7); 45 - } 46 - 47 - .loading-spinner { 48 - width: 32px; 49 - height: 32px; 50 - border: 3px solid var(--border-color); 51 - border-top-color: var(--accent); 52 - border-radius: 50%; 53 - animation: spin 0.8s linear infinite; 54 - } 55 - 56 - @keyframes spin { 57 - to { transform: rotate(360deg); } 58 - } 59 - </style>
-22
frontend/src/components/HandleInput.svelte
··· 47 47 <span class="domain-suffix">.{domains[0]}</span> 48 48 {/if} 49 49 </div> 50 - 51 - <style> 52 - .handle-input-group { 53 - display: flex; 54 - gap: var(--space-2); 55 - align-items: center; 56 - } 57 - 58 - .handle-input-group input { 59 - flex: 1; 60 - } 61 - 62 - .handle-input-group select { 63 - width: auto; 64 - } 65 - 66 - .domain-suffix { 67 - color: var(--text-secondary); 68 - font-size: var(--text-sm); 69 - white-space: nowrap; 70 - } 71 - </style>
-15
frontend/src/components/LoadMoreSentinel.svelte
··· 33 33 {/if} 34 34 </div> 35 35 {/if} 36 - 37 - <style> 38 - .load-more-sentinel { 39 - height: 40px; 40 - display: flex; 41 - align-items: center; 42 - justify-content: center; 43 - margin-top: var(--space-4); 44 - } 45 - 46 - .loading-indicator { 47 - color: var(--text-secondary); 48 - font-size: var(--text-sm); 49 - } 50 - </style>
+3 -111
frontend/src/components/ReauthModal.svelte
··· 148 148 {/if} 149 149 150 150 {#if availableMethods.length > 1} 151 - <div class="method-tabs"> 151 + <div class="tabs"> 152 152 {#if availableMethods.includes('password')} 153 153 <button 154 154 class="tab" ··· 182 182 <div class="modal-content"> 183 183 {#if activeMethod === 'password'} 184 184 <form onsubmit={handlePasswordSubmit}> 185 - <div class="field"> 185 + <div> 186 186 <label for="reauth-password">{$_('reauth.password')}</label> 187 187 <input 188 188 id="reauth-password" ··· 198 198 </form> 199 199 {:else if activeMethod === 'totp'} 200 200 <form onsubmit={handleTotpSubmit}> 201 - <div class="field"> 201 + <div> 202 202 <label for="reauth-totp">{$_('reauth.authenticatorCode')}</label> 203 203 <input 204 204 id="reauth-totp" ··· 232 232 </div> 233 233 </div> 234 234 {/if} 235 - 236 - <style> 237 - .modal-backdrop { 238 - position: fixed; 239 - inset: 0; 240 - background: var(--overlay-bg); 241 - display: flex; 242 - align-items: center; 243 - justify-content: center; 244 - z-index: var(--z-modal); 245 - } 246 - 247 - .modal { 248 - background: var(--bg-card); 249 - border-radius: var(--radius-xl); 250 - box-shadow: var(--shadow-lg); 251 - max-width: var(--width-sm); 252 - width: 90%; 253 - max-height: 90vh; 254 - overflow-y: auto; 255 - } 256 - 257 - .modal-header { 258 - display: flex; 259 - justify-content: space-between; 260 - align-items: center; 261 - padding: var(--space-4) var(--space-6); 262 - border-bottom: 1px solid var(--border-color); 263 - } 264 - 265 - .modal-header h2 { 266 - margin: 0; 267 - font-size: var(--text-lg); 268 - } 269 - 270 - .close-btn { 271 - background: none; 272 - border: none; 273 - font-size: var(--text-xl); 274 - cursor: pointer; 275 - color: var(--text-secondary); 276 - padding: 0; 277 - line-height: 1; 278 - } 279 - 280 - .close-btn:hover { 281 - color: var(--text-primary); 282 - } 283 - 284 - .error-message { 285 - margin: var(--space-4) var(--space-6) 0; 286 - padding: var(--space-3); 287 - background: var(--error-bg); 288 - border: 1px solid var(--error-border); 289 - border-radius: var(--radius-md); 290 - color: var(--error-text); 291 - font-size: var(--text-sm); 292 - } 293 - 294 - .method-tabs { 295 - display: flex; 296 - gap: var(--space-2); 297 - padding: var(--space-4) var(--space-6) 0; 298 - } 299 - 300 - .tab { 301 - flex: 1; 302 - padding: var(--space-2) var(--space-4); 303 - background: var(--bg-input); 304 - border: 1px solid var(--border-color); 305 - border-radius: var(--radius-md); 306 - cursor: pointer; 307 - color: var(--text-secondary); 308 - font-size: var(--text-sm); 309 - } 310 - 311 - .tab:hover { 312 - background: var(--bg-secondary); 313 - } 314 - 315 - .tab.active { 316 - background: var(--accent); 317 - border-color: var(--accent); 318 - color: var(--text-inverse); 319 - } 320 - 321 - .modal-content { 322 - padding: var(--space-6); 323 - } 324 - 325 - .modal-content .field { 326 - margin-bottom: var(--space-4); 327 - } 328 - 329 - .passkey-auth { 330 - text-align: center; 331 - } 332 - 333 - .modal-content button:not(.tab) { 334 - width: 100%; 335 - } 336 - 337 - .modal-footer { 338 - padding: 0 var(--space-6) var(--space-6); 339 - display: flex; 340 - justify-content: flex-end; 341 - } 342 - </style>
-41
frontend/src/components/Skeleton.svelte
··· 30 30 <div class="skeleton-line {size} {className}" class:last={i === lines - 1}></div> 31 31 {/each} 32 32 {/if} 33 - 34 - <style> 35 - .skeleton-card { 36 - background: var(--bg-card); 37 - border: 1px solid var(--border-color); 38 - border-radius: var(--radius-md); 39 - padding: var(--space-3); 40 - } 41 - 42 - .skeleton-header { 43 - display: flex; 44 - gap: var(--space-2); 45 - margin-bottom: var(--space-2); 46 - } 47 - 48 - .skeleton-line { 49 - height: 14px; 50 - background: var(--bg-tertiary); 51 - border-radius: var(--radius-sm); 52 - animation: skeleton-pulse 1.5s ease-in-out infinite; 53 - margin-bottom: var(--space-1); 54 - } 55 - 56 - .skeleton-line.last { 57 - margin-bottom: 0; 58 - } 59 - 60 - .skeleton-line.tiny { width: 50px; } 61 - .skeleton-line.short { width: 80px; } 62 - .skeleton-line.medium { width: 60%; } 63 - .skeleton-line.full { width: 100%; } 64 - 65 - .skeleton-circle { 66 - width: 40px; 67 - height: 40px; 68 - border-radius: 50%; 69 - background: var(--bg-tertiary); 70 - animation: skeleton-pulse 1.5s ease-in-out infinite; 71 - } 72 - 73 - </style>
-6
frontend/src/components/SsoIcon.svelte
··· 44 44 <line x1="15" y1="12" x2="3" y2="12" /> 45 45 </svg> 46 46 {/if} 47 - 48 - <style> 49 - svg { 50 - display: block; 51 - } 52 - </style>
-156
frontend/src/components/Toast.svelte
··· 7 7 dismissToast(id) 8 8 } 9 9 10 - function getIcon(type: Toast['type']): string { 11 - switch (type) { 12 - case 'success': 13 - return '✓' 14 - case 'error': 15 - return '!' 16 - case 'warning': 17 - return '⚠' 18 - case 'info': 19 - return 'i' 20 - } 21 - } 22 10 </script> 23 11 24 12 {#if toasts.length > 0} ··· 26 14 {#each toasts as toast (toast.id)} 27 15 <div 28 16 class="toast toast-{toast.type}" 29 - class:dismissing={toast.dismissing} 30 17 role="alert" 31 18 aria-live="polite" 32 19 > 33 - <span class="toast-icon">{getIcon(toast.type)}</span> 34 20 <span class="toast-message">{toast.message}</span> 35 21 <button 36 22 type="button" ··· 44 30 {/each} 45 31 </div> 46 32 {/if} 47 - 48 - <style> 49 - .toast-container { 50 - position: fixed; 51 - top: var(--space-6); 52 - right: var(--space-6); 53 - z-index: 9999; 54 - display: flex; 55 - flex-direction: column; 56 - gap: var(--space-3); 57 - max-width: min(400px, calc(100vw - var(--space-12))); 58 - pointer-events: none; 59 - } 60 - 61 - .toast { 62 - display: flex; 63 - align-items: flex-start; 64 - gap: var(--space-3); 65 - padding: var(--space-4); 66 - border-radius: var(--radius-lg); 67 - box-shadow: var(--shadow-lg); 68 - pointer-events: auto; 69 - animation: toast-in 0.1s ease-out; 70 - } 71 - 72 - .toast.dismissing { 73 - animation: toast-out 0.15s ease-in forwards; 74 - } 75 - 76 - @keyframes toast-in { 77 - from { 78 - opacity: 0; 79 - transform: scale(0.95); 80 - } 81 - to { 82 - opacity: 1; 83 - transform: scale(1); 84 - } 85 - } 86 - 87 - @keyframes toast-out { 88 - from { 89 - opacity: 1; 90 - transform: scale(1); 91 - } 92 - to { 93 - opacity: 0; 94 - transform: scale(0.95); 95 - } 96 - } 97 - 98 - .toast-success { 99 - background: var(--success-bg); 100 - border: 1px solid var(--success-border); 101 - color: var(--success-text); 102 - } 103 - 104 - .toast-error { 105 - background: var(--error-bg); 106 - border: 1px solid var(--error-border); 107 - color: var(--error-text); 108 - } 109 - 110 - .toast-warning { 111 - background: var(--warning-bg); 112 - border: 1px solid var(--warning-border); 113 - color: var(--warning-text); 114 - } 115 - 116 - .toast-info { 117 - background: var(--accent-muted); 118 - border: 1px solid var(--accent); 119 - color: var(--text-primary); 120 - } 121 - 122 - .toast-icon { 123 - flex-shrink: 0; 124 - width: 20px; 125 - height: 20px; 126 - display: flex; 127 - align-items: center; 128 - justify-content: center; 129 - border-radius: 50%; 130 - font-size: var(--text-xs); 131 - font-weight: var(--font-bold); 132 - } 133 - 134 - .toast-success .toast-icon { 135 - background: var(--success-text); 136 - color: var(--success-bg); 137 - } 138 - 139 - .toast-error .toast-icon { 140 - background: var(--error-text); 141 - color: var(--error-bg); 142 - } 143 - 144 - .toast-warning .toast-icon { 145 - background: var(--warning-text); 146 - color: var(--warning-bg); 147 - } 148 - 149 - .toast-info .toast-icon { 150 - background: var(--accent); 151 - color: var(--bg-card); 152 - } 153 - 154 - .toast-message { 155 - flex: 1; 156 - font-size: var(--text-sm); 157 - line-height: 1.4; 158 - } 159 - 160 - .toast-dismiss { 161 - flex-shrink: 0; 162 - width: 20px; 163 - height: 20px; 164 - padding: 0; 165 - border: none; 166 - background: transparent; 167 - cursor: pointer; 168 - opacity: 0.6; 169 - font-size: var(--text-sm); 170 - line-height: 1; 171 - color: inherit; 172 - border-radius: var(--radius-sm); 173 - } 174 - 175 - .toast-dismiss:hover { 176 - opacity: 1; 177 - background: rgba(0, 0, 0, 0.1); 178 - } 179 - 180 - @media (max-width: 480px) { 181 - .toast-container { 182 - top: var(--space-4); 183 - right: var(--space-4); 184 - left: var(--space-4); 185 - max-width: none; 186 - } 187 - } 188 - </style>
+1 -42
frontend/src/components/ui/Button.svelte
··· 22 22 </script> 23 23 24 24 <button 25 - class="btn btn-{variant} btn-{size}" 25 + class="{variant === 'primary' ? '' : variant} {size !== 'md' ? size : ''}" 26 26 class:full-width={fullWidth} 27 27 disabled={disabled || loading} 28 28 {...rest} 29 29 > 30 - {#if loading} 31 - <span class="spinner"></span> 32 - {/if} 33 30 {@render children()} 34 31 </button> 35 - 36 - <style> 37 - .btn { 38 - display: inline-flex; 39 - align-items: center; 40 - justify-content: center; 41 - gap: var(--space-2); 42 - } 43 - 44 - .btn-sm { 45 - padding: var(--space-2) var(--space-4); 46 - font-size: var(--text-sm); 47 - } 48 - 49 - .btn-md { 50 - padding: var(--space-4) var(--space-6); 51 - font-size: var(--text-base); 52 - } 53 - 54 - .btn-lg { 55 - padding: var(--space-5) var(--space-7); 56 - font-size: var(--text-lg); 57 - } 58 - 59 - .full-width { 60 - width: 100%; 61 - } 62 - 63 - .spinner { 64 - width: 1em; 65 - height: 1em; 66 - border: 2px solid currentColor; 67 - border-right-color: transparent; 68 - border-radius: 50%; 69 - animation: spin 0.6s linear infinite; 70 - } 71 - 72 - </style>
-28
frontend/src/components/ui/Card.svelte
··· 19 19 <div class="card card-{variant} padding-{padding}" {...rest}> 20 20 {@render children()} 21 21 </div> 22 - 23 - <style> 24 - .card { 25 - background: var(--bg-card); 26 - border: 1px solid var(--border-color); 27 - border-radius: var(--radius-xl); 28 - } 29 - 30 - .card-interactive { 31 - cursor: pointer; 32 - transition: border-color var(--transition-normal), box-shadow var(--transition-normal); 33 - } 34 - 35 - .card-interactive:hover { 36 - border-color: var(--accent); 37 - box-shadow: 0 2px 8px var(--accent-muted); 38 - } 39 - 40 - .card-danger { 41 - background: var(--error-bg); 42 - border-color: var(--error-border); 43 - } 44 - 45 - .padding-none { padding: 0; } 46 - .padding-sm { padding: var(--space-4); } 47 - .padding-md { padding: var(--space-6); } 48 - .padding-lg { padding: var(--space-7); } 49 - </style>
+1 -12
frontend/src/components/ui/Input.svelte
··· 19 19 let inputId = $derived(id || fallbackId) 20 20 </script> 21 21 22 - <div class="field"> 22 + <div> 23 23 {#if label} 24 24 <label for={inputId}>{label}</label> 25 25 {/if} ··· 30 30 <span class="hint">{hint}</span> 31 31 {/if} 32 32 </div> 33 - 34 - <style> 35 - .has-error { 36 - border-color: var(--error-text); 37 - } 38 - 39 - .has-error:focus { 40 - border-color: var(--error-text); 41 - box-shadow: 0 0 0 2px var(--error-bg); 42 - } 43 - </style>
+1 -33
frontend/src/components/ui/Message.svelte
··· 9 9 let { variant, children }: Props = $props() 10 10 </script> 11 11 12 - <div class="message message-{variant}"> 12 + <div class="message {variant}"> 13 13 {@render children()} 14 14 </div> 15 - 16 - <style> 17 - .message { 18 - padding: var(--space-4); 19 - border-radius: var(--radius-md); 20 - font-size: var(--text-sm); 21 - } 22 - 23 - .message-success { 24 - background: var(--success-bg); 25 - border: 1px solid var(--success-border); 26 - color: var(--success-text); 27 - } 28 - 29 - .message-error { 30 - background: var(--error-bg); 31 - border: 1px solid var(--error-border); 32 - color: var(--error-text); 33 - } 34 - 35 - .message-warning { 36 - background: var(--warning-bg); 37 - border: 1px solid var(--warning-border); 38 - color: var(--warning-text); 39 - } 40 - 41 - .message-info { 42 - background: var(--accent-muted); 43 - border: 1px solid var(--accent); 44 - color: var(--text-primary); 45 - } 46 - </style>
-43
frontend/src/components/ui/Page.svelte
··· 37 37 </header> 38 38 {@render children()} 39 39 </div> 40 - 41 - <style> 42 - .page { 43 - margin: 0 auto; 44 - padding: var(--space-7); 45 - } 46 - 47 - .page-sm { max-width: var(--width-md); } 48 - .page-md { max-width: var(--width-lg); } 49 - .page-lg { max-width: var(--width-xl); } 50 - 51 - header { 52 - margin-bottom: var(--space-7); 53 - } 54 - 55 - .back-link { 56 - display: inline-block; 57 - color: var(--text-secondary); 58 - font-size: var(--text-sm); 59 - text-decoration: none; 60 - margin-bottom: var(--space-3); 61 - } 62 - 63 - .back-link:hover { 64 - color: var(--accent); 65 - } 66 - 67 - .header-row { 68 - display: flex; 69 - justify-content: space-between; 70 - align-items: center; 71 - gap: var(--space-4); 72 - } 73 - 74 - h1 { 75 - margin: 0; 76 - } 77 - 78 - .actions { 79 - display: flex; 80 - gap: var(--space-3); 81 - } 82 - </style>
+1 -29
frontend/src/components/ui/Section.svelte
··· 16 16 }: Props = $props() 17 17 </script> 18 18 19 - <section class="section section-{variant}"> 19 + <section class:danger={variant === 'danger'}> 20 20 {#if title} 21 21 <h2>{title}</h2> 22 22 {/if} ··· 25 25 {/if} 26 26 {@render children()} 27 27 </section> 28 - 29 - <style> 30 - .section { 31 - background: var(--bg-secondary); 32 - border-radius: var(--radius-xl); 33 - padding: var(--space-6); 34 - } 35 - 36 - .section-danger { 37 - background: var(--error-bg); 38 - border: 1px solid var(--error-border); 39 - } 40 - 41 - .section-danger h2 { 42 - color: var(--error-text); 43 - } 44 - 45 - h2 { 46 - margin: 0 0 var(--space-3) 0; 47 - font-size: var(--text-lg); 48 - } 49 - 50 - .description { 51 - color: var(--text-secondary); 52 - font-size: var(--text-sm); 53 - margin-bottom: var(--space-5); 54 - } 55 - </style>

History

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