@vmz/ui 0.3.7 → 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 +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 +217 -63
  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
@@ -1,15 +1,40 @@
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
11
  <div v-if="item.divider" role="separator" class="vmz-ui-dropdown__divider" />
9
- <a v-else-if="hasHref(item)" :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">
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">
10
35
  {{ item.title }}
11
36
  </a>
12
- <button v-else type="button" 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" @click="onItemClick" class="vmz-ui-dropdown__item">
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">
13
38
  {{ item.title }}
14
39
  </button>
15
40
  </div>
@@ -24,41 +49,41 @@
24
49
 
25
50
  .vmz-ui-dropdown__trigger {
26
51
  list-style: none;
27
- min-height: 2.5rem;
52
+ min-height: 40px;
53
+ box-sizing: border-box;
28
54
  display: inline-flex;
29
55
  align-items: center;
30
- gap: 0.375rem;
31
- padding: 0 0.9375rem;
56
+ gap: 6px;
57
+ padding: 0 15px;
32
58
  border: 1px solid var(--vmz-line-default);
33
- border-radius: var(--vmz-radius-md, 6px);
59
+ border-radius: var(--vmz-radius-md);
34
60
  background: var(--vmz-surface-paper);
35
61
  color: var(--vmz-text-ink);
36
62
  cursor: pointer;
37
- font-weight: 550;
63
+ font: inherit;
64
+ font-size: 14px;
65
+ font-weight: 500;
66
+ line-height: 22px;
38
67
  outline: none;
39
68
  transition:
40
- border-color var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1)),
41
- background-color var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1)),
42
- 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);
43
71
  }
44
72
 
45
- /* Chevron: affordance that the trigger opens a surface, not a page. */
46
- .vmz-ui-dropdown__trigger::after {
47
- content: "";
48
- width: 0.4375rem;
49
- height: 0.4375rem;
50
- margin-top: -0.1875rem;
51
- border-right: 1.5px solid currentColor;
52
- border-bottom: 1.5px solid currentColor;
53
- opacity: 0.55;
54
- transform: rotate(45deg);
55
- 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;
56
75
  }
57
76
 
58
- .vmz-ui-dropdown summary::-webkit-details-marker {
77
+ .vmz-ui-dropdown__trigger-label:empty {
59
78
  display: none;
60
79
  }
61
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
+
62
87
  .vmz-ui-dropdown__trigger:hover {
63
88
  border-color: var(--vmz-action-primary-hover);
64
89
  background: var(--vmz-surface-mist);
@@ -72,34 +97,40 @@
72
97
  border-color: var(--vmz-action-primary-background);
73
98
  }
74
99
 
75
- .vmz-ui-dropdown[open] > .vmz-ui-dropdown__trigger::after {
76
- margin-top: 0.125rem;
77
- transform: rotate(-135deg);
100
+ .vmz-ui-dropdown[open] > .vmz-ui-dropdown__trigger .vmz-ui-icon {
101
+ transform: rotate(180deg);
78
102
  }
79
103
 
80
104
  .vmz-ui-dropdown__menu {
81
105
  position: absolute;
82
- top: calc(100% + 0.375rem);
106
+ top: calc(100% + 6px);
83
107
  right: 0;
84
108
  z-index: 50;
85
- min-width: 11rem;
86
- padding: 0.25rem;
87
- border: 1px solid var(--vmz-line-subtle, var(--vmz-line-default));
88
- 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);
89
114
  background: var(--vmz-surface-paper);
115
+ color: var(--vmz-text-ink);
90
116
  box-shadow: var(--vmz-shadow-popup);
91
117
  transform-origin: top right;
92
- 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;
93
124
  }
94
125
 
95
126
  @keyframes vmz-dropdown-in {
96
127
  from {
97
128
  opacity: 0;
98
- transform: translateY(-0.25rem) scale(0.98);
129
+ transform: translateY(-4px) scale(0.98);
99
130
  }
100
131
  to {
101
132
  opacity: 1;
102
- transform: translateY(0) scale(1);
133
+ transform: none;
103
134
  }
104
135
  }
105
136
 
@@ -108,7 +139,7 @@
108
139
  animation: none;
109
140
  }
