@vmz/ui 0.3.5 → 0.3.6

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 (43) hide show
  1. package/README.md +51 -51
  2. package/package.json +2 -2
  3. package/presets/web-surface.v0.json +19 -7
  4. package/src/components/Accordion.vmz +94 -94
  5. package/src/components/AppShell.vmz +77 -33
  6. package/src/components/Autocomplete.vmz +154 -154
  7. package/src/components/Badge.vmz +70 -70
  8. package/src/components/Breadcrumb.vmz +72 -72
  9. package/src/components/BulkActions.vmz +51 -51
  10. package/src/components/Button.vmz +211 -192
  11. package/src/components/Callout.vmz +77 -77
  12. package/src/components/Card.vmz +120 -106
  13. package/src/components/Checkbox.vmz +55 -55
  14. package/src/components/CodeBlock.vmz +77 -18
  15. package/src/components/ConsoleShell.vmz +251 -251
  16. package/src/components/DataTable.vmz +194 -194
  17. package/src/components/DatePicker.vmz +674 -674
  18. package/src/components/Dialog.vmz +10 -7
  19. package/src/components/Drawer.vmz +427 -427
  20. package/src/components/Dropdown.vmz +135 -16
  21. package/src/components/Empty.vmz +54 -54
  22. package/src/components/Field.vmz +115 -115
  23. package/src/components/FormItem.vmz +79 -79
  24. package/src/components/Link.vmz +34 -4
  25. package/src/components/List.vmz +102 -102
  26. package/src/components/Menu.vmz +243 -243
  27. package/src/components/Pagination.vmz +69 -69
  28. package/src/components/Popover.vmz +223 -223
  29. package/src/components/Prose.vmz +81 -81
  30. package/src/components/RadioGroup.vmz +124 -124
  31. package/src/components/Segmented.vmz +51 -51
  32. package/src/components/Select.vmz +533 -533
  33. package/src/components/Steps.vmz +103 -103
  34. package/src/components/Switch.vmz +98 -98
  35. package/src/components/Table.vmz +165 -133
  36. package/src/components/Tabs.vmz +110 -110
  37. package/src/components/Tag.vmz +43 -40
  38. package/src/components/TextArea.vmz +118 -118
  39. package/src/components/Timeline.vmz +76 -76
  40. package/src/components/Toc.vmz +74 -74
  41. package/src/components/Tooltip.vmz +58 -58
  42. package/src/components/Tree.vmz +191 -191
  43. package/src/components/Upload.vmz +1289 -1289
