@meddleware/token-deployer-ui 0.0.2 → 0.0.4
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/package.json +3 -1
- package/src/App.vue +1 -2
- package/src/component-styles.css +257 -0
- package/src/components/ConfigForm.vue +104 -93
- package/src/components/GithubPush.vue +4 -4
- package/src/components/IconPicker.vue +19 -73
- package/src/components/LicensePicker.vue +7 -9
- package/src/components/ResultPanel.vue +2 -2
- package/src/components/ReviewPanel.vue +3 -3
- package/src/components/TokenDeployerView.vue +81 -14
- package/src/components/WalletBar.vue +6 -7
- package/src/components/WalrusBlobBrowser.vue +2 -2
- package/src/components/WalrusIconUpload.vue +4 -4
- package/src/index.ts +1 -0
- package/src/main.ts +5 -1
- package/src/styles.css +3 -335
- package/src/components/AppNotice.vue +0 -15
- package/src/components/ColorModeControl.vue +0 -81
- package/src/components/FieldHint.vue +0 -79
- package/src/components/FormField.vue +0 -28
- package/src/components/FormSelect.vue +0 -43
- package/src/composables/useColorMode.ts +0 -76
package/src/styles.css
CHANGED
|
@@ -1,64 +1,7 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
1
|
+
/* SPA shell globals — imported by main.ts only (standalone SPA mode).
|
|
2
|
+
Token variables come from @meddleware/design-tokens, imported before this.
|
|
3
|
+
Component utility classes live in component-styles.css. */
|
|
3
4
|
|
|
4
|
-
/* Oxblood / Indigo (masonic) palette — vendored mirror of the shared design
|
|
5
|
-
system for visual parity with the dashboard. The repo is self-contained
|
|
6
|
-
(no cross-repo links), so the token VALUES are copied here.
|
|
7
|
-
TODO(unify): consume `@meddleware/design-tokens` once it is published.
|
|
8
|
-
Dark mode is driven by `data-theme` (see composables/useColorMode.ts, which
|
|
9
|
-
also enables the light/dark/system control); the `:not([data-theme])` media
|
|
10
|
-
block preserves OS-follow before that runs (anti-flash). */
|
|
11
|
-
:root {
|
|
12
|
-
--bg: #f7f4f1;
|
|
13
|
-
--surface: #ffffff;
|
|
14
|
-
--border: #ded6cf;
|
|
15
|
-
--text: #201b19;
|
|
16
|
-
--muted: #6e635c;
|
|
17
|
-
--accent: #5e1622;
|
|
18
|
-
--accent-contrast: #ffffff;
|
|
19
|
-
--danger: #b3261e;
|
|
20
|
-
--ok: #1c5e4a;
|
|
21
|
-
--radius: 10px;
|
|
22
|
-
--lift: #efeae6;
|
|
23
|
-
color-scheme: light;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
:root[data-theme="dark"] {
|
|
27
|
-
--bg: #120e10;
|
|
28
|
-
--surface: #1c1618;
|
|
29
|
-
--border: #372b2e;
|
|
30
|
-
--text: #f2eae6;
|
|
31
|
-
--muted: #b7a9a3;
|
|
32
|
-
--accent: #b0465a;
|
|
33
|
-
--accent-contrast: #ffffff;
|
|
34
|
-
--danger: #f08a7e;
|
|
35
|
-
--ok: #5bb392;
|
|
36
|
-
--lift: #241c1f;
|
|
37
|
-
color-scheme: dark;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/* Pre-JS OS-follow (only until useColorMode sets an explicit data-theme). */
|
|
41
|
-
@media (prefers-color-scheme: dark) {
|
|
42
|
-
:root:not([data-theme]) {
|
|
43
|
-
--bg: #120e10;
|
|
44
|
-
--surface: #1c1618;
|
|
45
|
-
--border: #372b2e;
|
|
46
|
-
--text: #f2eae6;
|
|
47
|
-
--muted: #b7a9a3;
|
|
48
|
-
--accent: #b0465a;
|
|
49
|
-
--accent-contrast: #ffffff;
|
|
50
|
-
--danger: #f08a7e;
|
|
51
|
-
--ok: #5bb392;
|
|
52
|
-
--lift: #241c1f;
|
|
53
|
-
color-scheme: dark;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
* {
|
|
58
|
-
box-sizing: border-box;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/* Top-right colour-mode control row (parity with the dashboard). */
|
|
62
5
|
.theme-bar {
|
|
63
6
|
display: flex;
|
|
64
7
|
justify-content: flex-end;
|
|
@@ -101,284 +44,9 @@ p.lede {
|
|
|
101
44
|
color: var(--muted);
|
|
102
45
|
font-size: 1.05rem;
|
|
103
46
|
}
|
|
104
|
-
a {
|
|
105
|
-
color: var(--accent);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.card {
|
|
109
|
-
background: var(--surface);
|
|
110
|
-
border: 1px solid var(--border);
|
|
111
|
-
border-radius: var(--radius);
|
|
112
|
-
padding: 1.25rem;
|
|
113
|
-
margin: 1.5rem 0 1rem;
|
|
114
|
-
border-top: 2px solid var(--border);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
label {
|
|
118
|
-
display: block;
|
|
119
|
-
font-weight: 600;
|
|
120
|
-
margin: 0.9rem 0 0.3rem;
|
|
121
|
-
}
|
|
122
|
-
.hint {
|
|
123
|
-
color: var(--muted);
|
|
124
|
-
font-weight: 400;
|
|
125
|
-
font-size: 0.9rem;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
input,
|
|
129
|
-
select,
|
|
130
|
-
textarea {
|
|
131
|
-
width: 100%;
|
|
132
|
-
padding: 0.6rem 0.7rem;
|
|
133
|
-
font: inherit;
|
|
134
|
-
color: var(--text);
|
|
135
|
-
background: var(--bg);
|
|
136
|
-
border: 1px solid var(--border);
|
|
137
|
-
border-radius: 8px;
|
|
138
|
-
}
|
|
139
|
-
textarea {
|
|
140
|
-
min-height: 4rem;
|
|
141
|
-
resize: vertical;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
input[aria-invalid='true'],
|
|
145
|
-
select[aria-invalid='true'],
|
|
146
|
-
textarea[aria-invalid='true'] {
|
|
147
|
-
border-color: var(--danger);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.field-error {
|
|
151
|
-
color: var(--danger);
|
|
152
|
-
font-size: 0.88rem;
|
|
153
|
-
margin-top: 0.3rem;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
:focus-visible {
|
|
157
|
-
outline: 3px solid var(--accent);
|
|
158
|
-
outline-offset: 2px;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
button {
|
|
162
|
-
font: inherit;
|
|
163
|
-
font-weight: 600;
|
|
164
|
-
padding: 0.6rem 1rem;
|
|
165
|
-
border-radius: 8px;
|
|
166
|
-
border: 1px solid var(--border);
|
|
167
|
-
background: var(--surface);
|
|
168
|
-
color: var(--text);
|
|
169
|
-
cursor: pointer;
|
|
170
|
-
}
|
|
171
|
-
button.primary {
|
|
172
|
-
background: var(--accent);
|
|
173
|
-
color: var(--accent-contrast);
|
|
174
|
-
border-color: transparent;
|
|
175
|
-
}
|
|
176
|
-
button:disabled {
|
|
177
|
-
opacity: 0.55;
|
|
178
|
-
cursor: not-allowed;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.row {
|
|
182
|
-
display: flex;
|
|
183
|
-
gap: 0.75rem;
|
|
184
|
-
flex-wrap: wrap;
|
|
185
|
-
align-items: center;
|
|
186
|
-
}
|
|
187
|
-
.grid2 {
|
|
188
|
-
display: grid;
|
|
189
|
-
grid-template-columns: 1fr;
|
|
190
|
-
gap: 0 1rem;
|
|
191
|
-
}
|
|
192
|
-
@media (min-width: 520px) {
|
|
193
|
-
.grid2 {
|
|
194
|
-
grid-template-columns: 1fr 1fr;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
fieldset {
|
|
199
|
-
background: var(--lift);
|
|
200
|
-
border: 1px solid var(--border);
|
|
201
|
-
border-radius: 8px;
|
|
202
|
-
padding: 0.5rem 1rem 1rem;
|
|
203
|
-
margin: 1rem 0;
|
|
204
|
-
}
|
|
205
|
-
legend {
|
|
206
|
-
font-weight: 700;
|
|
207
|
-
padding: 0 0.4rem;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
details {
|
|
211
|
-
margin: 1rem 0;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
details > summary {
|
|
215
|
-
color: var(--accent);
|
|
216
|
-
font-weight: 700;
|
|
217
|
-
margin: 0.5rem 0;
|
|
218
|
-
padding: 0.75rem;
|
|
219
|
-
background: var(--lift);
|
|
220
|
-
border-radius: 8px;
|
|
221
|
-
border: 1px solid transparent;
|
|
222
|
-
transition: border-color 0.2s ease;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
details > summary:hover {
|
|
226
|
-
border-color: var(--border);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
details[open] > summary {
|
|
230
|
-
border-color: var(--border);
|
|
231
|
-
margin-bottom: 1rem;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
details > fieldset {
|
|
235
|
-
margin-top: 0;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
.permanent {
|
|
239
|
-
color: var(--danger);
|
|
240
|
-
font-weight: 600;
|
|
241
|
-
}
|
|
242
|
-
.mono {
|
|
243
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
244
|
-
word-break: break-all;
|
|
245
|
-
}
|
|
246
|
-
.badge {
|
|
247
|
-
display: inline-block;
|
|
248
|
-
font-size: 0.8rem;
|
|
249
|
-
padding: 0.15rem 0.5rem;
|
|
250
|
-
border-radius: 999px;
|
|
251
|
-
background: var(--border);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
.visually-hidden {
|
|
255
|
-
position: absolute;
|
|
256
|
-
width: 1px;
|
|
257
|
-
height: 1px;
|
|
258
|
-
padding: 0;
|
|
259
|
-
margin: -1px;
|
|
260
|
-
overflow: hidden;
|
|
261
|
-
clip: rect(0 0 0 0);
|
|
262
|
-
white-space: nowrap;
|
|
263
|
-
border: 0;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/* Keyboard skip link: hidden until focused, then visible at the top-left. */
|
|
267
|
-
.skip-link {
|
|
268
|
-
position: absolute;
|
|
269
|
-
left: 0.5rem;
|
|
270
|
-
top: -3rem;
|
|
271
|
-
z-index: 100;
|
|
272
|
-
background: var(--accent);
|
|
273
|
-
color: #fff;
|
|
274
|
-
padding: 0.5rem 0.9rem;
|
|
275
|
-
border-radius: 6px;
|
|
276
|
-
text-decoration: none;
|
|
277
|
-
transition: top 0.15s ease;
|
|
278
|
-
}
|
|
279
|
-
.skip-link:focus {
|
|
280
|
-
top: 0.5rem;
|
|
281
|
-
}
|
|
282
|
-
@media (prefers-reduced-motion: reduce) {
|
|
283
|
-
.skip-link {
|
|
284
|
-
transition: none;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
.steps {
|
|
289
|
-
list-style: none;
|
|
290
|
-
padding: 0;
|
|
291
|
-
}
|
|
292
|
-
.steps li {
|
|
293
|
-
padding: 0.35rem 0;
|
|
294
|
-
color: var(--muted);
|
|
295
|
-
}
|
|
296
|
-
.steps li[data-state='active'] {
|
|
297
|
-
color: var(--text);
|
|
298
|
-
font-weight: 600;
|
|
299
|
-
}
|
|
300
|
-
.steps li[data-state='done'] {
|
|
301
|
-
color: var(--ok);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
.spinner {
|
|
305
|
-
width: 1em;
|
|
306
|
-
height: 1em;
|
|
307
|
-
border: 2px solid currentColor;
|
|
308
|
-
border-right-color: transparent;
|
|
309
|
-
border-radius: 50%;
|
|
310
|
-
display: inline-block;
|
|
311
|
-
animation: spin 0.7s linear infinite;
|
|
312
|
-
vertical-align: -0.15em;
|
|
313
|
-
}
|
|
314
|
-
@keyframes spin {
|
|
315
|
-
to {
|
|
316
|
-
transform: rotate(360deg);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
@media (prefers-reduced-motion: reduce) {
|
|
320
|
-
.spinner {
|
|
321
|
-
animation: none;
|
|
322
|
-
}
|
|
323
|
-
* {
|
|
324
|
-
scroll-behavior: auto;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
47
|
|
|
328
48
|
footer {
|
|
329
49
|
border-top: 1px solid var(--border);
|
|
330
50
|
padding-top: 1.5rem;
|
|
331
51
|
margin-top: 2rem;
|
|
332
52
|
}
|
|
333
|
-
|
|
334
|
-
.notice {
|
|
335
|
-
border-left: 4px solid var(--accent);
|
|
336
|
-
padding: 0.5rem 0.75rem;
|
|
337
|
-
background: var(--surface);
|
|
338
|
-
border-radius: 6px;
|
|
339
|
-
}
|
|
340
|
-
.notice.error {
|
|
341
|
-
border-color: var(--danger);
|
|
342
|
-
}
|
|
343
|
-
.notice.ok {
|
|
344
|
-
border-color: var(--ok);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
dialog {
|
|
348
|
-
background: var(--surface);
|
|
349
|
-
border: 1px solid var(--border);
|
|
350
|
-
border-radius: var(--radius);
|
|
351
|
-
padding: 1.5rem;
|
|
352
|
-
width: min(420px, 90vw);
|
|
353
|
-
max-height: 90vh;
|
|
354
|
-
overflow-y: auto;
|
|
355
|
-
color: var(--text);
|
|
356
|
-
}
|
|
357
|
-
dialog::backdrop {
|
|
358
|
-
background: rgba(0, 0, 0, 0.45);
|
|
359
|
-
-webkit-backdrop-filter: blur(2px);
|
|
360
|
-
backdrop-filter: blur(2px);
|
|
361
|
-
}
|
|
362
|
-
.dialog-header {
|
|
363
|
-
display: flex;
|
|
364
|
-
justify-content: space-between;
|
|
365
|
-
align-items: center;
|
|
366
|
-
margin-bottom: 1.25rem;
|
|
367
|
-
}
|
|
368
|
-
.dialog-header h2 {
|
|
369
|
-
margin: 0;
|
|
370
|
-
font-size: 1.1rem;
|
|
371
|
-
}
|
|
372
|
-
.wallet-list {
|
|
373
|
-
display: flex;
|
|
374
|
-
flex-direction: column;
|
|
375
|
-
gap: 0.5rem;
|
|
376
|
-
margin-top: 1rem;
|
|
377
|
-
}
|
|
378
|
-
.wallet-list button {
|
|
379
|
-
display: flex;
|
|
380
|
-
align-items: center;
|
|
381
|
-
gap: 0.6rem;
|
|
382
|
-
width: 100%;
|
|
383
|
-
justify-content: flex-start;
|
|
384
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
-
|
|
4
|
-
const props = defineProps<{ type?: 'info' | 'error' | 'ok' }>()
|
|
5
|
-
|
|
6
|
-
const role = computed(() => {
|
|
7
|
-
if (props.type === 'error') return 'alert'
|
|
8
|
-
if (props.type === 'ok') return 'status'
|
|
9
|
-
return undefined
|
|
10
|
-
})
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
<template>
|
|
14
|
-
<p :class="['notice', type && type !== 'info' ? type : '']" :role="role"><slot /></p>
|
|
15
|
-
</template>
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { ColorMode } from '../composables/useColorMode'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Presentational light/dark/system control. Owns no state — bind with `v-model`
|
|
6
|
-
* to external state (`useColorMode`). Vendored mirror of `@meddleware/ui`'s
|
|
7
|
-
* `ColorModeControl` for parity with the dashboard.
|
|
8
|
-
* TODO(unify): consume `@meddleware/ui` once published.
|
|
9
|
-
*/
|
|
10
|
-
withDefaults(
|
|
11
|
-
defineProps<{
|
|
12
|
-
modelValue: ColorMode
|
|
13
|
-
modes?: ColorMode[]
|
|
14
|
-
}>(),
|
|
15
|
-
{ modes: () => ['light', 'dark', 'system'] },
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
defineEmits<{ 'update:modelValue': [ColorMode] }>()
|
|
19
|
-
|
|
20
|
-
const LABEL: Record<ColorMode, string> = { light: 'Light', dark: 'Dark', system: 'System' }
|
|
21
|
-
const GLYPH: Record<ColorMode, string> = { light: '☀', dark: '☾', system: '◐' }
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<template>
|
|
25
|
-
<div class="mode-control" role="group" aria-label="Colour mode">
|
|
26
|
-
<button
|
|
27
|
-
v-for="m in modes"
|
|
28
|
-
:key="m"
|
|
29
|
-
type="button"
|
|
30
|
-
class="mode-control__btn"
|
|
31
|
-
:class="{ 'is-active': modelValue === m }"
|
|
32
|
-
:aria-pressed="modelValue === m"
|
|
33
|
-
:title="LABEL[m]"
|
|
34
|
-
@click="$emit('update:modelValue', m)"
|
|
35
|
-
>
|
|
36
|
-
<span aria-hidden="true">{{ GLYPH[m] }}</span>
|
|
37
|
-
<span class="mode-control__label">{{ LABEL[m] }}</span>
|
|
38
|
-
</button>
|
|
39
|
-
</div>
|
|
40
|
-
</template>
|
|
41
|
-
|
|
42
|
-
<style scoped>
|
|
43
|
-
.mode-control {
|
|
44
|
-
display: inline-flex;
|
|
45
|
-
gap: 2px;
|
|
46
|
-
padding: 2px;
|
|
47
|
-
border: 1px solid var(--border);
|
|
48
|
-
border-radius: var(--radius);
|
|
49
|
-
background: var(--surface);
|
|
50
|
-
}
|
|
51
|
-
.mode-control__btn {
|
|
52
|
-
display: inline-flex;
|
|
53
|
-
align-items: center;
|
|
54
|
-
gap: 0.35rem;
|
|
55
|
-
padding: 0.25rem 0.5rem;
|
|
56
|
-
border: 0;
|
|
57
|
-
border-radius: calc(var(--radius) - 3px);
|
|
58
|
-
background: transparent;
|
|
59
|
-
color: var(--text);
|
|
60
|
-
font: inherit;
|
|
61
|
-
font-size: 0.8rem;
|
|
62
|
-
cursor: pointer;
|
|
63
|
-
opacity: 0.7;
|
|
64
|
-
}
|
|
65
|
-
.mode-control__btn:hover {
|
|
66
|
-
opacity: 1;
|
|
67
|
-
}
|
|
68
|
-
.mode-control__btn.is-active {
|
|
69
|
-
opacity: 1;
|
|
70
|
-
background: var(--lift);
|
|
71
|
-
}
|
|
72
|
-
.mode-control__btn:focus-visible {
|
|
73
|
-
outline: 2px solid var(--accent);
|
|
74
|
-
outline-offset: 1px;
|
|
75
|
-
}
|
|
76
|
-
@media (max-width: 640px) {
|
|
77
|
-
.mode-control__label {
|
|
78
|
-
display: none;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
</style>
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
defineProps<{ fieldId: string }>()
|
|
3
|
-
|
|
4
|
-
function onKeyDown(e: KeyboardEvent) {
|
|
5
|
-
if (e.key === 'Escape') (e.currentTarget as HTMLElement).blur()
|
|
6
|
-
}
|
|
7
|
-
</script>
|
|
8
|
-
|
|
9
|
-
<template>
|
|
10
|
-
<span class="field-hint">
|
|
11
|
-
<button
|
|
12
|
-
type="button"
|
|
13
|
-
class="field-hint-btn"
|
|
14
|
-
:aria-describedby="`${fieldId}-hint`"
|
|
15
|
-
aria-label="Help"
|
|
16
|
-
@keydown="onKeyDown"
|
|
17
|
-
>?</button>
|
|
18
|
-
<span :id="`${fieldId}-hint`" role="tooltip" class="field-hint-tip">
|
|
19
|
-
<slot />
|
|
20
|
-
</span>
|
|
21
|
-
</span>
|
|
22
|
-
</template>
|
|
23
|
-
|
|
24
|
-
<style scoped>
|
|
25
|
-
.field-hint {
|
|
26
|
-
position: relative;
|
|
27
|
-
display: inline-block;
|
|
28
|
-
margin-left: 0.35rem;
|
|
29
|
-
vertical-align: middle;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.field-hint-btn {
|
|
33
|
-
display: inline-flex;
|
|
34
|
-
align-items: center;
|
|
35
|
-
justify-content: center;
|
|
36
|
-
width: 1.1em;
|
|
37
|
-
height: 1.1em;
|
|
38
|
-
padding: 0;
|
|
39
|
-
font-size: 0.75rem;
|
|
40
|
-
font-weight: 700;
|
|
41
|
-
line-height: 1;
|
|
42
|
-
border-radius: 50%;
|
|
43
|
-
border: 1px solid var(--muted);
|
|
44
|
-
color: var(--muted);
|
|
45
|
-
background: transparent;
|
|
46
|
-
cursor: help;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.field-hint-tip {
|
|
50
|
-
position: absolute;
|
|
51
|
-
top: calc(100% + 6px);
|
|
52
|
-
left: 0;
|
|
53
|
-
z-index: 10;
|
|
54
|
-
min-width: 200px;
|
|
55
|
-
max-width: min(300px, 90vw);
|
|
56
|
-
padding: 0.6rem 0.75rem;
|
|
57
|
-
background: var(--surface);
|
|
58
|
-
border: 1px solid var(--border);
|
|
59
|
-
border-radius: 8px;
|
|
60
|
-
font-size: 0.85rem;
|
|
61
|
-
font-weight: 400;
|
|
62
|
-
line-height: 1.5;
|
|
63
|
-
color: var(--text);
|
|
64
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
65
|
-
visibility: hidden;
|
|
66
|
-
opacity: 0;
|
|
67
|
-
transition:
|
|
68
|
-
opacity 0.15s ease,
|
|
69
|
-
visibility 0.15s ease;
|
|
70
|
-
pointer-events: none;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.field-hint:hover .field-hint-tip,
|
|
74
|
-
.field-hint:focus-within .field-hint-tip {
|
|
75
|
-
visibility: visible;
|
|
76
|
-
opacity: 1;
|
|
77
|
-
pointer-events: auto;
|
|
78
|
-
}
|
|
79
|
-
</style>
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
-
|
|
4
|
-
const props = defineProps<{
|
|
5
|
-
id: string
|
|
6
|
-
label: string
|
|
7
|
-
error?: string | null
|
|
8
|
-
hint?: string
|
|
9
|
-
}>()
|
|
10
|
-
|
|
11
|
-
const errorId = computed(() => `${props.id}-err`)
|
|
12
|
-
const slotAttrs = computed(() => ({
|
|
13
|
-
id: props.id,
|
|
14
|
-
'aria-invalid': props.error ? (true as const) : undefined,
|
|
15
|
-
'aria-describedby': props.error ? errorId.value : undefined,
|
|
16
|
-
}))
|
|
17
|
-
</script>
|
|
18
|
-
|
|
19
|
-
<template>
|
|
20
|
-
<div class="form-field">
|
|
21
|
-
<label :for="id">
|
|
22
|
-
{{ label }}<slot name="label-suffix" />
|
|
23
|
-
</label>
|
|
24
|
-
<slot :attrs="slotAttrs" />
|
|
25
|
-
<p v-if="hint && !error" class="hint">{{ hint }}</p>
|
|
26
|
-
<p v-if="error" :id="errorId" class="field-error">{{ error }}</p>
|
|
27
|
-
</div>
|
|
28
|
-
</template>
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
defineOptions({ inheritAttrs: false })
|
|
3
|
-
const model = defineModel<string>()
|
|
4
|
-
</script>
|
|
5
|
-
|
|
6
|
-
<template>
|
|
7
|
-
<div class="select-wrapper">
|
|
8
|
-
<select v-bind="$attrs" v-model="model"><slot /></select>
|
|
9
|
-
<svg
|
|
10
|
-
class="select-chevron"
|
|
11
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
-
width="12"
|
|
13
|
-
height="8"
|
|
14
|
-
viewBox="0 0 12 8"
|
|
15
|
-
aria-hidden="true"
|
|
16
|
-
>
|
|
17
|
-
<path d="M1 1l5 5 5-5" />
|
|
18
|
-
</svg>
|
|
19
|
-
</div>
|
|
20
|
-
</template>
|
|
21
|
-
|
|
22
|
-
<style scoped>
|
|
23
|
-
.select-wrapper {
|
|
24
|
-
position: relative;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.select-wrapper select {
|
|
28
|
-
appearance: none;
|
|
29
|
-
padding-right: 2.5rem;
|
|
30
|
-
width: 100%;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.select-chevron {
|
|
34
|
-
position: absolute;
|
|
35
|
-
right: 0.75rem;
|
|
36
|
-
top: 50%;
|
|
37
|
-
transform: translateY(-50%);
|
|
38
|
-
pointer-events: none;
|
|
39
|
-
stroke: var(--muted);
|
|
40
|
-
stroke-width: 1.5;
|
|
41
|
-
fill: none;
|
|
42
|
-
}
|
|
43
|
-
</style>
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { ref, watch, type Ref } from 'vue'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Colour-mode state manager (light | dark | system). Sets `data-theme` on
|
|
5
|
-
* <html>, persists the choice, and follows the OS in `system` mode.
|
|
6
|
-
*
|
|
7
|
-
* Vendored mirror of `@meddleware/ui`'s `useColorMode` for visual/UX parity with
|
|
8
|
-
* the dashboard — this repo is self-contained (no cross-repo links).
|
|
9
|
-
* TODO(unify): consume `@meddleware/ui` once it is published.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export type ColorMode = 'light' | 'dark' | 'system'
|
|
13
|
-
|
|
14
|
-
const STORAGE_KEY = 'sui-deployer:color-mode'
|
|
15
|
-
|
|
16
|
-
const mode: Ref<ColorMode> = ref<ColorMode>('system')
|
|
17
|
-
let initialized = false
|
|
18
|
-
|
|
19
|
-
function systemPrefersDark(): boolean {
|
|
20
|
-
return (
|
|
21
|
-
typeof window !== 'undefined' &&
|
|
22
|
-
typeof window.matchMedia === 'function' &&
|
|
23
|
-
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function resolveDark(m: ColorMode): boolean {
|
|
28
|
-
return m === 'dark' || (m === 'system' && systemPrefersDark())
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function apply(m: ColorMode): void {
|
|
32
|
-
if (typeof document === 'undefined') return
|
|
33
|
-
document.documentElement.setAttribute('data-theme', resolveDark(m) ? 'dark' : 'light')
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function useColorMode(defaultMode: ColorMode = 'system') {
|
|
37
|
-
if (!initialized) {
|
|
38
|
-
initialized = true
|
|
39
|
-
let stored: ColorMode | null = null
|
|
40
|
-
try {
|
|
41
|
-
stored =
|
|
42
|
-
typeof localStorage !== 'undefined'
|
|
43
|
-
? (localStorage.getItem(STORAGE_KEY) as ColorMode | null)
|
|
44
|
-
: null
|
|
45
|
-
} catch {
|
|
46
|
-
/* no storage — ignore */
|
|
47
|
-
}
|
|
48
|
-
mode.value = stored ?? defaultMode
|
|
49
|
-
apply(mode.value)
|
|
50
|
-
|
|
51
|
-
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
|
52
|
-
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
|
53
|
-
if (mode.value === 'system') apply('system')
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
watch(mode, (m) => {
|
|
58
|
-
apply(m)
|
|
59
|
-
try {
|
|
60
|
-
if (typeof localStorage !== 'undefined') localStorage.setItem(STORAGE_KEY, m)
|
|
61
|
-
} catch {
|
|
62
|
-
/* ignore */
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function set(m: ColorMode): void {
|
|
68
|
-
mode.value = m
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function cycle(): void {
|
|
72
|
-
set(mode.value === 'light' ? 'dark' : mode.value === 'dark' ? 'system' : 'light')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return { mode, set, cycle, isDark: () => resolveDark(mode.value) }
|
|
76
|
-
}
|