@vmz/ui 0.3.6 → 0.3.8

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 +53 -51
  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 +127 -94
  6. package/src/components/Alert.vmz +125 -28
  7. package/src/components/AppShell.vmz +125 -57
  8. package/src/components/Autocomplete.vmz +261 -154
  9. package/src/components/Avatar.vmz +20 -12
  10. package/src/components/Badge.vmz +62 -70
  11. package/src/components/Breadcrumb.vmz +86 -72
  12. package/src/components/BulkActions.vmz +56 -51
  13. package/src/components/Button.vmz +237 -211
  14. package/src/components/Callout.vmz +178 -77
  15. package/src/components/Card.vmz +141 -120
  16. package/src/components/Checkbox.vmz +131 -55
  17. package/src/components/CodeBlock.vmz +77 -30
  18. package/src/components/Collapse.vmz +64 -11
  19. package/src/components/ConsoleShell.vmz +405 -251
  20. package/src/components/Content.vmz +15 -1
  21. package/src/components/DataTable.vmz +294 -194
  22. package/src/components/DatePicker.vmz +766 -674
  23. package/src/components/Descriptions.vmz +90 -0
  24. package/src/components/Dialog.vmz +137 -38
  25. package/src/components/Divider.vmz +8 -1
  26. package/src/components/Drawer.vmz +518 -427
  27. package/src/components/Dropdown.vmz +195 -51
  28. package/src/components/Empty.vmz +96 -54
  29. package/src/components/Field.vmz +170 -115
  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 +78 -79
  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 +25 -10
  39. package/src/components/List.vmz +124 -102
  40. package/src/components/Menu.vmz +416 -243
  41. package/src/components/Message.vmz +167 -0
  42. package/src/components/Notification.vmz +132 -37
  43. package/src/components/Pagination.vmz +237 -69
  44. package/src/components/Popconfirm.vmz +287 -0
  45. package/src/components/Popover.vmz +341 -223
  46. package/src/components/Progress.vmz +16 -5
  47. package/src/components/Prose.vmz +180 -81
  48. package/src/components/QueryForm.vmz +31 -9
  49. package/src/components/RadioGroup.vmz +204 -124
  50. package/src/components/Result.vmz +60 -27
  51. package/src/components/Segmented.vmz +86 -51
  52. package/src/components/Select.vmz +620 -533
  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 +118 -103
  58. package/src/components/Switch.vmz +131 -98
  59. package/src/components/Table.vmz +270 -165
  60. package/src/components/Tabs.vmz +125 -110
  61. package/src/components/Tag.vmz +60 -43
  62. package/src/components/TextArea.vmz +125 -118
  63. package/src/components/Timeline.vmz +111 -76
  64. package/src/components/Toc.vmz +90 -74
  65. package/src/components/Tooltip.vmz +107 -58
  66. package/src/components/Tree.vmz +214 -191
  67. package/src/components/Typography.vmz +23 -8
  68. package/src/components/Upload.vmz +1394 -1289
@@ -1,12 +1,42 @@
1
1
  <template>
2
2
  <details data-vmz-ui="dropdown" data-vmz-motion="overlay" data-vmz-motion-token="motion.overlay" class="vmz-ui-dropdown">
3
3
  <summary data-vmz-dropdown-trigger="1" class="vmz-ui-dropdown__trigger">
4
- {{ label }}
4
+ <span class="vmz-ui-dropdown__trigger-label">
5
+ {{ label }}
6
+ </span>
7
+ <Icon name="action.chevron-down" size="sm" decorative />
5
8
  </summary>
6
9
  <div data-vmz-dropdown-menu="1" role="menu" class="vmz-ui-dropdown__menu">
7
10
  <div v-for="item in items" :key="item.id" class="vmz-ui-dropdown__row">
