@motion-proto/live-tokens 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -4
- package/dist-plugin/index.cjs +77 -3
- package/dist-plugin/index.js +77 -3
- package/package.json +10 -5
- package/src/component-editor/DialogEditor.svelte +2 -2
- package/src/component-editor/NotificationEditor.svelte +2 -2
- package/src/component-editor/scaffolding/ComponentFileManager.svelte +4 -1
- package/src/component-editor/scaffolding/SaveAsDialog.svelte +24 -7
- package/src/lib/ColumnsOverlay.svelte +1 -1
- package/src/lib/componentPersist.ts +65 -0
- package/src/lib/presetService.ts +121 -1
- package/src/lib/productionPulse.ts +32 -0
- package/src/pages/ComponentEditorPage.svelte +33 -1
- package/src/pages/Editor.svelte +8 -2
- package/src/pages/EditorShell.svelte +25 -0
- package/src/styles/site.css +138 -0
- package/src/styles/tokens.css +24 -0
- package/src/styles/ui-form-controls.css +186 -0
- package/src/ui/FontStackEditor.svelte +1 -1
- package/src/ui/PresetFileManager.svelte +763 -216
- package/src/ui/ProjectFontsSection.svelte +4 -4
- package/src/ui/ThemeFileManager.svelte +557 -307
- package/src/ui/UnsavedComponentsDialog.svelte +315 -0
- package/src/styles/form-controls.css +0 -188
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { selectedComponent } from '../lib/editorViewStore';
|
|
3
|
+
import { componentRegistryEntries } from '../component-editor/registry';
|
|
4
|
+
import { saveActiveComponentConfig } from '../lib/componentPersist';
|
|
5
|
+
import UIDialog from './UIDialog.svelte';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
show?: boolean;
|
|
9
|
+
/** Component IDs that have unsaved edits. */
|
|
10
|
+
dirtyComponents?: string[];
|
|
11
|
+
/** Called when the user chooses to capture the preset anyway (after handling, or skipping, unsaved components). */
|
|
12
|
+
onproceed?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { show = $bindable(false), dirtyComponents = [], onproceed }: Props = $props();
|
|
16
|
+
|
|
17
|
+
// Map id → registry entry (label, icon). Components not in the registry get
|
|
18
|
+
// a fallback so the dialog never renders an empty row. Typed as a plain
|
|
19
|
+
// string-keyed record so a stray dirty id (one we don't recognise) doesn't
|
|
20
|
+
// trip the typed `ComponentId` key.
|
|
21
|
+
const entryById: Record<string, { id: string; label: string; icon: string }> =
|
|
22
|
+
Object.fromEntries(
|
|
23
|
+
componentRegistryEntries.map((e) => [e.id, { id: e.id, label: e.label, icon: e.icon }]),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
type RowStatus = 'idle' | 'saving' | 'saved' | 'default' | 'error';
|
|
27
|
+
let rowStatus: Record<string, RowStatus> = $state({});
|
|
28
|
+
let savingAll = $state(false);
|
|
29
|
+
/** Snapshot the dirty list at open-time so rows don't vanish as the user
|
|
30
|
+
* saves them one-by-one (componentDirty updates instantly on save). */
|
|
31
|
+
let rowIds: string[] = $state([]);
|
|
32
|
+
|
|
33
|
+
function entryFor(id: string): { id: string; label: string; icon: string } {
|
|
34
|
+
return entryById[id] ?? { id, label: id, icon: 'fas fa-cube' };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function saveOne(id: string): Promise<RowStatus> {
|
|
38
|
+
rowStatus[id] = 'saving';
|
|
39
|
+
const result = await saveActiveComponentConfig(id);
|
|
40
|
+
if (result.ok) {
|
|
41
|
+
rowStatus[id] = 'saved';
|
|
42
|
+
return 'saved';
|
|
43
|
+
}
|
|
44
|
+
rowStatus[id] = result.reason === 'default' ? 'default' : 'error';
|
|
45
|
+
return rowStatus[id];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function handleSaveAll() {
|
|
49
|
+
savingAll = true;
|
|
50
|
+
for (const id of rowIds) {
|
|
51
|
+
// Skip rows the user already resolved this session.
|
|
52
|
+
if (rowStatus[id] === 'saved') continue;
|
|
53
|
+
await saveOne(id);
|
|
54
|
+
}
|
|
55
|
+
savingAll = false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function jumpTo(id: string) {
|
|
59
|
+
selectedComponent.set(id);
|
|
60
|
+
show = false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function handleProceed() {
|
|
64
|
+
show = false;
|
|
65
|
+
onproceed?.();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Snapshot the dirty list and reset row status whenever the dialog re-opens.
|
|
69
|
+
// Snapshotting keeps rows stable while the user saves them one-by-one (the
|
|
70
|
+
// underlying componentDirty store drops ids as soon as save completes).
|
|
71
|
+
$effect(() => {
|
|
72
|
+
if (show) {
|
|
73
|
+
rowIds = [...dirtyComponents];
|
|
74
|
+
rowStatus = {};
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
let allSaved = $derived(
|
|
79
|
+
rowIds.length > 0 && rowIds.every((id) => rowStatus[id] === 'saved'),
|
|
80
|
+
);
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<UIDialog
|
|
84
|
+
bind:show
|
|
85
|
+
title="Unsaved component changes"
|
|
86
|
+
cancelLabel="Cancel"
|
|
87
|
+
width="440px"
|
|
88
|
+
>
|
|
89
|
+
<div class="ucd-body">
|
|
90
|
+
<p class="ucd-lede">
|
|
91
|
+
{rowIds.length}
|
|
92
|
+
{rowIds.length === 1 ? 'component has' : 'components have'}
|
|
93
|
+
unsaved edits. Presets capture the files on disk, so these edits won't be
|
|
94
|
+
included until they're saved.
|
|
95
|
+
</p>
|
|
96
|
+
|
|
97
|
+
<ul class="ucd-list">
|
|
98
|
+
{#each rowIds as id}
|
|
99
|
+
{@const entry = entryFor(id)}
|
|
100
|
+
{@const status = rowStatus[id] ?? 'idle'}
|
|
101
|
+
<li class="ucd-row" class:saved={status === 'saved'}>
|
|
102
|
+
<span class="ucd-dot" class:saved={status === 'saved'} aria-hidden="true"></span>
|
|
103
|
+
<button
|
|
104
|
+
type="button"
|
|
105
|
+
class="ucd-name"
|
|
106
|
+
onclick={() => jumpTo(id)}
|
|
107
|
+
title="Open {entry.label} editor"
|
|
108
|
+
>
|
|
109
|
+
<i class={entry.icon}></i>
|
|
110
|
+
<span>{entry.label}</span>
|
|
111
|
+
</button>
|
|
112
|
+
<span class="ucd-status" class:saved={status === 'saved'} class:default={status === 'default'} class:error={status === 'error'}>
|
|
113
|
+
{#if status === 'saving'}Saving…
|
|
114
|
+
{:else if status === 'saved'}Saved
|
|
115
|
+
{:else if status === 'default'}On default — rename first
|
|
116
|
+
{:else if status === 'error'}Error
|
|
117
|
+
{/if}
|
|
118
|
+
</span>
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
class="ucd-row-btn"
|
|
122
|
+
onclick={() => saveOne(id)}
|
|
123
|
+
disabled={status === 'saving' || status === 'saved' || savingAll}
|
|
124
|
+
title="Save this component"
|
|
125
|
+
>
|
|
126
|
+
<i class="fas fa-save"></i>
|
|
127
|
+
</button>
|
|
128
|
+
</li>
|
|
129
|
+
{/each}
|
|
130
|
+
</ul>
|
|
131
|
+
|
|
132
|
+
<div class="ucd-actions">
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
class="ucd-btn primary"
|
|
136
|
+
onclick={handleSaveAll}
|
|
137
|
+
disabled={savingAll || allSaved}
|
|
138
|
+
>
|
|
139
|
+
{savingAll ? 'Saving…' : allSaved ? 'All saved' : 'Save all'}
|
|
140
|
+
</button>
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
class="ucd-btn"
|
|
144
|
+
onclick={handleProceed}
|
|
145
|
+
title="Save the preset using the files currently on disk"
|
|
146
|
+
>
|
|
147
|
+
Continue without saving
|
|
148
|
+
</button>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</UIDialog>
|
|
152
|
+
|
|
153
|
+
<style>
|
|
154
|
+
.ucd-body {
|
|
155
|
+
display: flex;
|
|
156
|
+
flex-direction: column;
|
|
157
|
+
gap: var(--ui-space-12);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.ucd-lede {
|
|
161
|
+
margin: 0;
|
|
162
|
+
font-size: var(--ui-font-size-sm);
|
|
163
|
+
line-height: 1.5;
|
|
164
|
+
color: var(--ui-text-secondary);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.ucd-list {
|
|
168
|
+
list-style: none;
|
|
169
|
+
margin: 0;
|
|
170
|
+
padding: 0;
|
|
171
|
+
display: flex;
|
|
172
|
+
flex-direction: column;
|
|
173
|
+
gap: var(--ui-space-2);
|
|
174
|
+
max-height: 240px;
|
|
175
|
+
overflow-y: auto;
|
|
176
|
+
border: 1px solid var(--ui-border-faint);
|
|
177
|
+
border-radius: var(--ui-radius-md);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.ucd-row {
|
|
181
|
+
display: grid;
|
|
182
|
+
grid-template-columns: 14px 1fr auto auto;
|
|
183
|
+
align-items: center;
|
|
184
|
+
gap: var(--ui-space-8);
|
|
185
|
+
padding: var(--ui-space-6) var(--ui-space-10);
|
|
186
|
+
border-bottom: 1px solid var(--ui-border-faint);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.ucd-row:last-child {
|
|
190
|
+
border-bottom: none;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.ucd-dot {
|
|
194
|
+
width: 8px;
|
|
195
|
+
height: 8px;
|
|
196
|
+
border-radius: 50%;
|
|
197
|
+
background: var(--ui-highlight);
|
|
198
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-highlight) 22%, transparent);
|
|
199
|
+
justify-self: center;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.ucd-dot.saved {
|
|
203
|
+
background: var(--ui-text-success, #5aa85e);
|
|
204
|
+
box-shadow: none;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.ucd-name {
|
|
208
|
+
display: inline-flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
gap: var(--ui-space-6);
|
|
211
|
+
padding: var(--ui-space-2) var(--ui-space-4);
|
|
212
|
+
background: none;
|
|
213
|
+
border: none;
|
|
214
|
+
color: var(--ui-text-primary);
|
|
215
|
+
font-size: var(--ui-font-size-md);
|
|
216
|
+
text-align: left;
|
|
217
|
+
cursor: pointer;
|
|
218
|
+
border-radius: var(--ui-radius-sm);
|
|
219
|
+
min-width: 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.ucd-name i {
|
|
223
|
+
width: 1rem;
|
|
224
|
+
text-align: center;
|
|
225
|
+
color: var(--ui-text-tertiary);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.ucd-name:hover {
|
|
229
|
+
background: var(--ui-hover);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.ucd-status {
|
|
233
|
+
font-size: var(--ui-font-size-xs);
|
|
234
|
+
color: var(--ui-text-tertiary);
|
|
235
|
+
white-space: nowrap;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.ucd-status.saved {
|
|
239
|
+
color: var(--ui-text-success, #5aa85e);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.ucd-status.default {
|
|
243
|
+
color: var(--ui-highlight);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.ucd-status.error {
|
|
247
|
+
color: var(--ui-text-muted);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.ucd-row-btn {
|
|
251
|
+
display: inline-flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
justify-content: center;
|
|
254
|
+
width: 28px;
|
|
255
|
+
height: 28px;
|
|
256
|
+
padding: 0;
|
|
257
|
+
background: var(--ui-surface-low);
|
|
258
|
+
border: 1px solid var(--ui-border-subtle);
|
|
259
|
+
border-radius: var(--ui-radius-sm);
|
|
260
|
+
color: var(--ui-text-secondary);
|
|
261
|
+
cursor: pointer;
|
|
262
|
+
transition: all var(--ui-transition-fast);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.ucd-row-btn:hover:not(:disabled) {
|
|
266
|
+
background: var(--ui-surface);
|
|
267
|
+
color: var(--ui-text-primary);
|
|
268
|
+
border-color: var(--ui-border-default);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.ucd-row-btn:disabled {
|
|
272
|
+
opacity: 0.4;
|
|
273
|
+
cursor: not-allowed;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.ucd-actions {
|
|
277
|
+
display: flex;
|
|
278
|
+
flex-wrap: wrap;
|
|
279
|
+
justify-content: flex-end;
|
|
280
|
+
gap: var(--ui-space-8);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.ucd-btn {
|
|
284
|
+
padding: var(--ui-space-6) var(--ui-space-12);
|
|
285
|
+
background: var(--ui-surface-low);
|
|
286
|
+
border: 1px solid var(--ui-border-subtle);
|
|
287
|
+
border-radius: var(--ui-radius-md);
|
|
288
|
+
color: var(--ui-text-secondary);
|
|
289
|
+
font-size: var(--ui-font-size-md);
|
|
290
|
+
cursor: pointer;
|
|
291
|
+
transition: all var(--ui-transition-fast);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.ucd-btn:hover:not(:disabled) {
|
|
295
|
+
background: var(--ui-surface);
|
|
296
|
+
color: var(--ui-text-primary);
|
|
297
|
+
border-color: var(--ui-border-default);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.ucd-btn:disabled {
|
|
301
|
+
opacity: 0.45;
|
|
302
|
+
cursor: not-allowed;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.ucd-btn.primary {
|
|
306
|
+
background: var(--ui-surface-high);
|
|
307
|
+
border-color: var(--ui-border-medium);
|
|
308
|
+
color: var(--ui-text-primary);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.ucd-btn.primary:hover:not(:disabled) {
|
|
312
|
+
background: var(--ui-surface-higher);
|
|
313
|
+
border-color: var(--ui-border-strong);
|
|
314
|
+
}
|
|
315
|
+
</style>
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/* Form Controls - Global Styling for Dropdowns, Selects, Inputs */
|
|
2
|
-
/* Ensures consistent, legible styling across the application */
|
|
3
|
-
|
|
4
|
-
/* ========================================
|
|
5
|
-
Form Field Layouts
|
|
6
|
-
======================================== */
|
|
7
|
-
|
|
8
|
-
/* Vertical Layout - Label Above Input/Select */
|
|
9
|
-
/* Usage: Add .form-field-vertical to container div wrapping label + input/select */
|
|
10
|
-
.form-field-vertical {
|
|
11
|
-
display: flex;
|
|
12
|
-
flex-direction: column;
|
|
13
|
-
align-items: stretch;
|
|
14
|
-
gap: var(--space-4);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.form-label {
|
|
18
|
-
margin-bottom: 0;
|
|
19
|
-
font-size: var(--font-size-md);
|
|
20
|
-
color: var(--text-primary);
|
|
21
|
-
font-weight: var(--font-weight-light);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/* Horizontal Layout - Label Beside Input/Select */
|
|
25
|
-
/* Usage: Add .form-field-horizontal to container div wrapping label + input/select */
|
|
26
|
-
.form-field-horizontal {
|
|
27
|
-
display: flex;
|
|
28
|
-
justify-content: space-between;
|
|
29
|
-
align-items: center;
|
|
30
|
-
gap: var(--space-8);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.form-field-horizontal .form-label {
|
|
34
|
-
white-space: nowrap;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/* Inline Layout - Label and Input/Select Close Together */
|
|
38
|
-
/* Usage: Add .form-field-inline to container div wrapping label + input/select */
|
|
39
|
-
.form-field-inline {
|
|
40
|
-
display: flex;
|
|
41
|
-
align-items: center;
|
|
42
|
-
gap: var(--space-12);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.form-field-inline .form-label {
|
|
46
|
-
white-space: nowrap;
|
|
47
|
-
flex-shrink: 0;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.form-field-inline .form-select,
|
|
51
|
-
.form-field-inline .form-input {
|
|
52
|
-
flex-shrink: 0;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/* ========================================
|
|
56
|
-
Form Control Styling
|
|
57
|
-
======================================== */
|
|
58
|
-
|
|
59
|
-
/* Select/Dropdown Styling - Design System Compliant */
|
|
60
|
-
.form-select {
|
|
61
|
-
/* Base styling using design system colors */
|
|
62
|
-
/* No vertical padding - let min-height and line-height center text naturally */
|
|
63
|
-
padding: 0 var(--space-16);
|
|
64
|
-
min-height: 2.375rem; /* ~38px to match button height */
|
|
65
|
-
background: var(--surface-neutral-lowest) !important;
|
|
66
|
-
border: 1px solid var(--border-neutral) !important;
|
|
67
|
-
border-radius: var(--radius-md);
|
|
68
|
-
color: var(--text-primary) !important;
|
|
69
|
-
font-family: var(--font-sans);
|
|
70
|
-
font-size: var(--font-size-md);
|
|
71
|
-
font-weight: var(--font-weight-light);
|
|
72
|
-
line-height: var(--line-height-md);
|
|
73
|
-
vertical-align: middle;
|
|
74
|
-
cursor: pointer;
|
|
75
|
-
transition: all var(--duration-150);
|
|
76
|
-
/* Prevent clipping */
|
|
77
|
-
overflow: visible !important;
|
|
78
|
-
box-sizing: border-box !important;
|
|
79
|
-
/* Reset browser defaults */
|
|
80
|
-
-webkit-appearance: none;
|
|
81
|
-
-moz-appearance: none;
|
|
82
|
-
appearance: none;
|
|
83
|
-
/* Custom dropdown arrow */
|
|
84
|
-
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23888' d='M6 8L1 3h10z'/%3E%3C/svg%3E") !important;
|
|
85
|
-
background-repeat: no-repeat !important;
|
|
86
|
-
background-position: right var(--space-12) center !important;
|
|
87
|
-
padding-right: var(--space-32) !important;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.form-select:hover:not(:disabled) {
|
|
91
|
-
background-color: var(--surface-neutral-low) !important;
|
|
92
|
-
border-color: var(--border-neutral-medium) !important;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.form-select:focus {
|
|
96
|
-
outline: none;
|
|
97
|
-
border-color: var(--border-neutral-strong) !important;
|
|
98
|
-
box-shadow: 0 0 0 2px hsla(0, 58%, 50%, 0.2);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.form-select:focus-visible {
|
|
102
|
-
outline: 2px solid var(--color-brand-500);
|
|
103
|
-
outline-offset: 2px;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.form-select:active:not(:disabled) {
|
|
107
|
-
background-color: var(--surface-neutral) !important;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.form-select:disabled {
|
|
111
|
-
background-color: var(--surface-neutral-lowest) !important;
|
|
112
|
-
border-color: var(--border-neutral-faint) !important;
|
|
113
|
-
color: var(--text-disabled) !important;
|
|
114
|
-
cursor: not-allowed;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/* Option Styling - Critical for Legibility */
|
|
118
|
-
/* Note: Most option styling is controlled by browser/OS and cannot be fully overridden */
|
|
119
|
-
/* These styles apply where browsers allow (limited support in Chrome/Firefox) */
|
|
120
|
-
.form-select option {
|
|
121
|
-
background-color: var(--surface-neutral-lowest) !important;
|
|
122
|
-
color: var(--text-primary) !important;
|
|
123
|
-
padding: var(--space-8) var(--space-12);
|
|
124
|
-
min-height: 2rem;
|
|
125
|
-
font-size: var(--font-size-md);
|
|
126
|
-
font-family: var(--font-sans);
|
|
127
|
-
line-height: var(--line-height-md);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/* Disabled options */
|
|
131
|
-
.form-select option:disabled {
|
|
132
|
-
color: var(--text-disabled) !important;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/* Input Field Styling */
|
|
136
|
-
.form-input {
|
|
137
|
-
padding: var(--space-8);
|
|
138
|
-
background: var(--surface-neutral-lowest);
|
|
139
|
-
border: 1px solid var(--border-neutral);
|
|
140
|
-
border-radius: var(--radius-md);
|
|
141
|
-
color: var(--text-primary);
|
|
142
|
-
font-family: var(--font-sans);
|
|
143
|
-
font-size: var(--font-size-md);
|
|
144
|
-
transition: border-color var(--duration-150);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.form-input:hover:not(:disabled) {
|
|
148
|
-
border-color: var(--border-neutral-strong);
|
|
149
|
-
background: var(--surface-neutral-low);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.form-input:focus {
|
|
153
|
-
outline: none;
|
|
154
|
-
border-color: var(--border-neutral-strong);
|
|
155
|
-
box-shadow: 0 0 0 3px hsla(240, 5%, 38%, 0.2);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.form-input:disabled {
|
|
159
|
-
background: var(--surface-neutral-lowest);
|
|
160
|
-
border-color: var(--border-neutral-faint);
|
|
161
|
-
color: var(--text-disabled);
|
|
162
|
-
cursor: not-allowed;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/* Placeholder text styling */
|
|
166
|
-
.form-input::placeholder {
|
|
167
|
-
color: var(--text-muted);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/* Number input spinner buttons */
|
|
171
|
-
.form-input[type="number"]::-webkit-inner-spin-button,
|
|
172
|
-
.form-input[type="number"]::-webkit-outer-spin-button {
|
|
173
|
-
opacity: 1;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/* Checkbox and Radio Styling */
|
|
177
|
-
.form-checkbox,
|
|
178
|
-
.form-radio {
|
|
179
|
-
cursor: pointer;
|
|
180
|
-
width: var(--space-16);
|
|
181
|
-
height: var(--space-16);
|
|
182
|
-
accent-color: var(--color-neutral-400);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.form-checkbox:disabled,
|
|
186
|
-
.form-radio:disabled {
|
|
187
|
-
cursor: not-allowed;
|
|
188
|
-
}
|