@@ -1,77 +1,77 @@
1
- <template>
2
- <aside data-vmz-ui="callout" :data-tone="tone" :role="roleName" class="vmz-ui-callout">
3
- <p v-if="title" class="vmz-ui-callout__title">
4
- {{ title }}
5
- </p>
6
- <p v-if="description" class="vmz-ui-callout__description">
7
- {{ description }}
8
- </p>
9
- <div class="vmz-ui-callout__body">
10
- <slot />
11
- </div>
12
- </aside>
13
- </template>
14
- <style>
15
- .vmz-ui-callout {
16
- display: flex;
17
- flex-direction: column;
18
- gap: 0.45rem;
19
- padding: var(--vmz-density-control-padding-y) var(--vmz-density-control-padding-x);
20
- border: 1px solid var(--vmz-status-info-accent);
21
- border-left-width: 1px;
22
- border-radius: var(--vmz-radius-md, 6px);
23
- background: color-mix(in srgb, var(--vmz-status-info-accent) 10%, var(--vmz-surface-paper));
24
- color: var(--vmz-status-info-foreground);
25
- }
26
-
27
- .vmz-ui-callout[data-tone="info"] {
28
- border-color: var(--vmz-status-info-accent);
29
- background: color-mix(in srgb, var(--vmz-status-info-accent) 10%, var(--vmz-surface-paper));
30
- color: var(--vmz-status-info-foreground);
31
- }
32
-
33
- .vmz-ui-callout[data-tone="success"] {
34
- border-color: var(--vmz-status-success-accent);
35
- background: color-mix(in srgb, var(--vmz-status-success-accent) 10%, var(--vmz-surface-paper));
36
- color: var(--vmz-status-success-foreground);
37
- }
38
-
39
- .vmz-ui-callout[data-tone="warning"] {
40
- border-color: var(--vmz-status-warning-accent);
41
- background: color-mix(in srgb, var(--vmz-status-warning-accent) 10%, var(--vmz-surface-paper));
42
- color: var(--vmz-status-warning-foreground);
43
- }
44
-
45
- .vmz-ui-callout[data-tone="danger"] {
46
- border-color: var(--vmz-status-danger-accent);
47
- background: color-mix(in srgb, var(--vmz-status-danger-accent) 10%, var(--vmz-surface-paper));
48
- color: var(--vmz-status-danger-foreground);
49
- }
50
-
51
- .vmz-ui-callout__title {
52
- margin: 0;
53
- font-family: var(--vmz-font-display);
54
- font-weight: 700;
55
- }
56
-
57
- .vmz-ui-callout__description {
58
- margin: 0;
59
- line-height: 1.5;
60
- }
61
-
62
- .vmz-ui-callout__body:empty {
63
- display: none;
64
- }
65
- </style>
66
- <script client>
67
- /**
68
- * VMZ UI — Callout (Document/Product composition).
69
- * Inline guidance block for prose; shares status.* tones with Alert.
70
- */
71
- export default class Callout {
72
- public title: string = "";
73
- public description: string = "";
74
- public tone: string = "info";
75
- public roleName: string = "note";
76
- }
77
- </script>
1
+ <template>
2
+ <aside data-vmz-ui="callout" :data-tone="tone" :role="roleName" class="vmz-ui-callout">
3
+ <p v-if="title" class="vmz-ui-callout__title">
4
+ {{ title }}
5
+ </p>
6
+ <p v-if="description" class="vmz-ui-callout__description">
7
+ {{ description }}
8
+ </p>
9
+ <div class="vmz-ui-callout__body">
10
+ <slot />
11
+ </div>
12
+ </aside>
13
+ </template>
14
+ <style>
15
+ .vmz-ui-callout {
16
+ display: flex;
17
+ flex-direction: column;
18
+ gap: 0.5rem;
19
+ padding: var(--vmz-density-control-padding-y) var(--vmz-density-control-padding-x);
20
+ border: 1px solid var(--vmz-status-info-accent);
21
+ border-left-width: 1px;
22
+ border-radius: var(--vmz-radius-md, 6px);
23
+ background: color-mix(in srgb, var(--vmz-status-info-accent) 10%, var(--vmz-surface-paper));
24
+ color: var(--vmz-status-info-foreground);
25
+ }
26
+
27
+ .vmz-ui-callout[data-tone="info"] {
28
+ border-color: var(--vmz-status-info-accent);
29
+ background: color-mix(in srgb, var(--vmz-status-info-accent) 10%, var(--vmz-surface-paper));
30
+ color: var(--vmz-status-info-foreground);
31
+ }
32
+
33
+ .vmz-ui-callout[data-tone="success"] {
34
+ border-color: var(--vmz-status-success-accent);
35
+ background: color-mix(in srgb, var(--vmz-status-success-accent) 10%, var(--vmz-surface-paper));
36
+ color: var(--vmz-status-success-foreground);
37
+ }
38
+
39
+ .vmz-ui-callout[data-tone="warning"] {
40
+ border-color: var(--vmz-status-warning-accent);
41
+ background: color-mix(in srgb, var(--vmz-status-warning-accent) 10%, var(--vmz-surface-paper));
42
+ color: var(--vmz-status-warning-foreground);
43
+ }
44
+
45
+ .vmz-ui-callout[data-tone="danger"] {
46
+ border-color: var(--vmz-status-danger-accent);
47
+ background: color-mix(in srgb, var(--vmz-status-danger-accent) 10%, var(--vmz-surface-paper));
48
+ color: var(--vmz-status-danger-foreground);
49
+ }
50
+
51
+ .vmz-ui-callout__title {
52
+ margin: 0;
53
+ font-family: var(--vmz-font-display);
54
+ font-weight: 700;
55
+ }
56
+
57
+ .vmz-ui-callout__description {
58
+ margin: 0;
59
+ line-height: 1.5;
60
+ }
61
+
62
+ .vmz-ui-callout__body:empty {
63
+ display: none;
64
+ }
65
+ </style>
66
+ <script client>
67
+ /**
68
+ * VMZ UI — Callout (Document/Product composition).
69
+ * Inline guidance block for prose; shares status.* tones with Alert.
70
+ */
71
+ export default class Callout {
72
+ public title: string = "";
73
+ public description: string = "";
74
+ public tone: string = "info";
75
+ public roleName: string = "note";
76
+ }
77
+ </script>
@@ -1,106 +1,120 @@
1
- <template>
2
- <section data-vmz-ui="card" :data-size="size" :data-bordered="bordered ? 'true' : 'false'" :data-hoverable="hoverable ? 'true' : 'false'" class="vmz-ui-card">
3
- <header v-if="title" class="vmz-ui-card__header">
4
- <h2 class="vmz-ui-card__title">
5
- {{ title }}
6
- </h2>
7
- <p v-if="description" class="vmz-ui-card__description">
8
- {{ description }}
9
- </p>
10
- </header>
11
- <div class="vmz-ui-card__body">
12
- <slot />
13
- </div>
14
- </section>
15
- </template>
16
- <style>
17
- .vmz-ui-card {
18
- display: flex;
19
- flex-direction: column;
20
- gap: 0.75rem;
21
- padding: 1rem 1.25rem;
22
- border: 1px solid var(--vmz-line-subtle, var(--vmz-line-default));
23
- border-radius: var(--vmz-radius-lg, 8px);
24
- background: var(--vmz-surface-paper);
25
- color: var(--vmz-text-ink);
26
- box-shadow: var(--vmz-shadow-card, none);
27
- }
28
- .vmz-ui-card[data-bordered="false"] {
29
- border-color: transparent;
30
- }
31
- .vmz-ui-card[data-hoverable="true"] {
32
- transition:
33
- border-color var(--vmz-motion-control-duration, 0.16s),
34
- box-shadow var(--vmz-motion-control-duration, 0.16s);
35
- }
36
- .vmz-ui-card[data-hoverable="true"]:hover {
37
- box-shadow: var(
38
- --vmz-shadow-card-hover,
39
- 0 1px 2px -2px rgba(0, 0, 0, 0.08),
40
- 0 3px 6px 0 rgba(0, 0, 0, 0.06)
41
- );
42
- }
43
- .vmz-ui-card[data-size="small"] {
44
- gap: 0.5rem;
45
- padding: 0.75rem 1rem;
46
- }
47
-
48
- :where([data-density="compact"]) .vmz-ui-card {
49
- gap: var(--vmz-density-compact-gap);
50
- padding: var(--vmz-density-compact-padding-y) var(--vmz-density-compact-padding-x);
51
- }
52
-
53
- :where([data-density="dense"]) .vmz-ui-card {
54
- gap: var(--vmz-density-dense-gap);
55
- padding: var(--vmz-density-dense-padding-y) var(--vmz-density-dense-padding-x);
56
- }
57
-
58
- .vmz-ui-card__header {
59
- display: flex;
60
- flex-direction: column;
61
- gap: 0.35rem;
62
- }
63
-
64
- .vmz-ui-card__title {
65
- margin: 0;
66
- font-family: var(--vmz-font-sans, inherit);
67
- font-size: 1rem;
68
- font-weight: 600;
69
- color: var(--vmz-text-ink);
70
- }
71
-
72
- .vmz-ui-card__description {
73
- margin: 0;
74
- color: var(--vmz-text-steel);
75
- line-height: 1.45;
76
- }
77
-
78
- .vmz-ui-card__body {
79
- display: flex;
80
- flex-direction: column;
81
- gap: var(--vmz-density-control-gap);
82
- }
83
-
84
- /* Direct host default is display:contents (runtime); Badge-only CSS special-case removed. */
85
-
86
- :where([data-density="compact"]) .vmz-ui-card__body {
87
- gap: var(--vmz-density-compact-gap);
88
- }
89
-
90
- :where([data-density="dense"]) .vmz-ui-card__body {
91
- gap: var(--vmz-density-dense-gap);
92
- }
93
- </style>
94
- <script client>
95
- /**
96
- * VMZ UI — Card (Commercial Surface).
97
- * Surface container; spacing from density.*; chrome from surface/line/text.
98
- */
99
- export default class Card {
100
- public title: string = "";
101
- public description: string = "";
102
- public size: string = "middle";
103
- public bordered: boolean = true;
104
- public hoverable: boolean = false;
105
- }
106
- </script>
1
+ <template>
2
+ <section data-vmz-ui="card" :data-size="size" :data-bordered="bordered ? 'true' : 'false'" :data-hoverable="hoverable ? 'true' : 'false'" class="vmz-ui-card">
3
+ <header v-if="title" class="vmz-ui-card__header">
4
+ <h2 class="vmz-ui-card__title">
5
+ {{ title }}
6
+ </h2>
7
+ <p v-if="description" class="vmz-ui-card__description">
8
+ {{ description }}
9
+ </p>
10
+ </header>
11
+ <div class="vmz-ui-card__body">
12
+ <slot />
13
+ </div>
14
+ </section>
15
+ </template>
16
+ <style>
17
+ .vmz-ui-card {
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: 0.75rem;
21
+ padding: 1.25rem 1.5rem;
22
+ border: 1px solid var(--vmz-line-subtle, var(--vmz-line-default));
23
+ border-radius: var(--vmz-radius-lg, 8px);
24
+ background: var(--vmz-surface-paper);
25
+ color: var(--vmz-text-ink);
26
+ box-shadow: var(--vmz-shadow-card, none);
27
+ }
28
+ .vmz-ui-card[data-bordered="false"] {
29
+ border-color: transparent;
30
+ }
31
+ .vmz-ui-card[data-hoverable="true"] {
32
+ transition:
33
+ border-color var(--vmz-motion-emphasis-duration, 240ms) var(--vmz-motion-emphasis-easing, cubic-bezier(0.16, 1, 0.3, 1)),
34
+ box-shadow var(--vmz-motion-emphasis-duration, 240ms) var(--vmz-motion-emphasis-easing, cubic-bezier(0.16, 1, 0.3, 1)),
35
+ transform var(--vmz-motion-emphasis-duration, 240ms) var(--vmz-motion-emphasis-easing, cubic-bezier(0.16, 1, 0.3, 1));
36
+ }
37
+ .vmz-ui-card[data-hoverable="true"]:hover {
38
+ border-color: var(--vmz-line-default);
39
+ box-shadow: var(
40
+ --vmz-shadow-card-hover,
41
+ 0 1px 2px -2px rgba(0, 0, 0, 0.08),
42
+ 0 3px 6px 0 rgba(0, 0, 0, 0.06)
43
+ );
44
+ transform: translateY(-1px);
45
+ }
46
+ .vmz-ui-card[data-hoverable="true"]:focus-within {
47
+ border-color: var(--vmz-action-primary-background);
48
+ }
49
+ @media (prefers-reduced-motion: reduce) {
50
+ .vmz-ui-card[data-hoverable="true"] {
51
+ transition: none;
52
+ }
53
+ .vmz-ui-card[data-hoverable="true"]:hover {
54
+ transform: none;
55
+ }
56
+ }
57
+ .vmz-ui-card[data-size="small"] {
58
+ gap: 0.5rem;
59
+ padding: 0.875rem 1rem;
60
+ }
61
+
62
+ :where([data-density="compact"]) .vmz-ui-card {
63
+ gap: var(--vmz-density-compact-gap);
64
+ padding: var(--vmz-density-compact-padding-y) var(--vmz-density-compact-padding-x);
65
+ }
66
+
67
+ :where([data-density="dense"]) .vmz-ui-card {
68
+ gap: var(--vmz-density-dense-gap);
69
+ padding: var(--vmz-density-dense-padding-y) var(--vmz-density-dense-padding-x);
70
+ }
71
+
72
+ .vmz-ui-card__header {
73
+ display: flex;
74
+ flex-direction: column;
75
+ gap: 0.375rem;
76
+ }
77
+
78
+ .vmz-ui-card__title {
79
+ margin: 0;
80
+ font-family: var(--vmz-font-sans, inherit);
81
+ font-size: 1rem;
82
+ font-weight: 600;
83
+ color: var(--vmz-text-ink);
84
+ }
85
+
86
+ .vmz-ui-card__description {
87
+ margin: 0;
88
+ color: var(--vmz-text-steel);
89
+ line-height: 1.45;
90
+ }
91
+
92
+ .vmz-ui-card__body {
93
+ display: flex;
94
+ flex-direction: column;
95
+ gap: var(--vmz-density-control-gap);
96
+ }
97
+
98
+ /* Direct host default is display:contents (runtime); Badge-only CSS special-case removed. */
99
+
100
+ :where([data-density="compact"]) .vmz-ui-card__body {
101
+ gap: var(--vmz-density-compact-gap);
102
+ }
103
+
104
+ :where([data-density="dense"]) .vmz-ui-card__body {
105
+ gap: var(--vmz-density-dense-gap);
106
+ }
107
+ </style>
108
+ <script client>
109
+ /**
110
+ * VMZ UI — Card (Commercial Surface).
111
+ * Surface container; spacing from density.*; chrome from surface/line/text.
112
+ */
113
+ export default class Card {
114
+ public title: string = "";
115
+ public description: string = "";
116
+ public size: string = "middle";
117
+ public bordered: boolean = true;
118
+ public hoverable: boolean = false;
119
+ }
120
+ </script>
@@ -1,55 +1,55 @@
1
- <template>
2
- <label data-vmz-ui="checkbox" :for="controlId" class="vmz-ui-checkbox">
3
- <input :id="controlId" type="checkbox" :checked="checked" :disabled="disabled" :aria-describedby="describedBy" @change="onToggle" class="vmz-ui-checkbox__input" />
4
- <span class="vmz-ui-checkbox__label">
5
- {{ label }}
6
- </span>
7
- </label>
8
- </template>
9
- <style>
10
- .vmz-ui-checkbox {
11
- display: inline-flex;
12
- align-items: center;
13
- gap: 0.45rem;
14
- font: inherit;
15
- cursor: pointer;
16
- }
17
-
18
- .vmz-ui-checkbox__input {
19
- width: 1rem;
20
- height: 1rem;
21
- accent-color: var(--vmz-action-primary-background);
22
- outline: none;
23
- }
24
-
25
- .vmz-ui-checkbox__input:focus-visible {
26
- box-shadow: 0 0 0 2px var(--vmz-focus-ring);
27
- }
28
-
29
- .vmz-ui-checkbox__input:disabled {
30
- opacity: 0.55;
31
- cursor: not-allowed;
32
- }
33
-
34
- .vmz-ui-checkbox__label {
35
- font-weight: 600;
36
- }
37
- </style>
38
- <script client>
39
- /**
40
- * VMZ UI — Checkbox (UI2).
41
- * Label association via wrapping label + for/id; parent owns checked (no parallel form store).
42
- * Native change reports the committed checked state to the parent.
43
- */
44
- export default class Checkbox {
45
- public label: string = "";
46
- public checked: boolean = false;
47
- public disabled: boolean = false;
48
- public controlId: string = "vmz-checkbox";
49
- public describedBy: string = "";
50
- onToggle(ev) {
51
- if (this.disabled) return;
52
- this.emit("change", !!ev?.target?.checked);
53
- }
54
- }
55
- </script>
1
+ <template>
2
+ <label data-vmz-ui="checkbox" :for="controlId" class="vmz-ui-checkbox">
3
+ <input :id="controlId" type="checkbox" :checked="checked" :disabled="disabled" :aria-describedby="describedBy" @change="onToggle" class="vmz-ui-checkbox__input" />
4
+ <span class="vmz-ui-checkbox__label">
5
+ {{ label }}
6
+ </span>
7
+ </label>
8
+ </template>
9
+ <style>
10
+ .vmz-ui-checkbox {
11
+ display: inline-flex;
12
+ align-items: center;
13
+ gap: 0.5rem;
14
+ font: inherit;
15
+ cursor: pointer;
16
+ }
17
+
18
+ .vmz-ui-checkbox__input {
19
+ width: 1rem;
20
+ height: 1rem;
21
+ accent-color: var(--vmz-action-primary-background);
22
+ outline: none;
23
+ }
24
+
25
+ .vmz-ui-checkbox__input:focus-visible {
26
+ box-shadow: 0 0 0 3px var(--vmz-focus-ring);
27
+ }
28
+
29
+ .vmz-ui-checkbox__input:disabled {
30
+ opacity: 0.55;
31
+ cursor: not-allowed;
32
+ }
33
+
34
+ .vmz-ui-checkbox__label {
35
+ font-weight: 600;
36
+ }
37
+ </style>
38
+ <script client>
39
+ /**
40
+ * VMZ UI — Checkbox (UI2).
41
+ * Label association via wrapping label + for/id; parent owns checked (no parallel form store).
42
+ * Native change reports the committed checked state to the parent.
43
+ */
44
+ export default class Checkbox {
45
+ public label: string = "";
46
+ public checked: boolean = false;
47
+ public disabled: boolean = false;
48
+ public controlId: string = "vmz-checkbox";
49
+ public describedBy: string = "";
50
+ onToggle(ev) {
51
+ if (this.disabled) return;
52
+ this.emit("change", !!ev?.target?.checked);
53
+ }
54
+ }
55
+ </script>
@@ -1,10 +1,12 @@
1
1
  <template>