8
- <div v-if="item.divider" role="separator" class="vmz-ui-dropdown__divider"></div>
9
- <a v-else :href="item.href" role="menuitem" :data-vmz-dropdown-item="item.id" :data-danger="item.danger ? 'true' : 'false'" :data-disabled="item.disabled ? 'true' : 'false'" :aria-disabled="item.disabled ? 'true' : null" :tabindex="item.disabled ? -1 : null" class="vmz-ui-dropdown__item">{{ item.title }}</a>
11
+ <div v-if="item.divider" role="separator" class="vmz-ui-dropdown__divider" />
12
+ <div v-else-if="item.kind === 'group'" role="presentation" class="vmz-ui-dropdown__group">
13
+ {{ item.title }}
14
+ </div>
15
+ <details v-else-if="item.children && item.children.length" class="vmz-ui-dropdown__sub">
16
+ <summary :data-danger="item.danger ? 'true' : 'false'" class="vmz-ui-dropdown__item vmz-ui-dropdown__item--sub">
17
+ <span class="vmz-ui-dropdown__label">
18
+ {{ item.title }}
19
+ </span>
20
+ <Icon name="action.chevron-right" size="sm" decorative />
21
+ </summary>
22
+ <div role="menu" class="vmz-ui-dropdown__subpanel">
23
+ <div v-for="child in item.children" :key="child.id" class="vmz-ui-dropdown__row">
24
+ <div v-if="child.divider" role="separator" class="vmz-ui-dropdown__divider" />
25
+ <a v-else-if="hasHref(child)" :href="child.href" role="menuitem" :data-vmz-dropdown-item="child.id" :data-danger="child.danger ? 'true' : 'false'" :data-active="child.active ? 'true' : 'false'" :data-disabled="child.disabled ? 'true' : 'false'" :aria-disabled="child.disabled ? 'true' : null" :tabindex="child.disabled ? -1 : null" class="vmz-ui-dropdown__item">
26
+ {{ child.title }}
27
+ </a>
28
+ <button v-else type="button" role="menuitem" :data-vmz-dropdown-item="child.id" :data-danger="child.danger ? 'true' : 'false'" :data-active="child.active ? 'true' : 'false'" :data-disabled="child.disabled ? 'true' : 'false'" :aria-disabled="child.disabled ? 'true' : null" :tabindex="child.disabled ? -1 : null" @click="onItemClick" class="vmz-ui-dropdown__item">
29
+ {{ child.title }}
30
+ </button>
31
+ </div>
32
+ </div>
33
+ </details>
34
+ <a v-else-if="hasHref(item)" :href="item.href" role="menuitem" :data-vmz-dropdown-item="item.id" :data-danger="item.danger ? 'true' : 'false'" :data-active="item.active ? 'true' : 'false'" :data-disabled="item.disabled ? 'true' : 'false'" :aria-disabled="item.disabled ? 'true' : null" :tabindex="item.disabled ? -1 : null" class="vmz-ui-dropdown__item">
35
+ {{ item.title }}
36
+ </a>
37
+ <button v-else type="button" role="menuitem" :data-vmz-dropdown-item="item.id" :data-danger="item.danger ? 'true' : 'false'" :data-active="item.active ? 'true' : 'false'" :data-disabled="item.disabled ? 'true' : 'false'" :aria-disabled="item.disabled ? 'true' : null" :tabindex="item.disabled ? -1 : null" @click="onItemClick" class="vmz-ui-dropdown__item">
38
+ {{ item.title }}
39
+ </button>
10
40
  </div>
11
41
  </div>
12
42
  </details>
@@ -19,41 +49,41 @@
19
49
 
20
50
  .vmz-ui-dropdown__trigger {
21
51
  list-style: none;
22
- min-height: 2.5rem;
52
+ min-height: 40px;
53
+ box-sizing: border-box;
23
54
  display: inline-flex;
24
55
  align-items: center;
25
- gap: 0.375rem;
26
- padding: 0 0.9375rem;
56
+ gap: 6px;
57
+ padding: 0 15px;
27
58
  border: 1px solid var(--vmz-line-default);
28
- border-radius: var(--vmz-radius-md, 6px);
59
+ border-radius: var(--vmz-radius-md);
29
60
  background: var(--vmz-surface-paper);
30
61
  color: var(--vmz-text-ink);
31
62
  cursor: pointer;
32
- font-weight: 550;
63
+ font: inherit;
64
+ font-size: 14px;
65
+ font-weight: 500;
66
+ line-height: 22px;
33
67
  outline: none;
34
68
  transition:
35
- border-color var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1)),
36
- background-color var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1)),
37
- box-shadow var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1));
69
+ border-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
70
+ background-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing);
38
71
  }