110
141
  .vmz-ui-dropdown__trigger,
111
- .vmz-ui-dropdown__trigger::after {
142
+ .vmz-ui-dropdown__trigger .vmz-ui-icon {
112
143
  transition: none;
113
144
  }
114
145
  }
@@ -118,23 +149,27 @@
118
149
  }
119
150
 
120
151
  .vmz-ui-dropdown__item {
121
- display: block;
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 8px;
122
155
  width: 100%;
123
- padding: 0.5rem 0.75rem;
156
+ min-height: 32px;
157
+ box-sizing: border-box;
158
+ padding: 0 12px;
124
159
  border: 0;
125
- border-radius: var(--vmz-radius-sm, 4px);
160
+ border-radius: var(--vmz-radius-md);
126
161
  background: transparent;
127
162
  color: var(--vmz-text-ink);
128
163
  cursor: pointer;
129
164
  font: inherit;
130
- font-size: 0.875rem;
131
- line-height: 1.4286;
165
+ font-size: 14px;
166
+ line-height: 22px;
132
167
  text-align: left;
133
168
  text-decoration: none;
134
169
  outline: none;
135
170
  transition:
136
- background-color var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1)),
137
- 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);
138
173
  }
139
174
 
140
175
  .vmz-ui-dropdown__item:hover {
@@ -143,27 +178,70 @@
143
178
 
144
179
  .vmz-ui-dropdown__item:focus-visible {
145
180
  background: var(--vmz-surface-mist);
146
- 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);
147
187
  }
148
188
 
149
189
  .vmz-ui-dropdown__item[data-danger="true"] {
150
190
  color: var(--vmz-status-danger-foreground);
151
191
  }
152
192
 
153
- .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 {
154
195
  background: color-mix(in srgb, var(--vmz-status-danger-accent) 12%, transparent);
155
196
  }
156
197
 
157
198
  .vmz-ui-dropdown__item[data-disabled="true"] {
158
- color: var(--vmz-text-steel);
199
+ color: var(--vmz-text-tertiary);
159
200
  cursor: not-allowed;
160
201
  pointer-events: none;
161
202
  }
162
203
 
163
204
  .vmz-ui-dropdown__divider {
164
205
  height: 1px;
165
- margin: 0.25rem 0.5rem;
166
- 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);
167
245
  }
168
246
  </style>
169
247
  <script client>
@@ -172,6 +250,9 @@
172
250
  * Native `<details>` disclosure: works without JS. An item with a non-empty
173
251
  * `href` is a plain link; an item without `href` is an action button that
174
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.
175
256
  */
176
257
  export type DropdownItem = {
177
258
  id: string;
@@ -184,10 +265,16 @@ export type DropdownItem = {
184
265
  disabled?: boolean;
185
266
  /** Render a separator instead of a row (title/href are ignored). */
186
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[];
187
274
  };
188
275
 
189
276
  export default class Dropdown {
190
- public label: string = "Menu";
277
+ public label: string = "";
191
278
  public items: DropdownItem[] = [];
192
279
  /** Action-item selection callback (`:on-select`). Link items bypass it. */
193
280
  public onSelect: ((id: string) => void) | null = null;
@@ -203,12 +290,23 @@ export default class Dropdown {
203
290
  ? el.getAttribute("data-vmz-dropdown-item")
204
291
  : null;
205
292
  if (!id) return;
206
- const item = this.items.find((candidate) => candidate.id === id);
293
+ const item = this._findItem(id);
207
294
  if (!item || item.disabled || this.hasHref(item)) return;
208
295
  this.close();
209
296
  if (typeof this.onSelect === "function") this.onSelect(id);
210
297
  }
211
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
+
212
310
  close() {
213
311
  const root = this.__vmzDomRoot;
214
312
  if (!root) return;
@@ -1,6 +1,9 @@
1
1
  <template>
2
2
  <div data-vmz-ui="empty" class="vmz-ui-empty">
3
- <h2 class="vmz-ui-empty__title">
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">
4
7
  {{ title }}
5
8
  </h2>
6
9
  <p v-if="description" class="vmz-ui-empty__description">
@@ -15,40 +18,79 @@
15
18
  .vmz-ui-empty {
16
19
  display: flex;
17
20
  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));
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;
24
27
  color: var(--vmz-text-ink);
25
28
  }
26
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
+
27
47
  .vmz-ui-empty__title {
28
48
  margin: 0;
29
- font-family: var(--vmz-font-display);
30
- font-size: 1.1rem;
31
- font-weight: 700;
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);
32
54
  }