2
2
  <figure data-vmz-ui="code-block" class="vmz-ui-code-block">
3
- <div class="vmz-ui-code-block__bar">
4
- <figcaption v-if="caption" class="vmz-ui-code-block__caption">
5
- {{ caption }}
6
- </figcaption>
7
- <Button type="button" variant="ghost" @click="onCopy">
3
+ <div v-if="barVisible()" class="vmz-ui-code-block__bar">
4
+ <div class="vmz-ui-code-block__heading">
5
+ <span v-if="language" class="vmz-ui-code-block__lang">{{ language }}</span>
6
+ <span v-if="title" class="vmz-ui-code-block__caption">{{ title }}</span>
7
+ <span v-else-if="caption" class="vmz-ui-code-block__caption">{{ caption }}</span>
8
+ </div>
9
+ <Button type="button" variant="text" size="sm" class="vmz-ui-code-block__copy" @click="onCopy">
8
10
  {{ copyLabel }}
9
11
  </Button>
10
12
  </div>
@@ -20,43 +22,91 @@
20
22
  margin: 0;
21
23
  display: flex;
22
24
  flex-direction: column;
23
- gap: 0.45rem;
24
25
  border: 1px solid var(--vmz-line-default);
25
- border-radius: var(--vmz-radius-md, 6px);
26
+ border-radius: var(--vmz-radius-lg, 8px);
26
27
  background: var(--vmz-surface-mist);
