@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,81 +1,180 @@
1
- <template>
2
- <article data-vmz-ui="prose" class="vmz-ui-prose">
3
- <slot />
4
- </article>
5
- </template>
6
- <style>
7
- .vmz-ui-prose {
8
- display: flex;
9
- flex-direction: column;
10
- gap: 1rem;
11
- max-width: 42rem;
12
- color: var(--vmz-text-ink);
13
- font-family: var(--vmz-font-sans);
14
- line-height: 1.65;
15
- }
16
-
17
- .vmz-ui-prose :where(h1, h2, h3) {
18
- margin: 0;
19
- font-family: var(--vmz-font-display);
20
- font-weight: 700;
21
- letter-spacing: -0.02em;
22
- line-height: 1.2;
23
- color: var(--vmz-text-ink);
24
- scroll-margin-top: 1.25rem;
25
- }
26
-
27
- .vmz-ui-prose :where(h1) {
28
- font-size: clamp(1.85rem, 4vw, 2.35rem);
29
- }
30
-
31
- .vmz-ui-prose :where(h2) {
32
- font-size: 1.35rem;
33
- margin-top: 0.5rem;
34
- }
35
-
36
- .vmz-ui-prose :where(h3) {
37
- font-size: 1.1rem;
38
- }
39
-
40
- .vmz-ui-prose :where(p) {
41
- margin: 0;
42
- color: var(--vmz-text-ink);
43
- }
44
-
45
- .vmz-ui-prose :where(p.lead) {
46
- font-size: 1.05rem;
47
- color: var(--vmz-text-steel);
48
- }
49
-
50
- .vmz-ui-prose :where(ul, ol) {
51
- margin: 0;
52
- padding-left: 1.25rem;
53
- color: var(--vmz-text-ink);
54
- }
55
-
56
- .vmz-ui-prose :where(li + li) {
57
- margin-top: 0.375rem;
58
- }
59
-
60
- .vmz-ui-prose :where(a) {
61
- color: var(--vmz-action-primary-background);
62
- text-underline-offset: 0.15em;
63
- }
64
-
65
- .vmz-ui-prose :where(a:hover) {
66
- color: var(--vmz-action-primary-hover);
67
- }
68
-
69
- .vmz-ui-prose :where(hr) {
70
- border: 0;
71
- border-top: 1px solid var(--vmz-line-default);
72
- margin: 0.5rem 0;
73
- }
74
- </style>
75
- <script client>
76
- /**
77
- * VMZ UI — Prose (Document/Product composition).
78
- * Typography measure for long-form content; values from Style Theme only.
79
- */
80
- export default class Prose {}
81
- </script>
1
+ <template>
2
+ <article data-vmz-ui="prose" class="vmz-ui-prose">
3
+ <slot />
4
+ </article>
5
+ </template>
6
+ <style>
7
+ .vmz-ui-prose {
8
+ max-width: 46rem;
9
+ color: var(--vmz-text-ink);
10
+ font-family: var(--vmz-font-sans);
11
+ font-size: 1rem;
12
+ line-height: 1.625;
13
+ }
14
+ .vmz-ui-prose :where(h1, h2, h3, h4, h5, h6, p, ul, ol, dl, blockquote, pre, figure, table, hr) {
15
+ margin: 0;
16
+ }
17
+ .vmz-ui-prose > * + * {
18
+ margin-top: 1em;
19
+ }
20
+ .vmz-ui-prose :where(div, section, article) > * + * {
21
+ margin-top: 1em;
22
+ }
23
+ .vmz-ui-prose :where(h1, h2, h3, h4, h5, h6) {
24
+ margin: 1.5em 0 0.5em;
25
+ color: var(--vmz-text-ink);
26
+ font-family: var(--vmz-font-display);
27
+ font-weight: 600;
28
+ letter-spacing: -0.01em;
29
+ line-height: 1.25;
30
+ scroll-margin-top: 5rem;
31
+ }
32
+ .vmz-ui-prose :where(h1) {
33
+ font-size: 1.875rem;
34
+ line-height: 1.2;
35
+ }
36
+ .vmz-ui-prose :where(h2) {
37
+ font-size: 1.5rem;
38
+ line-height: 1.33;
39
+ }
40
+ .vmz-ui-prose :where(h3) {
41
+ font-size: 1.25rem;
42
+ line-height: 1.4;
43
+ }
44
+ .vmz-ui-prose :where(h4) {
45
+ font-size: 1.0625rem;
46
+ line-height: 1.47;
47
+ }
48
+ .vmz-ui-prose :where(h5) {
49
+ font-size: 1rem;
50
+ line-height: 1.5;
51
+ }
52
+ .vmz-ui-prose :where(h6) {
53
+ font-size: 0.875rem;
54
+ line-height: 1.57;
55
+ color: var(--vmz-text-tertiary);
56
+ }
57
+ .vmz-ui-prose > :first-child,
58
+ .vmz-ui-prose :where(div, section, article) > :first-child {
59
+ margin-top: 0;
60
+ }
61
+ .vmz-ui-prose > :last-child,
62
+ .vmz-ui-prose :where(div, section, article) > :last-child {
63
+ margin-bottom: 0;
64
+ }
65
+ .vmz-ui-prose :where(p) {
66
+ color: var(--vmz-text-ink);
67
+ }
68
+ .vmz-ui-prose :where(p.lead) {
69
+ color: var(--vmz-text-steel);
70
+ font-size: 1.125rem;
71
+ line-height: 1.6;
72
+ }
73
+ .vmz-ui-prose :where(ul, ol) {
74
+ padding-left: 1.25em;
75
+ color: var(--vmz-text-ink);
76
+ }
77
+ .vmz-ui-prose :where(li + li) {
78
+ margin-top: 0.35em;
79
+ }
80
+ .vmz-ui-prose :where(dl) {
81
+ display: grid;
82
+ gap: 0.35em;
83
+ }
84
+ .vmz-ui-prose :where(dt) {
85
+ font-weight: 600;
86
+ }
87
+ .vmz-ui-prose :where(dd) {
88
+ margin: 0;
89
+ padding-left: 1.25em;
90
+ color: var(--vmz-text-steel);
91
+ }
92
+ .vmz-ui-prose :where(a) {
93
+ color: var(--vmz-action-primary-background);
94
+ text-decoration: underline;
95
+ text-decoration-thickness: 1px;
96
+ text-underline-offset: 0.15em;
97
+ transition: color var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1));
98
+ }
99
+ .vmz-ui-prose :where(a:hover) {
100
+ color: var(--vmz-action-primary-hover);
101
+ }
102
+ .vmz-ui-prose :where(a:focus-visible) {
103
+ border-radius: var(--vmz-radius-xs, 2px);
104
+ box-shadow: 0 0 0 3px var(--vmz-focus-ring);
105
+ outline: none;
106
+ }
107
+ .vmz-ui-prose :where(strong) {
108
+ font-weight: 600;
109
+ }
110
+ .vmz-ui-prose :where(small) {
111
+ color: var(--vmz-text-tertiary);
112
+ font-size: 0.875em;
113
+ }
114
+ .vmz-ui-prose :where(blockquote) {
115
+ padding: 0.25em 0 0.25em 1em;
116
+ border-left: 3px solid var(--vmz-line-default);
117
+ color: var(--vmz-text-steel);
118
+ }
119
+ .vmz-ui-prose :where(blockquote) :where(p) {
120
+ color: inherit;
121
+ }
122
+ .vmz-ui-prose :where(hr) {
123
+ margin: 2em 0;
124
+ border: 0;
125
+ border-top: 1px solid var(--vmz-line-default);
126
+ }
127
+ .vmz-ui-prose :where(img, video) {
128
+ max-width: 100%;
129
+ height: auto;
130
+ border-radius: var(--vmz-radius-md, 6px);
131
+ }
132
+ .vmz-ui-prose :where(pre:not(.vmz-ui-code-block__pre)) {
133
+ padding: var(--vmz-space-4, 1rem);
134
+ border-radius: var(--vmz-radius-lg, 8px);
135
+ background: var(--vmz-surface-code);
136
+ color: var(--vmz-text-on-brand);
137
+ font-family: var(--vmz-font-mono);
138
+ font-size: 0.875rem;
139
+ line-height: 1.5714;
140
+ overflow-x: auto;
141
+ }
142
+ .vmz-ui-prose :where(pre:not(.vmz-ui-code-block__pre)) :where(code) {
143
+ padding: 0;
144
+ background: transparent;
145
+ color: inherit;
146
+ font-size: inherit;
147
+ }
148
+ .vmz-ui-prose :where(p, li, td, th, blockquote, dd) :where(code, kbd, samp) {
149
+ padding: 0.15em 0.35em;
150
+ border-radius: var(--vmz-radius-xs, 2px);
151
+ background: var(--vmz-surface-mist);
152
+ color: var(--vmz-text-ink);
153
+ font-family: var(--vmz-font-mono);
154
+ font-size: 0.875em;
155
+ }
156
+ .vmz-ui-prose :where(table) {
157
+ width: 100%;
158
+ border-collapse: collapse;
159
+ font-size: 0.9375rem;
160
+ line-height: 1.5;
161
+ }
162
+ .vmz-ui-prose :where(th, td) {
163
+ padding: 0.625em 0.75em;
164
+ border-bottom: 1px solid var(--vmz-line-subtle);
165
+ text-align: left;
166
+ vertical-align: top;
167
+ }
168
+ .vmz-ui-prose :where(thead th) {
169
+ color: var(--vmz-text-tertiary);
170
+ font-weight: 600;
171
+ border-bottom-color: var(--vmz-line-default);
172
+ }
173
+ </style>
174
+ <script client>
175
+ /**
176
+ * VMZ UI — Prose (Document/Product composition).
177
+ * Typography measure for long-form content; values from Style Theme only.
178
+ */
179
+ export default class Prose {}
180
+ </script>
@@ -5,22 +5,44 @@
5
5
  </template>