33
55
 
34
56
  .vmz-ui-empty__description {
35
57
  margin: 0;
36
- color: var(--vmz-text-steel);
37
58
  max-width: 36rem;
38
- line-height: 1.45;
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);
39
70
  }
40
71
 
41
72
  .vmz-ui-empty__actions:empty {
42
73
  display: none;
43
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
+ }
44
85
  </style>
45
86
  <script client>
46
87
  /**
47
88
  * VMZ UI — Empty (Commercial composition).
48
- * Empty / zero-state surface with optional action slot.
89
+ * Empty / zero-state surface with neutral registry icon and optional action slot.
49
90
  */
50
91
  export default class Empty {
51
- public title: string = "Nothing here yet";
92
+ public title: string = "";
52
93
  public description: string = "";
94
+ public icon: string = "feature.runtime";
53
95
  }
54
96
  </script>
@@ -1,9 +1,13 @@
1
1
  <template>
2
- <div data-vmz-ui="field" :data-invalid="invalidAttr" class="vmz-ui-field">
2
+ <div data-vmz-ui="field" :data-invalid="error ? 'true' : 'false'" class="vmz-ui-field">
3
3
  <label v-if="label" :for="controlId" class="vmz-ui-field__label">
4
4
  {{ label }}
5
5
  </label>
6
- <input :id="controlId" :type="type" :value="value" :min="min" :max="max" :step="step" :disabled="disabled" :aria-invalid="error ? 'true' : 'false'" :aria-describedby="describedBy" @input="onDomInput" class="vmz-ui-field__control" />
6
+ <div :data-prefix="prefix ? 'true' : 'false'" :data-suffix="suffix ? 'true' : 'false'" class="vmz-ui-field__control-wrap">
7
+ <Icon v-if="prefix" :name="prefix" size="md" decorative class="vmz-ui-field__icon vmz-ui-field__icon--prefix" />
8
+ <input :id="controlId" :type="type" :value="value" :min="min" :max="max" :step="step" :placeholder="placeholder" :disabled="disabled" :readonly="readonly" :aria-invalid="error ? 'true' : 'false'" :aria-describedby="describedBy" @input="onDomInput" class="vmz-ui-field__control" />
9
+ <Icon v-if="suffix" :name="suffix" size="md" decorative class="vmz-ui-field__icon vmz-ui-field__icon--suffix" />
10
+ </div>
7
11
  <p v-if="description" :id="descriptionId" class="vmz-ui-field__description">
8
12
  {{ description }}
9
13
  </p>
@@ -16,7 +20,8 @@
16
20
  .vmz-ui-field {
17
21
  display: flex;
18
22
  flex-direction: column;
19
- gap: 0.375rem;
23
+ gap: var(--vmz-space-2);
24
+ width: 100%;
20
25
  max-width: 28rem;
21
26
  }
22
27
 
@@ -24,32 +29,67 @@
24
29
  font: inherit;
25
30
  font-weight: 500;
26
31
  font-size: 0.875rem;
27
- color: var(--vmz-text-ink, inherit);
32
+ line-height: 1.5715;
33
+ color: var(--vmz-text-ink);
34
+ }
35
+
36
+ .vmz-ui-field__control-wrap {
37
+ position: relative;
38
+ display: flex;
39
+ align-items: center;
40
+ width: 100%;
28
41
  }
29
42
 
