affora 0.1.0
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/CHANGELOG.md +16 -0
- package/CHECKS.md +40 -0
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/agent.md +70 -0
- package/checks/cli.mjs +44 -0
- package/checks/substrate.mjs +269 -0
- package/cli/affora.mjs +131 -0
- package/design.md +113 -0
- package/package.json +71 -0
- package/registry.json +83 -0
- package/rules/antd.js +173 -0
- package/rules/generic.js +299 -0
- package/rules/hidden-radio.js +94 -0
- package/rules/magento.js +325 -0
- package/rules/mui.js +230 -0
- package/rules/shadcn-baseui.js +169 -0
- package/rules/shadcn-radix.js +289 -0
- package/src/components/combobox.tsx +279 -0
- package/src/components/datatable.tsx +454 -0
- package/src/components/dialog.tsx +548 -0
- package/src/components/productcard.tsx +551 -0
- package/src/components/settingsform.tsx +583 -0
- package/src/patterns/confirmundo.tsx +134 -0
- package/src/patterns/errremedy.tsx +155 -0
- package/src/patterns/flowform.tsx +179 -0
- package/src/patterns/gatedaction.tsx +157 -0
- package/src/patterns/persistentfeedback.tsx +106 -0
- package/src/primitives/actionbutton.tsx +64 -0
- package/src/primitives/textfield.tsx +57 -0
- package/src/tokens/themes.css +1068 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import { useEffect, useId, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Dialog — a destructive-confirmation dialog.
|
|
6
|
+
*
|
|
7
|
+
* Substrate (never compromised, agent-legible):
|
|
8
|
+
* - a real <button> trigger that opens an in-DOM modal
|
|
9
|
+
* - a role="dialog" aria-modal="true" surface with a real heading,
|
|
10
|
+
* explanatory copy naming the workspace ("Aurora"), a real typed-confirm
|
|
11
|
+
* <input>, and two real <button>s (Cancel / Confirm delete)
|
|
12
|
+
* - all task-relevant text/state lives in the DOM when open
|
|
13
|
+
* - focus moves into the dialog on open and is restored on close
|
|
14
|
+
* - Escape and overlay click both dismiss
|
|
15
|
+
*
|
|
16
|
+
* Style is expressed ENTIRELY through CSS custom properties (var(--x)) supplied
|
|
17
|
+
* by an ancestor [data-flagship] theme wrapper, so the single authored component
|
|
18
|
+
* renders convincingly under all five archetypes.
|
|
19
|
+
*
|
|
20
|
+
* onCommit("on"|"off") fires when notification settings are saved.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const WORKSPACE = 'Aurora';
|
|
24
|
+
|
|
25
|
+
export const Dialog: React.FC<{ onCommit?: (v: string) => void }> = ({ onCommit }) => {
|
|
26
|
+
const [open, setOpen] = useState(false);
|
|
27
|
+
const [notif, setNotif] = useState(false);
|
|
28
|
+
const [status, setStatus] = useState<string>('');
|
|
29
|
+
|
|
30
|
+
const surfaceRef = useRef<HTMLDivElement>(null);
|
|
31
|
+
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
32
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
33
|
+
|
|
34
|
+
const titleId = useId();
|
|
35
|
+
const descId = useId();
|
|
36
|
+
const inputId = useId();
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
// Focus management: move focus into the dialog on open, restore on close,
|
|
40
|
+
// and trap Tab within the surface while open.
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!open) return;
|
|
43
|
+
const previouslyFocused = triggerRef.current;
|
|
44
|
+
// Focus the confirm input first — it is the primary interaction.
|
|
45
|
+
const raf = requestAnimationFrame(() => inputRef.current?.focus());
|
|
46
|
+
|
|
47
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
48
|
+
if (e.key === 'Escape') {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
close();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (e.key !== 'Tab') return;
|
|
54
|
+
const surface = surfaceRef.current;
|
|
55
|
+
if (!surface) return;
|
|
56
|
+
const focusables = surface.querySelectorAll<HTMLElement>(
|
|
57
|
+
'button:not([disabled]), input, [href], [tabindex]:not([tabindex="-1"])',
|
|
58
|
+
);
|
|
59
|
+
if (focusables.length === 0) return;
|
|
60
|
+
const first = focusables[0];
|
|
61
|
+
const last = focusables[focusables.length - 1];
|
|
62
|
+
const active = document.activeElement as HTMLElement | null;
|
|
63
|
+
if (e.shiftKey && active === first) {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
last.focus();
|
|
66
|
+
} else if (!e.shiftKey && active === last) {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
first.focus();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
document.addEventListener('keydown', onKeyDown);
|
|
73
|
+
const prevOverflow = document.body.style.overflow;
|
|
74
|
+
document.body.style.overflow = 'hidden';
|
|
75
|
+
|
|
76
|
+
return () => {
|
|
77
|
+
cancelAnimationFrame(raf);
|
|
78
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
79
|
+
document.body.style.overflow = prevOverflow;
|
|
80
|
+
previouslyFocused?.focus();
|
|
81
|
+
};
|
|
82
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
83
|
+
}, [open]);
|
|
84
|
+
|
|
85
|
+
function openDialog() {
|
|
86
|
+
setStatus('');
|
|
87
|
+
setOpen(true);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function close() {
|
|
91
|
+
setOpen(false);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function save() {
|
|
95
|
+
const value = notif ? 'on' : 'off';
|
|
96
|
+
setStatus(value);
|
|
97
|
+
onCommit?.(value);
|
|
98
|
+
setOpen(false);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div className="dlgf-root">
|
|
103
|
+
<style>{css}</style>
|
|
104
|
+
|
|
105
|
+
{/* Substrate trigger — the operable target where it visibly is. */}
|
|
106
|
+
<div className="dlgf-launch">
|
|
107
|
+
<div className="dlgf-launch-copy">
|
|
108
|
+
<span className="dlgf-launch-title">Preferences</span>
|
|
109
|
+
<span className="dlgf-launch-sub">
|
|
110
|
+
Notification and delivery settings for the <strong>{WORKSPACE}</strong> workspace.
|
|
111
|
+
</span>
|
|
112
|
+
</div>
|
|
113
|
+
<button
|
|
114
|
+
ref={triggerRef}
|
|
115
|
+
type="button"
|
|
116
|
+
className="dlgf-btn dlgf-btn--danger-outline"
|
|
117
|
+
aria-haspopup="dialog"
|
|
118
|
+
aria-expanded={open}
|
|
119
|
+
onClick={openDialog}
|
|
120
|
+
>
|
|
121
|
+
<GearIcon />
|
|
122
|
+
Settings
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{status && !open && (
|
|
127
|
+
<p className="dlgf-receipt" role="status">
|
|
128
|
+
<CheckIcon />
|
|
129
|
+
Notifications saved: <strong>{status}</strong>.
|
|
130
|
+
</p>
|
|
131
|
+
)}
|
|
132
|
+
|
|
133
|
+
{open && (
|
|
134
|
+
<div className="dlgf-overlay" onMouseDown={close}>
|
|
135
|
+
<div
|
|
136
|
+
ref={surfaceRef}
|
|
137
|
+
className="dlgf-surface"
|
|
138
|
+
role="dialog"
|
|
139
|
+
aria-modal="true"
|
|
140
|
+
aria-labelledby={titleId}
|
|
141
|
+
aria-describedby={descId}
|
|
142
|
+
// Stop overlay-dismiss when interacting inside the surface.
|
|
143
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
144
|
+
>
|
|
145
|
+
<div className="dlgf-head">
|
|
146
|
+
<span className="dlgf-badge" aria-hidden="true">
|
|
147
|
+
<AlertIcon />
|
|
148
|
+
</span>
|
|
149
|
+
<div className="dlgf-head-copy">
|
|
150
|
+
<h2 id={titleId} className="dlgf-title">
|
|
151
|
+
Settings
|
|
152
|
+
</h2>
|
|
153
|
+
<p id={descId} className="dlgf-desc">
|
|
154
|
+
Choose how <strong>{WORKSPACE}</strong> notifies you. Changes apply when you
|
|
155
|
+
save.
|
|
156
|
+
</p>
|
|
157
|
+
</div>
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
className="dlgf-icon-btn"
|
|
161
|
+
aria-label="Close dialog"
|
|
162
|
+
onClick={close}
|
|
163
|
+
>
|
|
164
|
+
<CloseIcon />
|
|
165
|
+
</button>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div className="dlgf-field">
|
|
169
|
+
<div className="dlgf-switchrow">
|
|
170
|
+
<span className="dlgf-label">
|
|
171
|
+
Notifications
|
|
172
|
+
<span className="dlgf-hint">Email me when a run finishes.</span>
|
|
173
|
+
</span>
|
|
174
|
+
<label className="dlgf-switch" data-on={notif || undefined}>
|
|
175
|
+
<input
|
|
176
|
+
ref={inputRef as unknown as React.RefObject<HTMLInputElement>}
|
|
177
|
+
id={inputId}
|
|
178
|
+
type="checkbox"
|
|
179
|
+
className="dlgf-switch-input"
|
|
180
|
+
checked={notif}
|
|
181
|
+
onChange={(e) => setNotif(e.target.checked)}
|
|
182
|
+
/>
|
|
183
|
+
<span className="dlgf-switch-thumb" aria-hidden="true" />
|
|
184
|
+
<span className="dlgf-switch-text">{notif ? 'on' : 'off'}</span>
|
|
185
|
+
</label>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div className="dlgf-actions">
|
|
190
|
+
<button type="button" className="dlgf-btn dlgf-btn--ghost" onClick={close}>
|
|
191
|
+
Cancel
|
|
192
|
+
</button>
|
|
193
|
+
<button type="button" className="dlgf-btn dlgf-btn--danger" onClick={save}>
|
|
194
|
+
<CheckIcon />
|
|
195
|
+
Save
|
|
196
|
+
</button>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/* ---------- inline icons (currentColor, no external assets) ---------- */
|
|
206
|
+
|
|
207
|
+
const GearIcon: React.FC = () => (
|
|
208
|
+
<svg className="dlgf-ico" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
|
|
209
|
+
<path fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinejoin="round"
|
|
210
|
+
d="M8 10.2a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4Z" />
|
|
211
|
+
<path fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinejoin="round"
|
|
212
|
+
d="m13.2 9.6.9.7-1.2 2-1.1-.4-1.2.7-.2 1.2H8l-.2-1.2-1.2-.7-1.1.4-1.2-2 .9-.7V7.9l-.9-.7 1.2-2 1.1.4 1.2-.7L8 3.7h2.4l.2 1.2 1.2.7 1.1-.4 1.2 2-.9.7v1.7Z" />
|
|
213
|
+
</svg>
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const AlertIcon: React.FC = () => (
|
|
217
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" width="20" height="20">
|
|
218
|
+
<path
|
|
219
|
+
fill="none"
|
|
220
|
+
stroke="currentColor"
|
|
221
|
+
strokeWidth="1.8"
|
|
222
|
+
strokeLinecap="round"
|
|
223
|
+
strokeLinejoin="round"
|
|
224
|
+
d="M12 3.6 21.2 19.4a1 1 0 0 1-.87 1.5H3.67a1 1 0 0 1-.87-1.5L12 3.6ZM12 9.5v4.2M12 17.2h.01"
|
|
225
|
+
/>
|
|
226
|
+
</svg>
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const CloseIcon: React.FC = () => (
|
|
230
|
+
<svg className="dlgf-ico" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
|
|
231
|
+
<path
|
|
232
|
+
fill="none"
|
|
233
|
+
stroke="currentColor"
|
|
234
|
+
strokeWidth="1.5"
|
|
235
|
+
strokeLinecap="round"
|
|
236
|
+
d="m4 4 8 8M12 4l-8 8"
|
|
237
|
+
/>
|
|
238
|
+
</svg>
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const CheckIcon: React.FC = () => (
|
|
242
|
+
<svg className="dlgf-ico" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
|
|
243
|
+
<path
|
|
244
|
+
fill="none"
|
|
245
|
+
stroke="currentColor"
|
|
246
|
+
strokeWidth="1.6"
|
|
247
|
+
strokeLinecap="round"
|
|
248
|
+
strokeLinejoin="round"
|
|
249
|
+
d="m3 8.5 3.2 3.2L13 5"
|
|
250
|
+
/>
|
|
251
|
+
</svg>
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
/* ---------- scoped, fully token-driven styling ---------- */
|
|
255
|
+
|
|
256
|
+
export const css = `
|
|
257
|
+
.dlgf-root {
|
|
258
|
+
font-family: var(--font);
|
|
259
|
+
font-size: var(--text-base);
|
|
260
|
+
line-height: var(--leading);
|
|
261
|
+
color: var(--fg);
|
|
262
|
+
letter-spacing: var(--tracking-tight);
|
|
263
|
+
-webkit-font-smoothing: antialiased;
|
|
264
|
+
}
|
|
265
|
+
.dlgf-root *, .dlgf-root *::before, .dlgf-root *::after { box-sizing: border-box; }
|
|
266
|
+
|
|
267
|
+
/* trigger card */
|
|
268
|
+
.dlgf-launch {
|
|
269
|
+
display: flex;
|
|
270
|
+
align-items: center;
|
|
271
|
+
justify-content: space-between;
|
|
272
|
+
gap: var(--pad-loose);
|
|
273
|
+
flex-wrap: wrap;
|
|
274
|
+
max-width: 560px;
|
|
275
|
+
padding: var(--pad-loose);
|
|
276
|
+
background: var(--glass-bg, var(--surface));
|
|
277
|
+
backdrop-filter: var(--glass-blur, none);
|
|
278
|
+
-webkit-backdrop-filter: var(--glass-blur, none);
|
|
279
|
+
border: 1px solid var(--glass-border, var(--border));
|
|
280
|
+
border-radius: var(--radius-lg);
|
|
281
|
+
box-shadow: var(--shadow-sm);
|
|
282
|
+
}
|
|
283
|
+
.dlgf-launch-copy { display: flex; flex-direction: column; gap: calc(var(--gap) * 0.35); min-width: 12rem; }
|
|
284
|
+
.dlgf-launch-title {
|
|
285
|
+
font-family: var(--font-display);
|
|
286
|
+
font-weight: var(--weight-display);
|
|
287
|
+
font-size: calc(var(--text-base) * var(--scale-ratio));
|
|
288
|
+
letter-spacing: var(--tracking-tight);
|
|
289
|
+
color: var(--fg);
|
|
290
|
+
}
|
|
291
|
+
.dlgf-launch-sub { color: var(--fg-muted); font-size: var(--text-base); }
|
|
292
|
+
.dlgf-launch-sub strong { color: var(--fg); font-weight: var(--weight-medium); }
|
|
293
|
+
|
|
294
|
+
.dlgf-receipt {
|
|
295
|
+
display: inline-flex;
|
|
296
|
+
align-items: center;
|
|
297
|
+
gap: calc(var(--gap) * 0.6);
|
|
298
|
+
margin: var(--pad) 0 0;
|
|
299
|
+
padding: var(--pad-tight) var(--pad);
|
|
300
|
+
color: var(--success);
|
|
301
|
+
background: var(--surface-2);
|
|
302
|
+
border: 1px solid var(--border);
|
|
303
|
+
border-radius: var(--radius-full);
|
|
304
|
+
font-size: calc(var(--text-base) / var(--scale-ratio) * var(--scale-ratio) * 0.92);
|
|
305
|
+
}
|
|
306
|
+
.dlgf-receipt strong { font-weight: var(--weight-bold); }
|
|
307
|
+
|
|
308
|
+
/* buttons */
|
|
309
|
+
.dlgf-btn {
|
|
310
|
+
display: inline-flex;
|
|
311
|
+
align-items: center;
|
|
312
|
+
justify-content: center;
|
|
313
|
+
gap: calc(var(--gap) * 0.5);
|
|
314
|
+
padding: var(--pad-tight) var(--pad);
|
|
315
|
+
min-height: calc(var(--text-base) * 2.6);
|
|
316
|
+
font-family: var(--font);
|
|
317
|
+
font-size: var(--text-base);
|
|
318
|
+
font-weight: var(--weight-medium);
|
|
319
|
+
letter-spacing: var(--tracking-tight);
|
|
320
|
+
line-height: 1;
|
|
321
|
+
border-radius: var(--radius);
|
|
322
|
+
border: 1px solid transparent;
|
|
323
|
+
cursor: pointer;
|
|
324
|
+
transition:
|
|
325
|
+
background var(--dur-fast) var(--ease),
|
|
326
|
+
color var(--dur-fast) var(--ease),
|
|
327
|
+
border-color var(--dur-fast) var(--ease),
|
|
328
|
+
box-shadow var(--dur-fast) var(--ease),
|
|
329
|
+
transform var(--dur-fast) var(--ease);
|
|
330
|
+
}
|
|
331
|
+
.dlgf-btn:active { transform: translateY(0.5px); }
|
|
332
|
+
.dlgf-btn:focus-visible {
|
|
333
|
+
outline: none;
|
|
334
|
+
box-shadow: 0 0 0 3px var(--ring);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.dlgf-btn--danger-outline {
|
|
338
|
+
color: var(--danger);
|
|
339
|
+
background: var(--surface);
|
|
340
|
+
border-color: var(--border-strong);
|
|
341
|
+
}
|
|
342
|
+
.dlgf-btn--danger-outline:hover {
|
|
343
|
+
border-color: var(--danger);
|
|
344
|
+
background: var(--surface-2);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.dlgf-btn--ghost {
|
|
348
|
+
color: var(--fg-muted);
|
|
349
|
+
background: transparent;
|
|
350
|
+
border-color: var(--border-strong);
|
|
351
|
+
}
|
|
352
|
+
.dlgf-btn--ghost:hover { color: var(--fg); background: var(--surface-2); }
|
|
353
|
+
|
|
354
|
+
.dlgf-btn--danger {
|
|
355
|
+
color: var(--accent-fg);
|
|
356
|
+
background: var(--danger);
|
|
357
|
+
border-color: var(--danger);
|
|
358
|
+
font-weight: var(--weight-bold);
|
|
359
|
+
box-shadow: var(--shadow-sm);
|
|
360
|
+
}
|
|
361
|
+
.dlgf-btn--danger:hover:not(:disabled) { filter: brightness(0.94); }
|
|
362
|
+
.dlgf-btn--danger:disabled,
|
|
363
|
+
.dlgf-btn--danger[aria-disabled="true"] {
|
|
364
|
+
cursor: not-allowed;
|
|
365
|
+
opacity: 0.5;
|
|
366
|
+
box-shadow: none;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.dlgf-icon-btn {
|
|
370
|
+
display: inline-flex;
|
|
371
|
+
align-items: center;
|
|
372
|
+
justify-content: center;
|
|
373
|
+
width: calc(var(--text-base) * 2.2);
|
|
374
|
+
height: calc(var(--text-base) * 2.2);
|
|
375
|
+
margin: calc(var(--pad-tight) * -0.5);
|
|
376
|
+
color: var(--fg-subtle);
|
|
377
|
+
background: transparent;
|
|
378
|
+
border: 1px solid transparent;
|
|
379
|
+
border-radius: var(--radius-sm);
|
|
380
|
+
cursor: pointer;
|
|
381
|
+
transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
|
|
382
|
+
}
|
|
383
|
+
.dlgf-icon-btn:hover { color: var(--fg); background: var(--surface-2); }
|
|
384
|
+
.dlgf-icon-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--ring); }
|
|
385
|
+
|
|
386
|
+
.dlgf-ico { width: 1em; height: 1em; flex: none; }
|
|
387
|
+
|
|
388
|
+
/* overlay + surface */
|
|
389
|
+
.dlgf-overlay {
|
|
390
|
+
position: fixed;
|
|
391
|
+
inset: 0;
|
|
392
|
+
z-index: 50;
|
|
393
|
+
display: flex;
|
|
394
|
+
align-items: center;
|
|
395
|
+
justify-content: center;
|
|
396
|
+
padding: var(--pad-loose);
|
|
397
|
+
background: color-mix(in srgb, var(--fg) 55%, transparent);
|
|
398
|
+
backdrop-filter: blur(2px);
|
|
399
|
+
animation: dlgf-fade var(--dur) var(--ease);
|
|
400
|
+
}
|
|
401
|
+
@supports not (background: color-mix(in srgb, red, blue)) {
|
|
402
|
+
.dlgf-overlay { background: rgba(0, 0, 0, 0.55); }
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.dlgf-surface {
|
|
406
|
+
width: 100%;
|
|
407
|
+
max-width: 30rem;
|
|
408
|
+
background: var(--glass-bg, var(--surface));
|
|
409
|
+
backdrop-filter: var(--glass-blur, none);
|
|
410
|
+
-webkit-backdrop-filter: var(--glass-blur, none);
|
|
411
|
+
color: var(--fg);
|
|
412
|
+
border: 1px solid var(--glass-border, var(--border));
|
|
413
|
+
border-radius: var(--radius-lg);
|
|
414
|
+
box-shadow: var(--shadow-lg);
|
|
415
|
+
padding: var(--pad-loose);
|
|
416
|
+
display: flex;
|
|
417
|
+
flex-direction: column;
|
|
418
|
+
gap: var(--gap);
|
|
419
|
+
transform-origin: center;
|
|
420
|
+
animation: dlgf-pop var(--dur) var(--ease-emphasis);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.dlgf-head { display: flex; align-items: flex-start; gap: var(--gap); }
|
|
424
|
+
.dlgf-badge {
|
|
425
|
+
display: inline-flex;
|
|
426
|
+
align-items: center;
|
|
427
|
+
justify-content: center;
|
|
428
|
+
flex: none;
|
|
429
|
+
width: calc(var(--text-base) * 2.8);
|
|
430
|
+
height: calc(var(--text-base) * 2.8);
|
|
431
|
+
color: var(--danger);
|
|
432
|
+
background: color-mix(in srgb, var(--danger) 12%, var(--surface));
|
|
433
|
+
border: 1px solid color-mix(in srgb, var(--danger) 28%, transparent);
|
|
434
|
+
border-radius: var(--radius);
|
|
435
|
+
}
|
|
436
|
+
@supports not (background: color-mix(in srgb, red, blue)) {
|
|
437
|
+
.dlgf-badge { background: var(--surface-2); }
|
|
438
|
+
}
|
|
439
|
+
.dlgf-head-copy { flex: 1; display: flex; flex-direction: column; gap: calc(var(--gap) * 0.4); min-width: 0; }
|
|
440
|
+
.dlgf-title {
|
|
441
|
+
margin: 0;
|
|
442
|
+
font-family: var(--font-display);
|
|
443
|
+
font-weight: var(--weight-display);
|
|
444
|
+
font-size: calc(var(--text-base) * var(--scale-ratio) * var(--scale-ratio));
|
|
445
|
+
line-height: 1.2;
|
|
446
|
+
letter-spacing: var(--tracking-tight);
|
|
447
|
+
color: var(--fg);
|
|
448
|
+
}
|
|
449
|
+
.dlgf-desc { margin: 0; color: var(--fg-muted); font-size: var(--text-base); }
|
|
450
|
+
.dlgf-desc strong { color: var(--fg); font-weight: var(--weight-bold); }
|
|
451
|
+
.dlgf-num {
|
|
452
|
+
font-family: var(--font-mono);
|
|
453
|
+
font-weight: var(--weight-medium);
|
|
454
|
+
font-variant-numeric: tabular-nums;
|
|
455
|
+
color: var(--fg);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/* confirm field */
|
|
459
|
+
.dlgf-field { display: flex; flex-direction: column; gap: calc(var(--gap) * 0.5); margin-top: calc(var(--gap) * 0.25); }
|
|
460
|
+
.dlgf-label { color: var(--fg-muted); font-size: calc(var(--text-base) * 0.94); font-weight: var(--weight-medium); }
|
|
461
|
+
.dlgf-code {
|
|
462
|
+
font-family: var(--font-mono);
|
|
463
|
+
font-size: 0.92em;
|
|
464
|
+
color: var(--danger);
|
|
465
|
+
background: var(--surface-2);
|
|
466
|
+
border: 1px solid var(--border);
|
|
467
|
+
border-radius: var(--radius-sm);
|
|
468
|
+
padding: 0.05em 0.4em;
|
|
469
|
+
}
|
|
470
|
+
.dlgf-input {
|
|
471
|
+
width: 100%;
|
|
472
|
+
padding: var(--pad-tight) var(--pad);
|
|
473
|
+
min-height: calc(var(--text-base) * 2.8);
|
|
474
|
+
font-family: var(--font-mono);
|
|
475
|
+
font-size: var(--text-base);
|
|
476
|
+
color: var(--fg);
|
|
477
|
+
background: var(--bg);
|
|
478
|
+
border: 1px solid var(--border-strong);
|
|
479
|
+
border-radius: var(--radius);
|
|
480
|
+
transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
|
|
481
|
+
}
|
|
482
|
+
.dlgf-input::placeholder { color: var(--fg-subtle); }
|
|
483
|
+
.dlgf-input:focus-visible {
|
|
484
|
+
outline: none;
|
|
485
|
+
border-color: var(--accent);
|
|
486
|
+
box-shadow: 0 0 0 3px var(--ring);
|
|
487
|
+
}
|
|
488
|
+
.dlgf-hint {
|
|
489
|
+
display: inline-flex;
|
|
490
|
+
align-items: center;
|
|
491
|
+
gap: calc(var(--gap) * 0.4);
|
|
492
|
+
min-height: 1.4em;
|
|
493
|
+
color: var(--fg-subtle);
|
|
494
|
+
font-size: calc(var(--text-base) * 0.88);
|
|
495
|
+
}
|
|
496
|
+
.dlgf-hint .dlgf-ico { color: var(--success); }
|
|
497
|
+
|
|
498
|
+
.dlgf-switchrow {
|
|
499
|
+
display: flex; align-items: center; justify-content: space-between; gap: var(--gap);
|
|
500
|
+
}
|
|
501
|
+
.dlgf-switch {
|
|
502
|
+
display: inline-flex; align-items: center; gap: var(--pad-tight);
|
|
503
|
+
padding: var(--pad-tight) var(--pad);
|
|
504
|
+
border: 1px solid var(--border-strong);
|
|
505
|
+
border-radius: var(--radius-full);
|
|
506
|
+
background: var(--surface-2); color: var(--fg);
|
|
507
|
+
font: inherit; cursor: pointer;
|
|
508
|
+
transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
|
|
509
|
+
}
|
|
510
|
+
.dlgf-switch[data-on] { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
|
|
511
|
+
.dlgf-switch:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--ring); }
|
|
512
|
+
.dlgf-switch { position: relative; }
|
|
513
|
+
.dlgf-switch-input {
|
|
514
|
+
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
515
|
+
margin: 0; opacity: 0; cursor: pointer;
|
|
516
|
+
}
|
|
517
|
+
.dlgf-switch-thumb {
|
|
518
|
+
width: .75em; height: .75em; border-radius: var(--radius-full);
|
|
519
|
+
background: currentColor; opacity: .55;
|
|
520
|
+
transition: opacity var(--dur-fast) var(--ease);
|
|
521
|
+
}
|
|
522
|
+
.dlgf-switch[data-on] .dlgf-switch { position: relative; }
|
|
523
|
+
.dlgf-switch-input {
|
|
524
|
+
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
525
|
+
margin: 0; opacity: 0; cursor: pointer;
|
|
526
|
+
}
|
|
527
|
+
.dlgf-switch-thumb { opacity: 1; }
|
|
528
|
+
.dlgf-switch-text { font-variant-numeric: tabular-nums; }
|
|
529
|
+
.dlgf-actions {
|
|
530
|
+
display: flex;
|
|
531
|
+
justify-content: flex-end;
|
|
532
|
+
gap: calc(var(--gap) * 0.75);
|
|
533
|
+
margin-top: var(--gap);
|
|
534
|
+
padding-top: var(--pad);
|
|
535
|
+
border-top: 1px solid var(--border);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
@keyframes dlgf-fade { from { opacity: 0; } to { opacity: 1; } }
|
|
539
|
+
@keyframes dlgf-pop {
|
|
540
|
+
from { opacity: 0; transform: translateY(8px) scale(0.98); }
|
|
541
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
542
|
+
}
|
|
543
|
+
@media (prefers-reduced-motion: reduce) {
|
|
544
|
+
.dlgf-overlay, .dlgf-surface, .dlgf-btn { animation: none; transition: none; }
|
|
545
|
+
}
|
|
546
|
+
`;
|
|
547
|
+
|
|
548
|
+
export default Dialog;
|