@vmz/ui 0.3.7 → 0.3.9

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.
Files changed (68) hide show
  1. package/README.md +2 -0
  2. package/contracts/token-requirements.v0.json +58 -1
  3. package/package.json +5 -2
  4. package/presets/web-surface.v0.json +14 -12
  5. package/src/components/Accordion.vmz +49 -16
  6. package/src/components/Alert.vmz +125 -28
  7. package/src/components/AppShell.vmz +121 -56
  8. package/src/components/Autocomplete.vmz +136 -29
  9. package/src/components/Avatar.vmz +20 -12
  10. package/src/components/Badge.vmz +18 -26
  11. package/src/components/Breadcrumb.vmz +28 -14
  12. package/src/components/BulkActions.vmz +13 -8
  13. package/src/components/Button.vmz +68 -44
  14. package/src/components/Callout.vmz +128 -27
  15. package/src/components/Card.vmz +67 -46
  16. package/src/components/Checkbox.vmz +81 -5
  17. package/src/components/CodeBlock.vmz +59 -27
  18. package/src/components/Collapse.vmz +64 -11
  19. package/src/components/ConsoleShell.vmz +249 -69
  20. package/src/components/Content.vmz +15 -1
  21. package/src/components/DataTable.vmz +123 -23
  22. package/src/components/DatePicker.vmz +171 -79
  23. package/src/components/Descriptions.vmz +90 -0
  24. package/src/components/Dialog.vmz +133 -50
  25. package/src/components/Divider.vmz +8 -1
  26. package/src/components/Drawer.vmz +131 -40
  27. package/src/components/Dropdown.vmz +147 -49
  28. package/src/components/Empty.vmz +56 -14
  29. package/src/components/Field.vmz +78 -23
  30. package/src/components/FilterBar.vmz +34 -4
  31. package/src/components/Flex.vmz +3 -1
  32. package/src/components/Footer.vmz +6 -3
  33. package/src/components/Form.vmz +41 -0
  34. package/src/components/FormItem.vmz +2 -3
  35. package/src/components/Grid.vmz +6 -3
  36. package/src/components/Header.vmz +193 -6
  37. package/src/components/Layout.vmz +3 -1
  38. package/src/components/Link.vmz +20 -9
  39. package/src/components/List.vmz +35 -13
  40. package/src/components/Menu.vmz +198 -25
  41. package/src/components/Message.vmz +167 -0
  42. package/src/components/Notification.vmz +132 -37
  43. package/src/components/Pagination.vmz +186 -20
  44. package/src/components/Popconfirm.vmz +287 -0
  45. package/src/components/Popover.vmz +152 -34
  46. package/src/components/Progress.vmz +16 -5
  47. package/src/components/Prose.vmz +131 -32
  48. package/src/components/QueryForm.vmz +31 -9
  49. package/src/components/RadioGroup.vmz +91 -11
  50. package/src/components/Result.vmz +60 -27
  51. package/src/components/Segmented.vmz +46 -11
  52. package/src/components/Select.vmz +174 -87
  53. package/src/components/Sider.vmz +120 -7
  54. package/src/components/Skeleton.vmz +9 -7
  55. package/src/components/Space.vmz +9 -3
  56. package/src/components/Spinner.vmz +5 -4
  57. package/src/components/Steps.vmz +40 -25
  58. package/src/components/Switch.vmz +48 -15
  59. package/src/components/Table.vmz +130 -25
  60. package/src/components/Tabs.vmz +28 -13
  61. package/src/components/Tag.vmz +29 -12
  62. package/src/components/TextArea.vmz +32 -25
  63. package/src/components/Timeline.vmz +65 -30
  64. package/src/components/Toc.vmz +45 -29
  65. package/src/components/Tooltip.vmz +70 -21
  66. package/src/components/Tree.vmz +48 -25
  67. package/src/components/Typography.vmz +23 -8
  68. package/src/components/Upload.vmz +191 -96
@@ -3,30 +3,35 @@
3
3
  <label v-if="label" :for="controlId" class="vmz-ui-select__label">
4
4
  {{ label }}
5
5
  </label>