39
72
 
40
- /* Chevron: affordance that the trigger opens a surface, not a page. */
41
- .vmz-ui-dropdown__trigger::after {
42
- content: "";
43
- width: 0.4375rem;
44
- height: 0.4375rem;
45
- margin-top: -0.1875rem;
46
- border-right: 1.5px solid currentColor;
47
- border-bottom: 1.5px solid currentColor;
48
- opacity: 0.55;
49
- transform: rotate(45deg);
50
- transition: transform var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1));
73
+ .vmz-ui-dropdown summary::-webkit-details-marker {
74
+ display: none;
51
75
  }
52
76
 
53
- .vmz-ui-dropdown summary::-webkit-details-marker {
77
+ .vmz-ui-dropdown__trigger-label:empty {
54
78
  display: none;
55
79
  }
56
80
 
81
+ .vmz-ui-dropdown__trigger .vmz-ui-icon {
82
+ flex: 0 0 auto;
83
+ color: var(--vmz-text-tertiary);
84
+ transition: transform var(--vmz-motion-control-duration) var(--vmz-motion-control-easing);
85
+ }
86
+
57
87
  .vmz-ui-dropdown__trigger:hover {
58
88
  border-color: var(--vmz-action-primary-hover);
59
89
  background: var(--vmz-surface-mist);
@@ -67,34 +97,40 @@
67
97
  border-color: var(--vmz-action-primary-background);
68
98
  }
69
99
 
70
- .vmz-ui-dropdown[open] > .vmz-ui-dropdown__trigger::after {
71
- margin-top: 0.125rem;
72
- transform: rotate(-135deg);
100
+ .vmz-ui-dropdown[open] > .vmz-ui-dropdown__trigger .vmz-ui-icon {
101
+ transform: rotate(180deg);
73
102
  }
74
103
 
75
104
  .vmz-ui-dropdown__menu {
76
105
  position: absolute;
77
- top: calc(100% + 0.375rem);
106
+ top: calc(100% + 6px);
78
107
  right: 0;
79
108
  z-index: 50;
80
- min-width: 11rem;
81
- padding: 0.25rem;
82
- border: 1px solid var(--vmz-line-subtle, var(--vmz-line-default));
83
- border-radius: var(--vmz-radius-lg, 8px);
109
+ min-width: 176px;
110
+ box-sizing: border-box;
111
+ padding: 4px;
112
+ border: 1px solid var(--vmz-line-subtle);
113
+ border-radius: var(--vmz-radius-lg);
84
114
  background: var(--vmz-surface-paper);
115
+ color: var(--vmz-text-ink);
85
116
  box-shadow: var(--vmz-shadow-popup);
86
117
  transform-origin: top right;
87
- animation: vmz-dropdown-in var(--vmz-motion-overlay-duration, 180ms) var(--vmz-motion-overlay-easing, cubic-bezier(0.2, 0, 0, 1));
118
+ animation: vmz-dropdown-in var(--vmz-motion-overlay-duration)
119
+ var(--vmz-motion-overlay-easing);
120
+ }
121
+
122
+ .vmz-ui-dropdown__item--sub {
123
+ list-style: none;
88
124
  }
89
125
 
90
126
  @keyframes vmz-dropdown-in {
91
127
  from {
92
128
  opacity: 0;
93
- transform: translateY(-0.25rem) scale(0.98);
129
+ transform: translateY(-4px) scale(0.98);
94
130
  }
95
131
  to {
96
132
  opacity: 1;
97
- transform: translateY(0) scale(1);
133
+ transform: none;
98
134
  }
99
135
  }
100
136
 
@@ -103,7 +139,7 @@
103
139
  animation: none;
104
140
  }
105
141
  .vmz-ui-dropdown__trigger,
106
- .vmz-ui-dropdown__trigger::after {
142
+ .vmz-ui-dropdown__trigger .vmz-ui-icon {
107
143
  transition: none;
108
144
  }
109
145
  }
@@ -113,17 +149,27 @@
113
149
  }
114
150
 
