+8
-608
Diff
round #0
-46
frontend/src/components/AccountTypeSwitcher.svelte
-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
+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
-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
-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
+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
-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
-6
frontend/src/components/SsoIcon.svelte
-156
frontend/src/components/Toast.svelte
-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
+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
-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
+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
+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
-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
+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
oyster.cafe
submitted
#0
1 commit
expand
collapse
refactor(frontend): extract UI primitive component styles
expand 0 comments
pull request successfully merged