6
- <button type="button" :id="controlId" role="combobox" :disabled="disabled" aria-haspopup="listbox" :aria-expanded="open ? 'true' : 'false'" :aria-controls="listId" :aria-invalid="error ? 'true' : 'false'" :aria-describedby="describedBy" :aria-multiselectable="isMultiple ? 'true' : 'false'" @click="toggle" :onKeyDown="onTriggerKeyDown" class="vmz-ui-select__control">
7
- <span class="vmz-ui-select__value">
8
- {{ displayLabel }}
9
- </span>
10
- <span aria-hidden="true" class="vmz-ui-select__chev" />
11
- </button>
12
- <div v-if="open" :id="listId" data-vmz-overlay="select" data-vmz-overlay-owner="select" data-vmz-focus="enter" class="vmz-ui-select__panel">
13
- <div v-if="searchable" class="vmz-ui-select__search">
14
- <input type="search" data-vmz-select="search" :value="searchQuery" :placeholder="searchPlaceholder" :aria-label="searchPlaceholder" @input="onSearchInput" :onKeyDown="onSearchKeyDown" class="vmz-ui-select__search-input" />
15
- </div>
16
- <ul role="listbox" :aria-multiselectable="isMultiple ? 'true' : 'false'" tabindex="-1" :onKeyDown="onListKeyDown" class="vmz-ui-select__list">
17
- <li v-if="showEmpty" role="presentation" class="vmz-ui-select__empty">
18
- {{ emptyText }}
19
- </li>
20
- <li v-for="opt in visibleOptions" :key="opt.value" role="presentation">
21
- <button type="button" role="option" :data-vmz-option="opt.value" :aria-selected="opt.selected ? 'true' : 'false'" @click="onOptionClick" :class='"vmz-ui-select__option" + (opt.selected ? " is-on" : "")'>
22
- {{ opt.label }}
6
+ <div class="vmz-ui-select__field">
7
+ <button type="button" :id="controlId" role="combobox" :disabled="disabled" aria-haspopup="listbox" :aria-expanded="open ? 'true' : 'false'" :aria-controls="listId" :aria-invalid="error ? 'true' : 'false'" :aria-describedby="describedBy" :aria-multiselectable="isMultiple ? 'true' : 'false'" @click="toggle" :onKeyDown="onTriggerKeyDown" :class='"vmz-ui-select__control" + (displayLabel ? "" : " is-placeholder")'>
8
+ <span class="vmz-ui-select__value">
9
+ {{ displayLabel }}
10
+ </span>
11
+ <Icon name="action.chevron-down" size="md" decorative class="vmz-ui-select__chev" />
12
+ </button>
13
+ <div v-if="open" :id="listId" data-vmz-overlay="select" data-vmz-overlay-owner="select" data-vmz-focus="enter" class="vmz-ui-select__panel">
14
+ <div v-if="searchable" class="vmz-ui-select__search">
15
+ <input type="search" data-vmz-select="search" :value="searchQuery" :placeholder="searchPlaceholder" :aria-label="searchLabel()" @input="onSearchInput" :onKeyDown="onSearchKeyDown" class="vmz-ui-select__search-input" />
16
+ </div>
17
+ <ul role="listbox" :aria-multiselectable="isMultiple ? 'true' : 'false'" tabindex="-1" :onKeyDown="onListKeyDown" class="vmz-ui-select__list">
18
+ <li v-if="showEmpty && emptyText" role="presentation" class="vmz-ui-select__empty">
19
+ {{ emptyText }}
20
+ </li>
21
+ <li v-for="opt in visibleOptions" :key="opt.value" role="presentation">
22
+ <button type="button" role="option" :data-vmz-option="opt.value" :aria-selected="opt.selected ? 'true' : 'false'" @click="onOptionClick" :class='"vmz-ui-select__option" + (opt.selected ? " is-on" : "")'>
23
+ <span class="vmz-ui-select__option-label">
24
+ {{ opt.label }}
25
+ </span>
26
+ <Icon v-if="opt.selected" name="action.check" size="md" decorative class="vmz-ui-select__option-check" />
27
+ </button>
28
+ </li>
29
+ </ul>
30
+ <div v-if="isMultiple && doneLabel" class="vmz-ui-select__footer">
31
+ <button type="button" data-vmz-select="done" @click="close" class="vmz-ui-select__done">
32
+ {{ doneLabel }}
23
33
  </button>
24
- </li>
25
- </ul>
26
- <div v-if="isMultiple" class="vmz-ui-select__footer">
27
- <button type="button" data-vmz-select="done" @click="close" class="vmz-ui-select__done">
28
- Done
29
- </button>
34
+ </div>
30
35
  </div>