115
151
  .vmz-ui-dropdown__item {
116
- display: block;
117
- padding: 0.5rem 0.75rem;
118
- border-radius: var(--vmz-radius-sm, 4px);
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 8px;
155
+ width: 100%;
156
+ min-height: 32px;
157
+ box-sizing: border-box;
158
+ padding: 0 12px;
159
+ border: 0;
160
+ border-radius: var(--vmz-radius-md);
161
+ background: transparent;
119
162
  color: var(--vmz-text-ink);
120
- font-size: 0.875rem;
121
- line-height: 1.4286;
163
+ cursor: pointer;
164
+ font: inherit;
165
+ font-size: 14px;
166
+ line-height: 22px;
167
+ text-align: left;
122
168
  text-decoration: none;
123
169
  outline: none;
124
170
  transition:
125
- background-color var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1)),
126
- color var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1));
171
+ background-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
172
+ color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing);
127
173
  }
128
174
 
129
175
  .vmz-ui-dropdown__item:hover {
@@ -132,49 +178,147 @@
132
178
 
133
179
  .vmz-ui-dropdown__item:focus-visible {
134
180
  background: var(--vmz-surface-mist);
135
- box-shadow: inset 0 0 0 3px var(--vmz-focus-ring);
181
+ box-shadow: inset 0 0 0 2px var(--vmz-focus-ring);
182
+ }
183
+
184
+ .vmz-ui-dropdown__item[data-active="true"] {
185
+ background: color-mix(in srgb, var(--vmz-action-primary-background) 10%, transparent);
186
+ box-shadow: inset 2px 0 0 var(--vmz-action-primary-background);
136
187
  }
137
188
 
138
189
  .vmz-ui-dropdown__item[data-danger="true"] {
139
190
  color: var(--vmz-status-danger-foreground);
140
191
  }
141
192
 
142
- .vmz-ui-dropdown__item[data-danger="true"]:hover {
193
+ .vmz-ui-dropdown__item[data-danger="true"]:hover,
194
+ .vmz-ui-dropdown__item[data-danger="true"]:focus-visible {
143
195
  background: color-mix(in srgb, var(--vmz-status-danger-accent) 12%, transparent);
144
196
  }
145
197
 
146
198
  .vmz-ui-dropdown__item[data-disabled="true"] {
147
- color: var(--vmz-text-steel);
199
+ color: var(--vmz-text-tertiary);
148
200
  cursor: not-allowed;
149
201
  pointer-events: none;
150
202
  }
151
203
 
152
204
  .vmz-ui-dropdown__divider {
153
205
  height: 1px;
154
- margin: 0.25rem 0.5rem;
155
- background: var(--vmz-line-subtle, var(--vmz-line-default));
206
+ margin: 4px 8px;
207
+ background: var(--vmz-line-subtle);
208
+ }
209
+
210
+ .vmz-ui-dropdown__group {
211
+ padding: 8px 12px 4px;
212
+ font-size: 12px;
213
+ font-weight: 600;
214
+ line-height: 20px;
215
+ color: var(--vmz-text-tertiary);
216
+ }
217
+
218
+ .vmz-ui-dropdown__label {
219
+ flex: 1 1 auto;
220
+ min-width: 0;
221
+ overflow: hidden;
222
+ text-overflow: ellipsis;
223
+ white-space: nowrap;
224
+ }
225
+
226
+ .vmz-ui-dropdown__item--sub .vmz-ui-icon {
227
+ flex: 0 0 auto;
228
+ color: var(--vmz-text-tertiary);
229
+ }
230
+
231
+ .vmz-ui-dropdown__sub {
232
+ position: relative;
233
+ }
234
+
235
+ .vmz-ui-dropdown__sub[open] > .vmz-ui-dropdown__item--sub {
236
+ background: var(--vmz-surface-mist);
237
+ }
238
+
239
+ .vmz-ui-dropdown__subpanel {
240
+ display: flex;
241
+ flex-direction: column;
242
+ margin: 2px 0 4px;
243
+ padding: 4px 0 4px 12px;
244
+ border-left: 1px solid var(--vmz-line-subtle);
156
245
  }
157
246
  </style>
158
247
  <script client>
159
248
  /**
160
249
  * VMZ UI — Dropdown (Menu).
161
- * Native `<details>` disclosure: works without JS, menu items are plain links
162
- * unless a host attaches selection handlers.
250
+ * Native `<details>` disclosure: works without JS. An item with a non-empty
251
+ * `href` is a plain link; an item without `href` is an action button that
252
+ * notifies via `onSelect(id)`. Selection handlers never navigate.
253
+ *
254
+ * Item model adds `kind: "group"` headers, `active` styling and one level of
255
+ * nested `children` rendered inside a nested `<details>` submenu.
163
256
  */
164
257
  export type DropdownItem = {
165
258
  id: string;
166
259
  title: string;
260
+ /** Non-empty → link item (native navigation). Empty/absent → action item. */
167
261
  href?: string;
168
262
  /** Destructive action styling. */
169
263
  danger?: boolean;
170
- /** Non-interactive state. */
264
+ /** Non-interactive state: neither navigates nor selects. */
171
265
  disabled?: boolean;
172
266
  /** Render a separator instead of a row (title/href are ignored). */
173
267
  divider?: boolean;
268
+ /** `"group"` renders a 12px tertiary section title. */
269
+ kind?: string;
270
+ /** Current selection — light primary fill plus a 2px leading bar. */
271
+ active?: boolean;
272
+ /** One level of nested items rendered as a submenu. */
273
+ children?: DropdownItem[];
174
274
  };
175
275
 
176
276
  export default class Dropdown {
177
- public label: string = "Menu";
277
+ public label: string = "";
178
278
  public items: DropdownItem[] = [];
279
+ /** Action-item selection callback (`:on-select`). Link items bypass it. */
280
+ public onSelect: ((id: string) => void) | null = null;
281
+
282
+ hasHref(item) {
283
+ return typeof item?.href === "string" && item.href.length > 0;
284
+ }
285
+
286
+ onItemClick(ev) {
287
+ const el = ev && ev.currentTarget;
288
+ const id =
289
+ el && typeof el.getAttribute === "function"
290
+ ? el.getAttribute("data-vmz-dropdown-item")
291
+ : null;
292
+ if (!id) return;
293
+ const item = this._findItem(id);
294
+ if (!item || item.disabled || this.hasHref(item)) return;
295
+ this.close();
296
+ if (typeof this.onSelect === "function") this.onSelect(id);
297
+ }
298
+
299
+ _findItem(id) {
300
+ for (const item of this.items || []) {
301
+ if (item.id === id) return item;
302
+ if (Array.isArray(item.children)) {
303
+ const child = item.children.find((candidate) => candidate.id === id);
304
+ if (child) return child;
305
+ }
306
+ }
307
+ return null;
308
+ }
309
+
310
+ close() {
311
+ const root = this.__vmzDomRoot;
312
+ if (!root) return;
313
+ const details =
314
+ root.tagName === "DETAILS"
315
+ ? root
316
+ : typeof root.closest === "function"
317
+ ? root.closest("details")
318
+ : null;
319
+ if (details && typeof details.removeAttribute === "function") {
320
+ details.removeAttribute("open");
321
+ }
322
+ }
179
323
  }
180
324
  </script>
@@ -1,54 +1,96 @@
1
- <template>
2
- <div data-vmz-ui="empty" class="vmz-ui-empty">
3
- <h2 class="vmz-ui-empty__title">
4
- {{ title }}
5
- </h2>
6
- <p v-if="description" class="vmz-ui-empty__description">
7
- {{ description }}
8
- </p>
9
- <div class="vmz-ui-empty__actions">
10
- <slot />
11
- </div>
12
- </div>
13
- </template>
14
- <style>
15
- .vmz-ui-empty {
16
- display: flex;
17
- flex-direction: column;
18
- align-items: flex-start;
19
- gap: 0.625rem;
20
- padding: 1.5rem 1.15rem;
21
- border: 1px dashed var(--vmz-line-default);
22
- border-radius: var(--vmz-radius-md, 6px);
23
- background: color-mix(in srgb, var(--vmz-surface-mist) 70%, var(--vmz-surface-paper));
24
- color: var(--vmz-text-ink);
25
- }
26
-
27
- .vmz-ui-empty__title {
28
- margin: 0;
29
- font-family: var(--vmz-font-display);
30
- font-size: 1.1rem;
31
- font-weight: 700;
32
- }
33
-
34
- .vmz-ui-empty__description {
35
- margin: 0;
36
- color: var(--vmz-text-steel);
37
- max-width: 36rem;
38
- line-height: 1.45;
39
- }
40
-
41
- .vmz-ui-empty__actions:empty {
42
- display: none;
43
- }
44
- </style>
45
- <script client>
46
- /**
47
- * VMZ UI — Empty (Commercial composition).
48
- * Empty / zero-state surface with optional action slot.
49
- */
50
- export default class Empty {
51
- public title: string = "Nothing here yet";
52
- public description: string = "";
53
- }
54
- </script>
1
+ <template>
2
+ <div data-vmz-ui="empty" class="vmz-ui-empty">
3
+ <span aria-hidden="true" class="vmz-ui-empty__illustration">
4
+ <Icon :name="icon" size="lg" decorative />
5
+ </span>
6
+ <h2 v-if="title" class="vmz-ui-empty__title">
7
+ {{ title }}
8
+ </h2>
9
+ <p v-if="description" class="vmz-ui-empty__description">
10
+ {{ description }}
11
+ </p>
12
+ <div class="vmz-ui-empty__actions">
13
+ <slot />
14
+ </div>
15
+ </div>
16
+ </template>
17
+ <style>
18
+ .vmz-ui-empty {
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ justify-content: center;
23
+ gap: var(--vmz-space-2, 0.5rem);
24
+ padding: var(--vmz-space-6, 2rem);
25
+ text-align: center;
26
+ background: transparent;
27
+ color: var(--vmz-text-ink);
28
+ }
29
+
30
+ .vmz-ui-empty__illustration {
31
+ display: inline-flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ width: 4rem;
35
+ height: 4rem;
36
+ margin-bottom: var(--vmz-space-1, 0.25rem);
37
+ border: 1px solid var(--vmz-line-default, var(--vmz-line-strong));
38
+ border-radius: var(--vmz-radius-pill, 999px);
39
+ background: var(--vmz-surface-mist);
40
+ color: var(--vmz-text-muted);
41
+ }
42
+
43
+ .vmz-ui-empty__illustration .vmz-ui-icon[data-size] {
44
+ font-size: 2rem;
45
+ }
46
+
47
+ .vmz-ui-empty__title {
48
+ margin: 0;
49
+ font-family: var(--vmz-font-display, var(--vmz-font-sans, inherit));
50
+ font-size: 1rem;
51
+ font-weight: 600;
52
+ line-height: 1.4;
53
+ color: var(--vmz-text-ink);
54
+ }
55
+
56
+ .vmz-ui-empty__description {
57
+ margin: 0;
58
+ max-width: 36rem;
59
+ font-size: 0.875rem;
60
+ line-height: 1.55;
61
+ color: var(--vmz-text-tertiary);
62
+ }
63
+
64
+ .vmz-ui-empty__actions {
65
+ display: flex;
66
+ flex-wrap: wrap;
67
+ justify-content: center;
68
+ gap: var(--vmz-space-2, 0.5rem);
69
+ margin-top: var(--vmz-space-2, 0.5rem);
70
+ }
71
+
72
+ .vmz-ui-empty__actions:empty {
73
+ display: none;
74
+ }
75
+
76
+ :where([data-density="compact"]) .vmz-ui-empty {
77
+ gap: var(--vmz-density-compact-gap);
78
+ padding: var(--vmz-space-5, 1.5rem);
79
+ }
80
+
81
+ :where([data-density="dense"]) .vmz-ui-empty {
82
+ gap: var(--vmz-density-dense-gap);
83
+ padding: var(--vmz-space-4, 1rem);
84
+ }
85
+ </style>
86
+ <script client>
87
+ /**
88
+ * VMZ UI — Empty (Commercial composition).
89
+ * Empty / zero-state surface with neutral registry icon and optional action slot.
90
+ */
91
+ export default class Empty {
92
+ public title: string = "";
93
+ public description: string = "";
94
+ public icon: string = "feature.runtime";
95
+ }
96
+ </script>