30
43
  .vmz-ui-field__control {
31
44
  box-sizing: border-box;
32
45
  width: 100%;
33
- min-height: var(--vmz-control-height, 2rem);
34
- font-family: var(--vmz-font-sans, inherit);
46
+ min-height: var(--vmz-control-height);
47
+ font-family: inherit;
35
48
  font-size: 0.875rem;
36
49
  line-height: 1.5715;
37
- padding: var(--vmz-density-control-padding-y, 0.25rem)
38
- var(--vmz-density-control-padding-x, 0.75rem);
39
- border: 1px solid var(--vmz-line-default, #d9d9d9);
40
- border-radius: var(--vmz-radius-md, 6px);
41
- background: var(--vmz-surface-paper, #fff);
42
- color: var(--vmz-text-ink, inherit);
50
+ padding: var(--vmz-density-control-padding-y) var(--vmz-density-control-padding-x);
51
+ border: 1px solid var(--vmz-line-default);
52
+ border-radius: var(--vmz-radius-lg);
53
+ background: var(--vmz-surface-paper);
54
+ color: var(--vmz-text-ink);
43
55
  outline: none;
44
56
  transition:
45
- border-color var(--vmz-motion-control-duration, 120ms)
46
- var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1)),
47
- box-shadow var(--vmz-motion-control-duration, 120ms)
48
- var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1));
57
+ border-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
58
+ box-shadow var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
59
+ background-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing);
60
+ }
61
+
62
+ .vmz-ui-field__control-wrap[data-prefix="true"] .vmz-ui-field__control {
63
+ padding-left: calc(
64
+ var(--vmz-density-control-padding-x) + var(--vmz-space-4) + var(--vmz-space-1)
65
+ );
66
+ }
67
+
68
+ .vmz-ui-field__control-wrap[data-suffix="true"] .vmz-ui-field__control {
69
+ padding-right: calc(
70
+ var(--vmz-density-control-padding-x) + var(--vmz-space-4) + var(--vmz-space-1)
71
+ );
72
+ }
73
+
74
+ .vmz-ui-field__icon {
75
+ position: absolute;
76
+ top: 50%;
77
+ z-index: 1;
78
+ color: var(--vmz-text-tertiary);
79
+ transform: translateY(-50%);
80
+ pointer-events: none;
81
+ }
82
+
83
+ .vmz-ui-field__icon--prefix {
84
+ left: var(--vmz-density-control-padding-x);
85
+ }
86
+
87
+ .vmz-ui-field__icon--suffix {
88
+ right: var(--vmz-density-control-padding-x);
49
89
  }
50
90
 
51
91
  .vmz-ui-field__control::placeholder {
52
- color: var(--vmz-text-steel, #8c8c8c);
92
+ color: var(--vmz-text-tertiary);
53
93
  opacity: 1;
54
94
  }
55
95
 
@@ -57,8 +97,13 @@
57
97
  border-color: var(--vmz-status-danger-accent);
58
98
  }
59
99
 
100
+ .vmz-ui-field[data-invalid="true"] .vmz-ui-field__control:hover:not(:disabled),
101
+ .vmz-ui-field[data-invalid="true"] .vmz-ui-field__control:focus-visible {
102
+ border-color: var(--vmz-status-danger-foreground);
103
+ }
104
+
60
105
  .vmz-ui-field__control:hover:not(:disabled):not(:focus-visible) {
61
- border-color: var(--vmz-action-primary-background);
106
+ border-color: var(--vmz-action-primary-hover);
62
107
  }
63
108
 
64
109
  .vmz-ui-field__control:focus-visible {
@@ -67,22 +112,29 @@
67
112
  }
68
113
 