27
28
  overflow: hidden;
28
29
  }
29
30
 
30
31
  .vmz-ui-code-block__bar {
31
32
  display: flex;
32
- flex-wrap: wrap;
33
33
  align-items: center;
34
34
  justify-content: space-between;
35
- gap: 0.45rem;
36
- padding: 0.35rem 0.55rem;
37
- border-bottom: 1px solid var(--vmz-line-default);
35
+ gap: 0.5rem;
36
+ min-height: 2.25rem;
37
+ padding: 0 0.5rem 0 0.75rem;
38
+ border-bottom: 1px solid var(--vmz-line-subtle, var(--vmz-line-default));
38
39
  background: var(--vmz-surface-paper);
39
40
  }
40
41
 
41
- .vmz-ui-code-block__caption {
42
- margin: 0;
42
+ .vmz-ui-code-block__heading {
43
+ display: flex;
44
+ align-items: center;
45
+ gap: 0.5rem;
46
+ min-width: 0;
47
+ }
48
+
49
+ .vmz-ui-code-block__lang {
50
+ flex: none;
51
+ padding: 0.0625rem 0.4375rem;
52
+ border: 1px solid var(--vmz-line-default);
53
+ border-radius: var(--vmz-radius-xs, 2px);
54
+ background: var(--vmz-surface-mist);
55
+ color: var(--vmz-text-steel);
43
56
  font-family: var(--vmz-font-mono);
44
- font-size: 0.8rem;
57
+ font-size: 0.6875rem;
58
+ font-weight: 600;
59
+ line-height: 1.4;
60
+ letter-spacing: 0.04em;
61
+ text-transform: uppercase;
62
+ }
63
+
64
+ .vmz-ui-code-block__caption {
65
+ overflow: hidden;
45
66
  color: var(--vmz-text-steel);
67
+ font-size: 0.8125rem;
68
+ line-height: 1.5;
69
+ text-overflow: ellipsis;
70
+ white-space: nowrap;
71
+ }
72
+
73
+ .vmz-ui-code-block__copy {
74
+ flex: none;
75
+ opacity: 0.72;
76
+ transition: opacity var(--vmz-motion-control-duration, 150ms) var(--vmz-motion-control-easing, cubic-bezier(0.4, 0, 0.2, 1));
77
+ }
78
+
79
+ .vmz-ui-code-block:hover .vmz-ui-code-block__copy,
80
+ .vmz-ui-code-block__copy:focus-visible {
81
+ opacity: 1;
46
82
  }
47
83
 
48
84
  .vmz-ui-code-block__pre {
49
85
  margin: 0;
50
- padding: 0.85rem 1rem;
86
+ padding: 0.875rem 1rem;
51
87
  overflow-x: auto;
88
+ overflow-y: auto;
89
+ max-height: 32rem;
52
90
  }
53
91
 
54
92
  .vmz-ui-code-block__code {
55
- font-family: var(--vmz-font-mono);
56
- font-size: 0.88rem;
57
- line-height: 1.5;
93
+ display: block;
58
94
  color: var(--vmz-text-ink);
95
+ font-family: var(--vmz-font-mono);
96
+ font-size: 0.8125rem;
97
+ line-height: 1.65;
59
98
  white-space: pre;
99
+ tab-size: 2;
100
+ }
101
+
102
+ .vmz-ui-code-block__pre::-webkit-scrollbar {
103
+ width: 0.5rem;
104
+ height: 0.5rem;
105
+ }
106
+
107
+ .vmz-ui-code-block__pre::-webkit-scrollbar-thumb {
108
+ border-radius: var(--vmz-radius-pill, 999px);
109
+ background: var(--vmz-line-default);
60
110
  }
61
111
  </style>
62
112
  <script client>
@@ -67,7 +117,16 @@
67
117
  export default class CodeBlock {
68
118
  public code: string = "";
69
119
  public caption: string = "";
120
+ /** Preferred heading text; falls back to `caption` when absent. */
121
+ public title: string = "";
122
+ /** Optional syntax label rendered as a chip in the bar (e.g. `ts`, `bash`). */
123
+ public language: string = "";
70
124
  public copyLabel: string = "Copy";
71
125
  public onCopy: ((e: Event) => void) | null = null;
126
+
127
+ /** The bar renders when there is anything to show in it. */
128
+ public barVisible(): boolean {
129
+ return !!(this.title || this.caption || this.language || this.onCopy);
130
+ }
72
131
  }
73
132
  </script>