6
6
  <style>
7
7
  .vmz-ui-query-form {
8
- display: flex;
9
- flex-wrap: wrap;
10
- align-items: flex-end;
11
- gap: var(--vmz-density-compact-gap);
12
- padding: var(--vmz-density-compact-padding-y) var(--vmz-density-compact-padding-x);
8
+ display: grid;
9
+ grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
10
+ align-items: end;
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
+ .vmz-ui-query-form .vmz-ui-field__control,
19
+ .vmz-ui-query-form .vmz-ui-select__control,
20
+ .vmz-ui-query-form .vmz-ui-btn {
21
+ min-height: 2rem;
22
+ }
23
+
24
+ .vmz-ui-query-form > :has(.vmz-ui-btn),
25
+ .vmz-ui-query-form > button.vmz-ui-btn {
26
+ justify-self: end;
27
+ }
28
+
29
+ .vmz-ui-query-form .vmz-ui-field {
30
+ min-width: 0;
31
+ }
32
+
33
+ @media (max-width: 640px) {
34
+ .vmz-ui-query-form {
35
+ grid-template-columns: 1fr;
36
+ }
15
37
  }
16
38
  </style>
17
39
  <script client>
18
40
  /**
19
- * VMZ UI — QueryForm (UI5 Console Surface).
20
- * Dense multi-field query landmark; spacing from density.compact.* tokens.
41
+ * VMZ UI — QueryForm (Console composition).
42
+ * Dense multi-field query landmark; single column on narrow screens.
21
43
  */
22
44
  export default class QueryForm {
23
- public label: string = "Query";
45
+ public label: string = "";
24
46
  public onSubmit: (() => void) | null = null;
25
47
  onSubmitGuard(ev) {
26
48
  if (ev && typeof ev.preventDefault === "function") ev.preventDefault();
@@ -1,124 +1,204 @@
1
- <template>
2
- <fieldset data-vmz-ui="radio-group" :data-invalid="invalidAttr" :aria-describedby="describedBy" :disabled="disabled" class="vmz-ui-radio-group">
3
- <legend class="vmz-ui-radio-group__legend">
4
- {{ label }}
5
- </legend>
6
- <div role="radiogroup" :aria-label="label" @click="onListClick" class="vmz-ui-radio-group__list">
7
- <label v-for="item in items" :key="item.value" :for="item.id" class="vmz-ui-radio-group__option">
8
- <input :id="item.id" type="radio" :name="name" :value="item.value" :checked="value === item.value" :disabled="disabled" :data-vmz-radio="item.value" class="vmz-ui-radio-group__input" />
9
- <span class="vmz-ui-radio-group__text">
10
- {{ item.label }}
11
- </span>
12
- </label>
13
- </div>
14
- <p v-if="description" :id="descriptionId" class="vmz-ui-radio-group__description">
15
- {{ description }}
16
- </p>
17
- <p v-if="error" :id="errorId" role="alert" class="vmz-ui-radio-group__error">
18
- {{ error }}
19
- </p>
20
- </fieldset>
21
- </template>
22
- <style>
23
- .vmz-ui-radio-group {
24
- display: flex;
25
- flex-direction: column;
26
- gap: 0.5rem;
27
- max-width: 28rem;
28
- margin: 0;
29
- padding: 0;
30
- border: 0;
31
- min-width: 0;
32
- }
33
-
34
- .vmz-ui-radio-group__legend {
35
- padding: 0;
36
- font: inherit;
37
- font-weight: 600;
38
- }
39
-
40
- .vmz-ui-radio-group__list {
41
- display: flex;
42
- flex-direction: column;
43
- gap: 0.375rem;
44
- }
45
-
46
- .vmz-ui-radio-group__option {
47
- display: inline-flex;
48
- align-items: center;
49
- gap: 0.5rem;
50
- font: inherit;
51
- cursor: pointer;
52
- }
53
-
54
- .vmz-ui-radio-group__input {
55
- width: 1rem;
56
- height: 1rem;
57
- accent-color: var(--vmz-action-primary-background);
58
- outline: none;
59
- }
60
-
61
- .vmz-ui-radio-group__input:focus-visible {
62
- box-shadow: 0 0 0 3px var(--vmz-focus-ring);
63
- }
64
-
65
- .vmz-ui-radio-group__input:disabled {
66
- opacity: 0.55;
67
- cursor: not-allowed;
68
- }
69
-
70
- .vmz-ui-radio-group__text {
71
- font-weight: 600;
72
- }
73
-
74
- .vmz-ui-radio-group__description {
75
- margin: 0;
76
- opacity: 0.75;
77
- font-size: 0.9em;
78
- }
79
-
80
- .vmz-ui-radio-group__error {
81
- margin: 0;
82
- color: var(--vmz-status-danger-foreground);
83
- font-size: 0.9em;
84
- }
85
-
86
- .vmz-ui-radio-group[data-invalid="true"] .vmz-ui-radio-group__legend {
87
- color: var(--vmz-status-danger-foreground);
88
- }
89
- </style>
90
- <script client>
91
- /**
92
- * VMZ UI — RadioGroup.
93
- * Parent owns selected value; click path updates via onChange (live prop remains authoritative).
94
- */
95
- export default class RadioGroup {
96
- public label: string = "";
97
- public description: string = "";
98
- public error: string = "";
99
- public name: string = "vmz-radio";
100
- public value: string = "";
101
- public items: Array<{
102
- id: string;
103
- value: string;
104
- label: string;
105
- }> = [];
106
- public disabled: boolean = false;
107
- public invalidAttr: string = "false";
108
- public descriptionId: string = "vmz-radio-desc";
109
- public errorId: string = "vmz-radio-err";
110
- public describedBy: string = "";
111
- public onChange: ((value: string) => void) | null = null;
112
- onListClick(ev) {
113
- if (this.disabled) return;
114
- const t = ev && ev.target;
115
- if (!t || typeof t.closest !== "function") return;
116
- const input = t.closest("input[data-vmz-radio]");
117
- if (!input) return;
118
- const next = input.getAttribute("data-vmz-radio") || input.value || "";
119
- if (!next || next === this.value) return;
120
- this.emit("change", next);
121
- if (typeof this.onChange === "function") this.onChange(next);
122
- }
123
- }
124
- </script>
1
+ <template>
2
+ <fieldset data-vmz-ui="radio-group" :data-invalid="invalidAttr" :data-disabled="disabled ? 'true' : 'false'" :aria-describedby="describedBy" :disabled="disabled" class="vmz-ui-radio-group">
3
+ <legend class="vmz-ui-radio-group__legend">
4
+ {{ label }}
5
+ </legend>
6
+ <div role="radiogroup" :aria-label="label" @click="onListClick" class="vmz-ui-radio-group__list">
7
+ <label v-for="item in items" :key="item.value" :for="item.id" class="vmz-ui-radio-group__option">
8
+ <span class="vmz-ui-radio-group__box">
9
+ <input :id="item.id" type="radio" :name="name" :value="item.value" :checked="value === item.value" :disabled="disabled" :data-vmz-radio="item.value" class="vmz-ui-radio-group__input" />
10
+ <span aria-hidden="true" class="vmz-ui-radio-group__dot" />
11
+ </span>
12
+ <span class="vmz-ui-radio-group__text">
13
+ {{ item.label }}
14
+ </span>
15
+ </label>
16
+ </div>
17
+ <p v-if="description" :id="descriptionId" class="vmz-ui-radio-group__description">
18
+ {{ description }}
19
+ </p>
20
+ <p v-if="error" :id="errorId" role="alert" class="vmz-ui-radio-group__error">
21
+ {{ error }}
22
+ </p>
23
+ </fieldset>
24
+ </template>
25
+ <style>
26
+ .vmz-ui-radio-group {
27
+ display: flex;
28
+ flex-direction: column;
29
+ gap: var(--vmz-space-2);
30
+ width: 100%;
31
+ max-width: 28rem;
32
+ margin: 0;
33
+ padding: 0;
34
+ border: 0;
35
+ min-width: 0;
36
+ }
37
+
38
+ .vmz-ui-radio-group__legend {
39
+ padding: 0;
40
+ font: inherit;
41
+ font-weight: 500;
42
+ font-size: 0.875rem;
43
+ line-height: 1.5715;
44
+ color: var(--vmz-text-ink);
45
+ }
46
+
47
+ .vmz-ui-radio-group__list {
48
+ display: flex;
49
+ flex-direction: column;
50
+ gap: var(--vmz-space-2);
51
+ }
52
+
53
+ .vmz-ui-radio-group__option {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ gap: var(--vmz-space-2);
57
+ font: inherit;
58
+ font-size: 0.875rem;
59
+ line-height: 1.5715;
60
+ color: var(--vmz-text-ink);
61
+ cursor: pointer;
62
+ }
63
+
64
+ .vmz-ui-radio-group__box {
65
+ position: relative;
66
+ display: inline-flex;
67
+ flex: 0 0 auto;
68
+ width: 1rem;
69
+ height: 1rem;
70
+ }
71
+
72
+ .vmz-ui-radio-group__input {
73
+ position: absolute;
74
+ inset: 0;
75
+ z-index: 1;
76
+ box-sizing: border-box;
77
+ width: 1rem;
78
+ height: 1rem;
79
+ margin: 0;
80
+ appearance: none;
81
+ border: 1px solid var(--vmz-line-default);
82
+ border-radius: var(--vmz-radius-pill);
83
+ background: var(--vmz-surface-paper);
84
+ outline: none;
85
+ cursor: pointer;
86
+ transition:
87
+ background-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
88
+ border-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
89
+ box-shadow var(--vmz-motion-control-duration) var(--vmz-motion-control-easing);
90
+ }
91
+
92
+ .vmz-ui-radio-group__input:hover:not(:disabled):not(:checked) {
93
+ border-color: var(--vmz-action-primary-hover);
94
+ }
95
+
96
+ .vmz-ui-radio-group__input:checked {
97
+ border-color: var(--vmz-action-primary-background);
98
+ background: var(--vmz-action-primary-background);
99
+ }
100
+
101
+ .vmz-ui-radio-group__input:checked:hover:not(:disabled) {
102
+ border-color: var(--vmz-action-primary-hover);
103
+ background: var(--vmz-action-primary-hover);
104
+ }
105
+
106
+ .vmz-ui-radio-group__input:focus-visible {
107
+ box-shadow: 0 0 0 3px var(--vmz-focus-ring);
108
+ }
109
+
110
+ .vmz-ui-radio-group__input:disabled {
111
+ cursor: not-allowed;
112
+ }
113
+
114
+ .vmz-ui-radio-group__dot {
115
+ position: absolute;
116
+ inset: 0;
117
+ z-index: 2;
118
+ width: 0.375rem;
119
+ height: 0.375rem;
120
+ margin: auto;
121
+ border-radius: var(--vmz-radius-pill);
122
+ background: var(--vmz-action-primary-foreground);
123
+ opacity: 0;
124
+ pointer-events: none;
125
+ transition: opacity var(--vmz-motion-control-duration) var(--vmz-motion-control-easing);
126
+ }
127
+
128
+ .vmz-ui-radio-group__input:checked + .vmz-ui-radio-group__dot {
129
+ opacity: 1;
130
+ }
131
+
132
+ .vmz-ui-radio-group[data-disabled="true"] {
133
+ opacity: 0.55;
134
+ }
135
+
136
+ .vmz-ui-radio-group[data-disabled="true"] .vmz-ui-radio-group__option,
137
+ .vmz-ui-radio-group[data-disabled="true"] .vmz-ui-radio-group__input {
138
+ cursor: not-allowed;
139
+ }
140
+
141
+ .vmz-ui-radio-group__text {
142
+ font-weight: 400;
143
+ }
144
+
145
+ .vmz-ui-radio-group__description {
146
+ margin: 0;
147
+ color: var(--vmz-text-steel);
148
+ font-size: 0.875rem;
149
+ line-height: 1.5715;
150
+ }
151
+
152
+ .vmz-ui-radio-group__error {
153
+ margin: 0;
154
+ color: var(--vmz-status-danger-foreground);
155
+ font-size: 0.875rem;
156
+ line-height: 1.5715;
157
+ }
158
+
159
+ .vmz-ui-radio-group[data-invalid="true"] .vmz-ui-radio-group__legend {
160
+ color: var(--vmz-status-danger-foreground);
161
+ }
162
+
163
+ @media (prefers-reduced-motion: reduce) {
164
+ .vmz-ui-radio-group__input,
165
+ .vmz-ui-radio-group__dot {
166
+ transition: none;
167
+ }
168
+ }
169
+ </style>
170
+ <script client>
171
+ /**
172
+ * VMZ UI — RadioGroup.
173
+ * Parent owns selected value; click path updates via onChange (live prop remains authoritative).
174
+ */
175
+ export default class RadioGroup {
176
+ public label: string = "";
177
+ public description: string = "";
178
+ public error: string = "";
179
+ public name: string = "vmz-radio";
180
+ public value: string = "";
181
+ public items: Array<{
182
+ id: string;
183
+ value: string;
184
+ label: string;
185
+ }> = [];
186
+ public disabled: boolean = false;
187
+ public invalidAttr: string = "false";
188
+ public descriptionId: string = "vmz-radio-desc";
189
+ public errorId: string = "vmz-radio-err";
190
+ public describedBy: string = "";
191
+ public onChange: ((value: string) => void) | null = null;
192
+ onListClick(ev) {
193
+ if (this.disabled) return;
194
+ const t = ev && ev.target;
195
+ if (!t || typeof t.closest !== "function") return;
196
+ const input = t.closest("input[data-vmz-radio]");
197
+ if (!input) return;
198
+ const next = input.getAttribute("data-vmz-radio") || input.value || "";
199
+ if (!next || next === this.value) return;
200
+ this.emit("change", next);
201
+ if (typeof this.onChange === "function") this.onChange(next);
202
+ }
203
+ }
204
+ </script>