69
114
  .vmz-ui-field__control:disabled {
70
- opacity: 0.55;
115
+ opacity: 0.6;
116
+ color: var(--vmz-text-muted);
71
117
  cursor: not-allowed;
72
- background: var(--vmz-surface-mist, #fafafa);
118
+ background: var(--vmz-surface-mist);
119
+ }
120
+
121
+ .vmz-ui-field__control:read-only {
122
+ cursor: default;
123
+ background: var(--vmz-surface-mist);
73
124
  }
74
125
 
75
126
  .vmz-ui-field__description {
76
127
  margin: 0;
77
- color: var(--vmz-text-steel, #8c8c8c);
128
+ color: var(--vmz-text-steel);
78
129
  font-size: 0.875rem;
79
- line-height: 1.4;
130
+ line-height: 1.5715;
80
131
  }
81
132
 
82
133
  .vmz-ui-field__error {
83
134
  margin: 0;
84
135
  color: var(--vmz-status-danger-foreground);
85
136
  font-size: 0.875rem;
137
+ line-height: 1.5715;
86
138
  }
87
139
  </style>
88
140
  <script client>
@@ -97,11 +149,14 @@ export default class Field {
97
149
  public error: string = "";
98
150
  public value: string = "";
99
151
  public type: string = "text";
152
+ public placeholder: string = "";
153
+ public prefix: string = "";
154
+ public suffix: string = "";
100
155
  public min: string = "";
101
156
  public max: string = "";
102
157
  public step: string = "";
103
158
  public disabled: boolean = false;
104
- public invalidAttr: string = "false";
159
+ public readonly: boolean = false;
105
160
  public controlId: string = "vmz-field";
106
161
  public descriptionId: string = "vmz-field-desc";
107
162
  public errorId: string = "vmz-field-err";
@@ -8,10 +8,40 @@
8
8
  display: flex;
9
9
  flex-wrap: wrap;
10
10
  align-items: flex-end;
11
- gap: 0.75rem 1rem;
12
- padding: 0.75rem 0.85rem;
11
+ gap: 0.75rem;
12
+ padding: 1rem;
13
13
  border: 1px solid var(--vmz-line-default);
14
- border-radius: var(--vmz-radius-md, 6px);
14
+ border-radius: var(--vmz-radius-lg, 8px);
15
+ background: var(--vmz-surface-paper);
16
+ }
17
+
18
+ /* Action controls (Field + buttons) keep 32px height and hug the right edge. */
19
+ .vmz-ui-filter-bar .vmz-ui-field__control,
20
+ .vmz-ui-filter-bar .vmz-ui-select__control,
21
+ .vmz-ui-filter-bar .vmz-ui-btn {
22
+ min-height: 2rem;
23
+ }
24
+
25
+ .vmz-ui-filter-bar > :has(.vmz-ui-btn),
26
+ .vmz-ui-filter-bar > button.vmz-ui-btn {
27
+ margin-left: auto;
28
+ }
29
+
30
+ @media (max-width: 40rem) {
31
+ .vmz-ui-filter-bar {
32
+ flex-direction: column;
33
+ align-items: flex-start;
34
+ gap: 0.5rem;
35
+ }
36
+
37
+ .vmz-ui-filter-bar > * {
38
+ width: 100%;
39
+ }
40
+
41
+ .vmz-ui-filter-bar > :has(.vmz-ui-btn),
42
+ .vmz-ui-filter-bar > button.vmz-ui-btn {
43
+ margin-left: 0;
44
+ }
15
45
  }
16
46
  </style>
17
47
  <script client>
@@ -20,6 +50,6 @@
20
50
  * Dense filter/query landmark; parent projects Field + action controls.
21
51
  */
22
52
  export default class FilterBar {
23
- public label: string = "Filters";
53
+ public label: string = "";
24
54
  }
25
55
  </script>
@@ -8,13 +8,15 @@
8
8
  display: flex;
9
9
  align-items: center;
10
10
  justify-content: flex-start;
11
- gap: 0.75rem;
11
+ gap: var(--vmz-space-3);
12
12
  min-width: 0;
13
13
  }
14
+
14
15
  .vmz-ui-flex[data-vertical="true"] {
15
16
  flex-direction: column;
16
17
  align-items: stretch;
17
18
  }
19
+
18
20
  .vmz-ui-flex[data-wrap="true"] {
19
21
  flex-wrap: wrap;
20
22
  }