@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/src/styles.css CHANGED
@@ -1,64 +1,7 @@
1
- /* Accessible, lightweight styling. System fonts, dark/light aware, high-contrast
2
- focus rings, and reduced-motion respect. No heavy CSS framework. */
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
- }