31
36
  </div>
32
37
  <p v-if="description" :id="descriptionId" class="vmz-ui-select__description">
@@ -42,47 +47,67 @@
42
47
  position: relative;
43
48
  display: flex;
44
49
  flex-direction: column;
45
- gap: 0.375rem;
50
+ gap: var(--vmz-space-2);
51
+ width: 100%;
46
52
  max-width: 28rem;
47
53
  }
48
54
 
55
+ .vmz-ui-select__field {
56
+ position: relative;
57
+ display: flex;
58
+ flex-direction: column;
59
+ width: 100%;
60
+ }
61
+
49
62
  .vmz-ui-select__label {
50
63
  font: inherit;
51
64
  font-weight: 500;
52
65
  font-size: 0.875rem;
66
+ line-height: 1.5715;
67
+ color: var(--vmz-text-ink);
53
68
  }
54
69
 
55
70
  .vmz-ui-select__control {
56
71
  display: inline-flex;
57
72
  align-items: center;
58
73
  justify-content: space-between;
59
- gap: 0.5rem;
74
+ gap: var(--vmz-space-2);
60
75
  width: 100%;
61
- min-height: var(--vmz-control-height, 2rem);
62
76
  box-sizing: border-box;
77
+ min-height: var(--vmz-control-height);
63
78
  font: inherit;
64
79
  font-size: 0.875rem;
80
+ line-height: 1.5715;
65
81
  text-align: left;
66
82
  padding: var(--vmz-density-control-padding-y) var(--vmz-density-control-padding-x);
67
- border: 1px solid var(--vmz-line-default, #d9d9d9);
68
- border-radius: var(--vmz-radius-md, 6px);
69
- background: var(--vmz-surface-paper, #fff);
70
- color: inherit;
83
+ border: 1px solid var(--vmz-line-default);
84
+ border-radius: var(--vmz-radius-lg);
85
+ background: var(--vmz-surface-paper);
86
+ color: var(--vmz-text-ink);
71
87
  outline: none;
72
88
  cursor: pointer;
73
89
  transition:
74
- border-color var(--vmz-motion-control-duration, 120ms)
75
- var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1)),
76
- box-shadow var(--vmz-motion-control-duration, 120ms)
77
- var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1));
90
+ border-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
91
+ box-shadow var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
92
+ background-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing);
93
+ }
94
+
95
+ .vmz-ui-select__control.is-placeholder .vmz-ui-select__value {
96
+ color: var(--vmz-text-tertiary);
78
97
  }
79
98
 
80
99
  .vmz-ui-select[data-invalid="true"] .vmz-ui-select__control {
81
100
  border-color: var(--vmz-status-danger-accent);
82
101
  }
83
102
 
84
- .vmz-ui-select__control:hover:not(:disabled) {
85
- border-color: var(--vmz-action-primary-background);
103
+ .vmz-ui-select[data-invalid="true"] .vmz-ui-select__control:hover:not(:disabled),
104
+ .vmz-ui-select[data-invalid="true"] .vmz-ui-select__control:focus-visible,
105
+ .vmz-ui-select[data-invalid="true"].is-opened .vmz-ui-select__control {
106
+ border-color: var(--vmz-status-danger-foreground);
107
+ }
108
+
109
+ .vmz-ui-select__control:hover:not(:disabled):not(:focus-visible) {
110
+ border-color: var(--vmz-action-primary-hover);
86
111
  }
87
112
 
88
113
  .vmz-ui-select__control:focus-visible,
@@ -92,8 +117,10 @@
92
117
  }
93
118
 
94
119
  .vmz-ui-select__control:disabled {
95
- opacity: 0.55;
120
+ opacity: 0.6;
121
+ color: var(--vmz-text-muted);
96
122
  cursor: not-allowed;
123
+ background: var(--vmz-surface-mist);
97
124
  }
98
125
 
99
126
  .vmz-ui-select__value {
@@ -106,62 +133,67 @@
106
133
 
107
134
  .vmz-ui-select__chev {
108
135
  flex: 0 0 auto;
109
- width: 0.45rem;
110
- height: 0.45rem;
111
- border-right: 1.5px solid currentColor;
112
- border-bottom: 1.5px solid currentColor;
113
- transform: rotate(45deg) translate(-1px, -2px);
114
- opacity: 0.7;
136
+ color: var(--vmz-text-tertiary);
137
+ transition: transform var(--vmz-motion-control-duration) var(--vmz-motion-control-easing);
115
138
  }
116
139
 
117
140
  .vmz-ui-select.is-opened .vmz-ui-select__chev {
118
- transform: rotate(225deg) translate(-1px, -1px);
141
+ transform: rotate(180deg);
119
142
  }
120
143
 
121
144
  .vmz-ui-select__panel {
122
145
  position: absolute;
123
146
  z-index: 40;
124
- top: calc(100% + 0.25rem);
147
+ top: calc(100% + var(--vmz-space-1));
125
148
  left: 0;
126
149
  right: 0;
127
150
  display: flex;
128
151
  flex-direction: column;
129
- border: 1px solid var(--vmz-line-default, #d9d9d9);
130
- border-radius: var(--vmz-radius-md, 6px);
131
- background: var(--vmz-surface-paper, #fff);
132
- color: inherit;
133
- box-shadow: var(
134
- --vmz-shadow-popup,
135
- 0 6px 16px 0 rgba(0, 0, 0, 0.08),
136
- 0 3px 6px -4px rgba(0, 0, 0, 0.12)
137
- );
152
+ padding: var(--vmz-space-1);
153
+ border: 1px solid var(--vmz-line-default);
154
+ border-radius: var(--vmz-radius-lg);
155
+ background: var(--vmz-surface-paper);
156
+ color: var(--vmz-text-ink);
157
+ box-shadow: var(--vmz-shadow-popup);
138
158
  outline: none;
139
159
  }
140
160
 
141
161
  .vmz-ui-select__search {
142
- padding: 0.375rem 0.5rem 0.125rem;
162
+ padding: var(--vmz-space-1) var(--vmz-space-1) 0;
143
163
  }
144
164
 
145
165
  .vmz-ui-select__search-input {
146
166
  width: 100%;
147
167
  box-sizing: border-box;
168
+ min-height: var(--vmz-control-height);
148
169
  font: inherit;
149
170
  font-size: 0.875rem;
150
- padding: 0.25rem 0.5rem;
151
- border: 1px solid var(--vmz-line-default, #d9d9d9);
152
- border-radius: var(--vmz-radius-md, 6px);
153
- background: var(--vmz-surface-paper, #fff);
154
- color: inherit;
171
+ line-height: 1.5715;
172
+ appearance: none;
173
+ padding: var(--vmz-density-control-padding-y) var(--vmz-density-control-padding-x);
174
+ border: 1px solid var(--vmz-line-default);
175
+ border-radius: var(--vmz-radius-md);
176
+ background: var(--vmz-surface-paper);
177
+ color: var(--vmz-text-ink);
155
178
  outline: none;
179
+ transition:
180
+ border-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
181
+ box-shadow var(--vmz-motion-control-duration) var(--vmz-motion-control-easing);
182
+ }
183
+
184
+ .vmz-ui-select__search-input::placeholder {
185
+ color: var(--vmz-text-tertiary);
186
+ opacity: 1;
156
187
  }
157
188
 
158
189
  .vmz-ui-select__search-input:focus-visible {
190
+ border-color: var(--vmz-action-primary-background);
159
191
  box-shadow: 0 0 0 3px var(--vmz-focus-ring);
160
192
  }
161
193
 
162
194
  .vmz-ui-select__list {
163
195
  margin: 0;
164
- padding: 0.25rem;
196
+ padding: 0;
165
197
  list-style: none;
166
198
  max-height: 16rem;
167
199
  overflow: auto;
@@ -169,64 +201,112 @@
169
201
  }
170
202
 
171
203
  .vmz-ui-select__empty {
172
- padding: 0.5rem 0.625rem;
173
- opacity: 0.7;
174
- font-size: 0.92em;
204
+ padding: var(--vmz-space-2) var(--vmz-density-control-padding-x);
205
+ color: var(--vmz-text-tertiary);
206
+ font-size: 0.875rem;
207
+ line-height: 1.5715;
175
208
  }
176
209
 
177
210
  .vmz-ui-select__option {
178
- display: block;
211
+ display: flex;
212
+ align-items: center;
213
+ justify-content: space-between;
214
+ gap: var(--vmz-space-2);
179
215
  width: 100%;
180
216
  box-sizing: border-box;
217
+ min-height: var(--vmz-control-height);
181
218
  text-align: left;
182
219
  font: inherit;
183
- padding: 0.375rem 0.625rem;
220
+ font-size: 0.875rem;
221
+ line-height: 1.5715;
222
+ padding: 0 var(--vmz-density-control-padding-x);
184
223
  border: 0;
185
- border-radius: var(--vmz-radius-md, 6px);
224
+ border-radius: var(--vmz-radius-md);
186
225
  background: transparent;
187
- color: inherit;
226
+ color: var(--vmz-text-ink);
188
227
  cursor: pointer;
189
228
  outline: none;
229
+ transition: background-color var(--vmz-motion-control-duration)
230
+ var(--vmz-motion-control-easing);
231
+ }
232
+
233
+ .vmz-ui-select__option-label {
234
+ flex: 1 1 auto;
235
+ min-width: 0;
236
+ overflow: hidden;
237
+ text-overflow: ellipsis;
238
+ white-space: nowrap;
239
+ }
240
+
241
+ .vmz-ui-select__option-check {
242
+ flex: 0 0 auto;
243
+ color: var(--vmz-action-primary-background);
190
244
  }
191
245
 
192
246
  .vmz-ui-select__option:hover,
193
247
  .vmz-ui-select__option:focus-visible {
194
- background: color-mix(in srgb, var(--vmz-action-primary-background) 18%, transparent);
248
+ background: var(--vmz-surface-mist);
249
+ }
250
+
251
+ .vmz-ui-select__option:focus-visible {
252
+ box-shadow: 0 0 0 3px var(--vmz-focus-ring);
195
253
  }
196
254
 
197
255
  .vmz-ui-select__option.is-on {
198
- background: color-mix(in srgb, var(--vmz-action-primary-background) 28%, transparent);
199
- font-weight: 600;
256
+ background: var(--vmz-action-primary-soft);
257
+ color: var(--vmz-action-primary-active);
200
258
  }
201
259
 
202
260
  .vmz-ui-select__footer {
203
261
  display: flex;
204
262
  justify-content: flex-end;
205
- padding: 0.375rem 0.5rem 0.5rem;
206
- border-top: 1px solid var(--vmz-line-default, #d9dee3);
263
+ padding: var(--vmz-space-1) var(--vmz-space-1) 0;
264
+ border-top: 1px solid var(--vmz-line-subtle);
207
265
  }
208
266
 
209
267
  .vmz-ui-select__done {
210
268
  font: inherit;
211
- font-weight: 600;
212
- padding: 0.25rem 0.7rem;
213
- border: 1px solid var(--vmz-line-default, #d9dee3);
214
- border-radius: var(--vmz-radius-md, 6px);
215
- background: var(--vmz-surface-paper, #fff);
216
- color: inherit;
269
+ font-size: 0.875rem;
270
+ font-weight: 500;
271
+ line-height: 1.5715;
272
+ min-height: var(--vmz-control-height-sm);
273
+ padding: 0 var(--vmz-space-3);
274
+ border: 1px solid var(--vmz-action-secondary-border);
275
+ border-radius: var(--vmz-radius-md);
276
+ background: var(--vmz-action-secondary-background);
277
+ color: var(--vmz-action-secondary-foreground);
217
278
  cursor: pointer;
279
+ outline: none;
280
+ transition:
281
+ background-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
282
+ border-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing);
283
+ }
284
+
285
+ .vmz-ui-select__done:hover {
286
+ background: var(--vmz-action-secondary-hover);
287
+ border-color: var(--vmz-action-primary-hover);
288
+ }
289
+
290
+ .vmz-ui-select__done:active {
291
+ background: var(--vmz-action-secondary-active);
292
+ }
293
+
294
+ .vmz-ui-select__done:focus-visible {
295
+ box-shadow: 0 0 0 3px var(--vmz-focus-ring);
218
296
  }
219
297
 
220
298
  .vmz-ui-select__description {
221
299
  margin: 0;
222
- opacity: 0.75;
223
- font-size: 0.9em;
300
+ color: var(--vmz-text-steel);
301
+ font-size: 0.875rem;
302
+ line-height: 1.5715;
224
303
  }
225
304
 
226
305
  .vmz-ui-select__error {
227
306
  margin: 0;
228
307
  color: var(--vmz-status-danger-foreground);
229
- font-size: 0.9em;
308
+ font-size: 0.875rem;
309
+ line-height: 1.5715;
230
310
  }
231
311
  </style>
232
312
  <script client>
@@ -240,6 +320,7 @@ export default class Select {
240
320
  public label: string = "";
241
321
  public description: string = "";
242
322
  public error: string = "";
323
+ public placeholder: string = "";
243
324
  /** single mode */
244
325
  public value: string = "";
245
326
  /** multiple mode — parent owns array */
@@ -251,8 +332,9 @@ export default class Select {
251
332
  /** "single" | "multiple" */
252
333
  public mode: string = "single";
253
334
  public searchable: boolean = false;
254
- public searchPlaceholder: string = "Search";
255
- public emptyText: string = "No matches";
335
+ public searchPlaceholder: string = "";
336
+ public emptyText: string = "";
337
+ public doneLabel: string = "";
256
338
  public disabled: boolean = false;
257
339
  public invalidAttr: string = "false";
258
340
  public controlId: string = "vmz-select";
@@ -339,13 +421,16 @@ export default class Select {
339
421
  if (this.isMultiple) {
340
422
  const selected = this._selectedSet();
341
423
  const labels = opts.filter((o) => selected.has(String(o.value))).map((o) => o.label);
342
- if (!labels.length) this.displayLabel = "—";
424
+ if (!labels.length) this.displayLabel = this.placeholder || "";
343
425
  else if (labels.length <= 2) this.displayLabel = labels.join(", ");
344
426
  else this.displayLabel = `${labels[0]}, ${labels[1]} +${labels.length - 2}`;
345
427
  return;
346
428
  }
347
429
  const hit = opts.find((o) => String(o.value) === String(this.value));
348
- this.displayLabel = hit ? hit.label : this.value || "—";
430
+ this.displayLabel = hit ? hit.label : this.value || this.placeholder || "";
431
+ }
432
+ searchLabel() {
433
+ return this.searchPlaceholder || this.label || "";
349
434
  }
350
435
  toggle() {
351
436
  if (this.disabled) return;
@@ -376,7 +461,9 @@ export default class Select {
376
461
  }
377
462
  /** Parent-owned prop writes (bindComponentProp) — keep derived label/options in sync. */
378
463
  __vmzOnParentProp(name: string) {
379
- if (name === "value" || name === "values" || name === "options") this._rebuildVisible();
464
+ if (name === "value" || name === "values" || name === "options" || name === "placeholder") {
465
+ this._rebuildVisible();
466
+ }
380
467
  }
381
468
  choose(next: string) {
382
469
  if (!next && next !== "") return;
@@ -1,23 +1,136 @@
1
1
  <template>
2
- <aside data-vmz-ui="sider" class="vmz-ui-sider">
3
- <slot />
2
+ <aside data-vmz-ui="sider" :onKeyDown="onKeyDown" class="vmz-ui-sider">
3
+ <div class="vmz-ui-sider__head">
4
+ <button type="button" :aria-label="label" :aria-expanded="open ? 'true' : 'false'" :aria-controls="bodyId" @click="toggle" class="vmz-ui-sider__toggle">
5
+ <Icon :name="open ? 'action.close' : 'action.menu'" size="md" decorative />
6
+ </button>
7
+ </div>
8
+ <div :id="bodyId" :data-open="open ? 'true' : null" class="vmz-ui-sider__body">
9
+ <slot />
10
+ </div>
4
11
  </aside>
5
12
  </template>
6
13
  <style>
7
14
  .vmz-ui-sider {
8
- width: 16rem;
9
- padding: 1rem;
10
- border-right: 1px solid var(--vmz-line-default);
15
+ flex: none;
16
+ width: 13rem;
17
+ padding: var(--vmz-space-3, 0.75rem);
18
+ border-right: 1px solid var(--vmz-line-subtle);
11
19
  background: var(--vmz-surface-paper);
20
+ color: var(--vmz-text-ink);
21
+ overflow-y: auto;
22
+ }
23
+ .vmz-ui-sider__head {
24
+ display: none;
25
+ }
26
+ .vmz-ui-sider__body {
27
+ display: flex;
28
+ flex-direction: column;
29
+ gap: var(--vmz-space-1, 0.25rem);
30
+ min-width: 0;
31
+ }
32
+ .vmz-ui-sider__body :where(nav) {
33
+ display: flex;
34
+ flex-direction: column;
35
+ gap: var(--vmz-space-1, 0.25rem);
36
+ }
37
+ .vmz-ui-sider__body :where(nav a, .vmz-ui-sider__item) {
38
+ display: flex;
39
+ align-items: center;
40
+ min-height: 2.25rem;
41
+ padding: 0 var(--vmz-space-3, 0.75rem);
42
+ border-radius: var(--vmz-radius-md, 6px);
43
+ color: var(--vmz-text-steel);
44
+ font-size: 0.875rem;
45
+ font-weight: 500;
46
+ line-height: 1.25rem;
47
+ text-decoration: none;
48
+ outline: none;
49
+ transition:
50
+ color var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1)),
51
+ background-color var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1));
52
+ }
53
+ .vmz-ui-sider__body :where(nav a, .vmz-ui-sider__item):hover {
54
+ color: var(--vmz-text-ink);
55
+ background: var(--vmz-surface-mist);
56
+ }
57
+ .vmz-ui-sider__body :where(nav a, .vmz-ui-sider__item)[data-active="true"],
58
+ .vmz-ui-sider__body :where(nav a, .vmz-ui-sider__item)[aria-current="page"] {
59
+ color: var(--vmz-action-primary-background);
60
+ background: var(--vmz-action-primary-soft);
61
+ font-weight: 600;
62
+ box-shadow: inset 2px 0 0 var(--vmz-action-primary-background);
63
+ }
64
+ .vmz-ui-sider__body :where(nav a, .vmz-ui-sider__item):focus-visible {
65
+ box-shadow: 0 0 0 3px var(--vmz-focus-ring);
66
+ }
67
+ .vmz-ui-sider__group-title {
68
+ margin: var(--vmz-space-3, 0.75rem) 0 var(--vmz-space-1, 0.25rem);
69
+ padding: 0 var(--vmz-space-3, 0.75rem);
70
+ color: var(--vmz-text-tertiary);
71
+ font-size: 0.75rem;
72
+ font-weight: 600;
73
+ line-height: 1.25rem;
74
+ letter-spacing: 0.02em;
12
75
  }
13
76
  @media (max-width: 720px) {
14
77
  .vmz-ui-sider {
15
78
  width: 100%;
79
+ padding: var(--vmz-space-2, 0.5rem) var(--vmz-space-4, 1rem);
16
80
  border-right: 0;
17
- border-bottom: 1px solid var(--vmz-line-default);
81
+ border-bottom: 1px solid var(--vmz-line-subtle);
82
+ overflow-y: visible;
83
+ }
84
+ .vmz-ui-sider__head {
85
+ display: flex;
86
+ justify-content: flex-end;
87
+ }
88
+ .vmz-ui-sider__toggle {
89
+ display: inline-flex;
90
+ align-items: center;
91
+ justify-content: center;
92
+ width: 2.5rem;
93
+ height: 2.5rem;
94
+ padding: 0;
95
+ border: 0;
96
+ border-radius: var(--vmz-radius-md, 6px);
97
+ background: transparent;
98
+ color: var(--vmz-text-ink);
99
+ cursor: pointer;
100
+ outline: none;
101
+ }
102
+ .vmz-ui-sider__toggle:hover {
103
+ background: var(--vmz-surface-mist);
104
+ }
105
+ .vmz-ui-sider__toggle:focus-visible {
106
+ box-shadow: 0 0 0 3px var(--vmz-focus-ring);
107
+ }
108
+ .vmz-ui-sider__body {
109
+ display: none;
110
+ padding-top: var(--vmz-space-2, 0.5rem);
111
+ }
112
+ .vmz-ui-sider__body[data-open="true"] {
113
+ display: flex;
18
114
  }
19
115
  }
20
116
  </style>
21
117
  <script client>
22
- export default class Sider {}
118
+ /**
119
+ * VMZ UI — Sider (Navigation column).
120
+ * Parent supplies grouped links; item 36px / radius 6 / selected soft-blue rail.
121
+ * Below 720px the column collapses behind a 40x40 menu control.
122
+ */
123
+ export default class Sider {
124
+ public label: string = "";
125
+ public bodyId: string = "vmz-ui-sider-body";
126
+ open = true;
127
+
128
+ toggle() {
129
+ this.open = !this.open;
130
+ }
131
+
132
+ onKeyDown(e) {
133
+ if (e && e.key === "Escape") this.open = false;
134
+ }
135
+ }
23
136
  </script>
@@ -13,25 +13,26 @@
13
13
  display: flex;
14
14
  flex-direction: column;
15
15
  gap: var(--vmz-density-compact-gap);
16
- min-height: 4.5rem;
16
+ min-height: calc(var(--vmz-space-8) + var(--vmz-space-2));
17
17
  }
18
18
 
19
19
  .vmz-ui-skeleton__block,
20
20
  .vmz-ui-skeleton__line {
21
- border-radius: var(--vmz-radius-md, 6px);
22
- background: color-mix(in srgb, var(--vmz-action-primary-background) 18%, transparent);
23
- animation: vmz-ui-skeleton-pulse var(--vmz-motion-overlay-duration)
24
- var(--vmz-motion-overlay-easing) infinite alternate;
21
+ background: var(--vmz-surface-mist);
22
+ animation: vmz-ui-skeleton-pulse calc(var(--vmz-motion-control-duration) * 10)
23
+ var(--vmz-motion-control-easing) infinite alternate;
25
24
  }
26
25
 
27
26
  .vmz-ui-skeleton__block {
28
27
  width: 100%;
29
- height: 2.5rem;
28
+ height: var(--vmz-control-height-lg);
29
+ border-radius: var(--vmz-radius-lg);
30
30
  }
31
31
 
32
32
  .vmz-ui-skeleton__line {
33
33
  width: 72%;
34
- height: 0.75rem;
34
+ height: var(--vmz-space-3);
35
+ border-radius: var(--vmz-radius-sm);
35
36
  }
36
37
 
37
38
  .vmz-ui-skeleton__line + .vmz-ui-skeleton__line {
@@ -57,6 +58,7 @@
57
58
  .vmz-ui-skeleton__block,
58
59
  .vmz-ui-skeleton__line {
59
60
  animation: none;
61
+ opacity: 0.75;
60
62
  }
61
63
  }
62
64
  </style>
@@ -6,19 +6,25 @@
6
6
  <style>
7
7
  .vmz-ui-space {
8
8
  display: flex;
9
- gap: var(--vmz-density-control-gap, 0.75rem);
9
+ align-items: center;
10
+ gap: var(--vmz-space-3);
10
11
  }
12
+
11
13
  .vmz-ui-space[data-direction="vertical"] {
12
14
  flex-direction: column;
15
+ align-items: stretch;
13
16
  }
17
+
14
18
  .vmz-ui-space[data-wrap="true"] {
15
19
  flex-wrap: wrap;
16
20
  }
21
+
17
22
  .vmz-ui-space[data-size="small"] {
18
- gap: 0.5rem;
23
+ gap: var(--vmz-space-2);
19
24
  }
25
+
20
26
  .vmz-ui-space[data-size="large"] {
21
- gap: 1.5rem;
27
+ gap: var(--vmz-space-5);
22
28
  }
23
29
  </style>
24
30
  <script client>
@@ -4,13 +4,14 @@
4
4
  <style>
5
5
  .vmz-ui-spinner {
6
6
  display: inline-block;
7
+ box-sizing: border-box;
7
8
  width: 1rem;
8
9
  height: 1rem;
9
- border: 2px solid color-mix(in srgb, var(--vmz-action-primary-background) 25%, transparent);
10
+ border: 2px solid var(--vmz-action-primary-soft);
10
11
  border-top-color: var(--vmz-action-primary-background);
11
- border-radius: 50%;
12
+ border-radius: var(--vmz-radius-pill);
12
13
  vertical-align: -0.15em;
13
- animation: vmz-ui-spin var(--vmz-motion-overlay-duration) var(--vmz-motion-overlay-easing)
14
+ animation: vmz-ui-spin var(--vmz-motion-control-duration) var(--vmz-motion-control-easing)
14
15
  infinite;
15
16
  }
16
17
 
@@ -34,6 +35,6 @@
34
35
  * Loading indicator; reduced-motion keeps static feedback.
35
36
  */
36
37
  export default class Spinner {
37
- public label: string = "Loading";
38
+ public label: string = "";
38
39
  }
39
40
  </script>