@vmz/ui 0.0.3 → 0.0.4

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 (53) hide show
  1. package/README.md +23 -1
  2. package/conformance/fixtures/button-control-motion.json +22 -0
  3. package/conformance/fixtures/dialog-overlay-motion.json +27 -0
  4. package/conformance/fixtures/drawer-overlay-motion.json +27 -0
  5. package/conformance/pack.v0.json +7 -0
  6. package/contracts/token-requirements.v0.json +604 -2
  7. package/package.json +50 -2
  8. package/presets/web-surface.v0.json +34 -0
  9. package/src/components/Accordion.vmz +112 -0
  10. package/src/components/Alert.vmz +84 -0
  11. package/src/components/AppShell.vmz +97 -0
  12. package/src/components/Autocomplete.vmz +176 -0
  13. package/src/components/Badge.vmz +63 -0
  14. package/src/components/Breadcrumb.vmz +66 -0
  15. package/src/components/BulkActions.vmz +52 -0
  16. package/src/components/Button.vmz +71 -4
  17. package/src/components/Callout.vmz +80 -0
  18. package/src/components/Card.vmz +81 -0
  19. package/src/components/Checkbox.vmz +69 -0
  20. package/src/components/CodeBlock.vmz +67 -0
  21. package/src/components/ConsoleShell.vmz +127 -0
  22. package/src/components/DataTable.vmz +220 -0
  23. package/src/components/DatePicker.vmz +93 -0
  24. package/src/components/Dialog.vmz +442 -0
  25. package/src/components/Drawer.vmz +435 -0
  26. package/src/components/Empty.vmz +52 -0
  27. package/src/components/Field.vmz +88 -0
  28. package/src/components/FilterBar.vmz +27 -0
  29. package/src/components/Form.vmz +72 -0
  30. package/src/components/FormItem.vmz +78 -0
  31. package/src/components/Link.vmz +48 -0
  32. package/src/components/List.vmz +116 -0
  33. package/src/components/Menu.vmz +270 -0
  34. package/src/components/Notification.vmz +83 -0
  35. package/src/components/Pagination.vmz +81 -0
  36. package/src/components/Popover.vmz +253 -0
  37. package/src/components/Prose.vmz +83 -0
  38. package/src/components/QueryForm.vmz +39 -0
  39. package/src/components/RadioGroup.vmz +135 -0
  40. package/src/components/Result.vmz +84 -0
  41. package/src/components/Select.vmz +351 -0
  42. package/src/components/Skeleton.vmz +80 -0
  43. package/src/components/Spinner.vmz +45 -0
  44. package/src/components/Steps.vmz +107 -0
  45. package/src/components/Switch.vmz +109 -0
  46. package/src/components/Table.vmz +134 -0
  47. package/src/components/Tabs.vmz +132 -0
  48. package/src/components/TextArea.vmz +91 -0
  49. package/src/components/Timeline.vmz +67 -0
  50. package/src/components/Toc.vmz +77 -0
  51. package/src/components/Tooltip.vmz +68 -0
  52. package/src/components/Tree.vmz +215 -0
  53. package/src/components/Upload.vmz +102 -0
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <div
3
+ if={selectedCount}
4
+ class="vmz-ui-bulk-actions"
5
+ data-vmz-ui="bulk-actions"
6
+ role="region"
7
+ aria-label={label}
8
+ >
9
+ <p class="vmz-ui-bulk-actions__count" data-dogfood="bulk-count">{countLabel}</p>
10
+ <div class="vmz-ui-bulk-actions__actions">
11
+ <slot />
12
+ </div>
13
+ </div>
14
+ </template>
15
+
16
+ <style>
17
+ .vmz-ui-bulk-actions {
18
+ display: flex;
19
+ flex-wrap: wrap;
20
+ align-items: center;
21
+ justify-content: space-between;
22
+ gap: 0.65rem 1rem;
23
+ padding: 0.65rem 0.85rem;
24
+ border: 1px solid var(--vmz-action-primary-background);
25
+ border-radius: 2px;
26
+ background: transparent;
27
+ }
28
+
29
+ .vmz-ui-bulk-actions__count {
30
+ margin: 0;
31
+ font-weight: 700;
32
+ color: var(--vmz-action-primary-active);
33
+ }
34
+
35
+ .vmz-ui-bulk-actions__actions {
36
+ display: flex;
37
+ flex-wrap: wrap;
38
+ gap: 0.5rem;
39
+ }
40
+ </style>
41
+
42
+ <script client>
43
+ /**
44
+ * VMZ UI — BulkActions (Console composition).
45
+ * Renders when selectedCount is truthy; parent owns selection count + label.
46
+ */
47
+ export default class BulkActions {
48
+ public label: string = 'Bulk actions';
49
+ public selectedCount: number = 0;
50
+ public countLabel: string = '';
51
+ }
52
+ </script>
@@ -2,6 +2,10 @@
2
2
  <button
3
3
  type={type}
4
4
  class="vmz-ui-btn"
5
+ data-vmz-ui="button"
6
+ data-vmz-motion="control"
7
+ data-vmz-motion-token="motion.control"
8
+ data-variant={variant}
5
9
  disabled={disabled}
6
10
  onClick={onClick}
7
11
  >
@@ -15,7 +19,7 @@
15
19
  align-items: center;
16
20
  justify-content: center;
17
21
  gap: 0.35rem;
18
- padding: 0.55rem 0.95rem;
22
+ padding: var(--vmz-density-control-padding-y) var(--vmz-density-control-padding-x);
19
23
  border: 1px solid transparent;
20
24
  border-radius: 2px;
21
25
  font: inherit;
@@ -25,6 +29,11 @@
25
29
  background: var(--vmz-action-primary-background);
26
30
  color: var(--vmz-action-primary-foreground);
27
31
  outline: none;
32
+ transition:
33
+ background-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
34
+ border-color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
35
+ color var(--vmz-motion-control-duration) var(--vmz-motion-control-easing),
36
+ box-shadow var(--vmz-motion-control-duration) var(--vmz-motion-control-easing);
28
37
  }
29
38
 
30
39
  .vmz-ui-btn:hover:not(:disabled) {
@@ -35,6 +44,49 @@
35
44
  background: var(--vmz-action-primary-active);
36
45
  }
37
46
 
47
+ .vmz-ui-btn[data-variant='secondary'] {
48
+ background: var(--vmz-action-secondary-background);
49
+ color: var(--vmz-action-secondary-foreground);
50
+ border-color: var(--vmz-action-secondary-border);
51
+ }
52
+
53
+ .vmz-ui-btn[data-variant='secondary']:hover:not(:disabled) {
54
+ background: var(--vmz-action-secondary-hover);
55
+ }
56
+
57
+ .vmz-ui-btn[data-variant='secondary']:active:not(:disabled) {
58
+ background: var(--vmz-action-secondary-hover);
59
+ border-color: var(--vmz-action-primary-active);
60
+ }
61
+
62
+ .vmz-ui-btn[data-variant='ghost'] {
63
+ background: transparent;
64
+ color: var(--vmz-text-ink);
65
+ border-color: transparent;
66
+ }
67
+
68
+ .vmz-ui-btn[data-variant='ghost']:hover:not(:disabled) {
69
+ background: color-mix(in srgb, var(--vmz-surface-mist) 85%, transparent);
70
+ }
71
+
72
+ .vmz-ui-btn[data-variant='ghost']:active:not(:disabled) {
73
+ background: var(--vmz-surface-mist);
74
+ }
75
+
76
+ .vmz-ui-btn[data-variant='danger'] {
77
+ background: var(--vmz-status-danger-accent);
78
+ color: var(--vmz-action-primary-foreground);
79
+ border-color: transparent;
80
+ }
81
+
82
+ .vmz-ui-btn[data-variant='danger']:hover:not(:disabled) {
83
+ background: color-mix(in srgb, var(--vmz-status-danger-accent) 82%, var(--vmz-text-ink));
84
+ }
85
+
86
+ .vmz-ui-btn[data-variant='danger']:active:not(:disabled) {
87
+ background: var(--vmz-status-danger-foreground);
88
+ }
89
+
38
90
  .vmz-ui-btn:focus-visible {
39
91
  box-shadow: 0 0 0 2px var(--vmz-focus-ring);
40
92
  }
@@ -43,16 +95,31 @@
43
95
  opacity: 0.55;
44
96
  cursor: not-allowed;
45
97
  }
98
+
99
+ :where([data-density='compact']) .vmz-ui-btn {
100
+ padding: var(--vmz-density-compact-padding-y) var(--vmz-density-compact-padding-x);
101
+ }
102
+
103
+ :where([data-density='dense']) .vmz-ui-btn {
104
+ padding: var(--vmz-density-dense-padding-y) var(--vmz-density-dense-padding-x);
105
+ }
106
+
107
+ @media (prefers-reduced-motion: reduce) {
108
+ .vmz-ui-btn {
109
+ transition: none;
110
+ }
111
+ }
46
112
  </style>
47
113
 
48
114
  <script client>
49
115
  /**
50
- * VMZ UI — Button probe (UI0).
51
- * Colors come only from Style Theme semantic tokens — never brand hex.
52
- * Requirements: contracts/token-requirements.v0.json
116
+ * VMZ UI — Button (variants + motion control feedback).
117
+ * Colors / motion come only from Style Theme semantic tokens — never brand hex.
118
+ * Variants: primary | secondary | ghost | danger
53
119
  */
54
120
  export default class Button {
55
121
  public type: string = 'button';
122
+ public variant: string = 'primary';
56
123
  public disabled: boolean = false;
57
124
  public onClick: ((e: Event) => void) | null = null;
58
125
  }
@@ -0,0 +1,80 @@
1
+ <template>
2
+ <aside
3
+ class="vmz-ui-callout"
4
+ data-vmz-ui="callout"
5
+ data-tone={tone}
6
+ role={roleName}
7
+ >
8
+ <p if={title} class="vmz-ui-callout__title">{title}</p>
9
+ <p if={description} class="vmz-ui-callout__description">{description}</p>
10
+ <div class="vmz-ui-callout__body">
11
+ <slot />
12
+ </div>
13
+ </aside>
14
+ </template>
15
+
16
+ <style>
17
+ .vmz-ui-callout {
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: 0.45rem;
21
+ padding: var(--vmz-density-control-padding-y) var(--vmz-density-control-padding-x);
22
+ border: 1px solid var(--vmz-status-info-accent);
23
+ border-left-width: 4px;
24
+ border-radius: 2px;
25
+ background: color-mix(in srgb, var(--vmz-status-info-accent) 10%, var(--vmz-surface-paper));
26
+ color: var(--vmz-status-info-foreground);
27
+ }
28
+
29
+ .vmz-ui-callout[data-tone='info'] {
30
+ border-color: var(--vmz-status-info-accent);
31
+ background: color-mix(in srgb, var(--vmz-status-info-accent) 10%, var(--vmz-surface-paper));
32
+ color: var(--vmz-status-info-foreground);
33
+ }
34
+
35
+ .vmz-ui-callout[data-tone='success'] {
36
+ border-color: var(--vmz-status-success-accent);
37
+ background: color-mix(in srgb, var(--vmz-status-success-accent) 10%, var(--vmz-surface-paper));
38
+ color: var(--vmz-status-success-foreground);
39
+ }
40
+
41
+ .vmz-ui-callout[data-tone='warning'] {
42
+ border-color: var(--vmz-status-warning-accent);
43
+ background: color-mix(in srgb, var(--vmz-status-warning-accent) 10%, var(--vmz-surface-paper));
44
+ color: var(--vmz-status-warning-foreground);
45
+ }
46
+
47
+ .vmz-ui-callout[data-tone='danger'] {
48
+ border-color: var(--vmz-status-danger-accent);
49
+ background: color-mix(in srgb, var(--vmz-status-danger-accent) 10%, var(--vmz-surface-paper));
50
+ color: var(--vmz-status-danger-foreground);
51
+ }
52
+
53
+ .vmz-ui-callout__title {
54
+ margin: 0;
55
+ font-family: var(--vmz-font-display);
56
+ font-weight: 700;
57
+ }
58
+
59
+ .vmz-ui-callout__description {
60
+ margin: 0;
61
+ line-height: 1.5;
62
+ }
63
+
64
+ .vmz-ui-callout__body:empty {
65
+ display: none;
66
+ }
67
+ </style>
68
+
69
+ <script client>
70
+ /**
71
+ * VMZ UI — Callout (Document/Product composition).
72
+ * Inline guidance block for prose; shares status.* tones with Alert.
73
+ */
74
+ export default class Callout {
75
+ public title: string = '';
76
+ public description: string = '';
77
+ public tone: string = 'info';
78
+ public roleName: string = 'note';
79
+ }
80
+ </script>
@@ -0,0 +1,81 @@
1
+ <template>
2
+ <section class="vmz-ui-card" data-vmz-ui="card">
3
+ <header if={title} class="vmz-ui-card__header">
4
+ <h2 class="vmz-ui-card__title">{title}</h2>
5
+ <p if={description} class="vmz-ui-card__description">{description}</p>
6
+ </header>
7
+ <div class="vmz-ui-card__body">
8
+ <slot />
9
+ </div>
10
+ </section>
11
+ </template>
12
+
13
+ <style>
14
+ .vmz-ui-card {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: var(--vmz-density-control-gap);
18
+ padding: calc(var(--vmz-density-control-padding-y) + 0.35rem)
19
+ calc(var(--vmz-density-control-padding-x) + 0.25rem);
20
+ border: 1px solid var(--vmz-line-default);
21
+ border-radius: 2px;
22
+ background: var(--vmz-surface-paper);
23
+ color: var(--vmz-text-ink);
24
+ box-shadow: 0 1px 0 color-mix(in srgb, var(--vmz-line-default) 65%, transparent);
25
+ }
26
+
27
+ :where([data-density='compact']) .vmz-ui-card {
28
+ gap: var(--vmz-density-compact-gap);
29
+ padding: var(--vmz-density-compact-padding-y) var(--vmz-density-compact-padding-x);
30
+ }
31
+
32
+ :where([data-density='dense']) .vmz-ui-card {
33
+ gap: var(--vmz-density-dense-gap);
34
+ padding: var(--vmz-density-dense-padding-y) var(--vmz-density-dense-padding-x);
35
+ }
36
+
37
+ .vmz-ui-card__header {
38
+ display: flex;
39
+ flex-direction: column;
40
+ gap: 0.35rem;
41
+ }
42
+
43
+ .vmz-ui-card__title {
44
+ margin: 0;
45
+ font-family: var(--vmz-font-display);
46
+ font-size: 1.1rem;
47
+ font-weight: 700;
48
+ color: var(--vmz-text-ink);
49
+ }
50
+
51
+ .vmz-ui-card__description {
52
+ margin: 0;
53
+ color: var(--vmz-text-steel);
54
+ line-height: 1.45;
55
+ }
56
+
57
+ .vmz-ui-card__body {
58
+ display: flex;
59
+ flex-direction: column;
60
+ gap: var(--vmz-density-control-gap);
61
+ }
62
+
63
+ :where([data-density='compact']) .vmz-ui-card__body {
64
+ gap: var(--vmz-density-compact-gap);
65
+ }
66
+
67
+ :where([data-density='dense']) .vmz-ui-card__body {
68
+ gap: var(--vmz-density-dense-gap);
69
+ }
70
+ </style>
71
+
72
+ <script client>
73
+ /**
74
+ * VMZ UI — Card (Commercial Surface).
75
+ * Surface container; spacing from density.*; chrome from surface/line/text.
76
+ */
77
+ export default class Card {
78
+ public title: string = '';
79
+ public description: string = '';
80
+ }
81
+ </script>
@@ -0,0 +1,69 @@
1
+ <template>
2
+ <label class="vmz-ui-checkbox" data-vmz-ui="checkbox" for={controlId}>
3
+ <input
4
+ id={controlId}
5
+ class="vmz-ui-checkbox__input"
6
+ type="checkbox"
7
+ checked={checked}
8
+ disabled={disabled}
9
+ aria-describedby={describedBy}
10
+ onClick={onToggle}
11
+ />
12
+ <span class="vmz-ui-checkbox__label">{label}</span>
13
+ </label>
14
+ </template>
15
+
16
+ <style>
17
+ .vmz-ui-checkbox {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ gap: 0.45rem;
21
+ font: inherit;
22
+ cursor: pointer;
23
+ }
24
+
25
+ .vmz-ui-checkbox__input {
26
+ width: 1rem;
27
+ height: 1rem;
28
+ accent-color: var(--vmz-action-primary-background);
29
+ outline: none;
30
+ }
31
+
32
+ .vmz-ui-checkbox__input:focus-visible {
33
+ box-shadow: 0 0 0 2px var(--vmz-focus-ring);
34
+ }
35
+
36
+ .vmz-ui-checkbox__input:disabled {
37
+ opacity: 0.55;
38
+ cursor: not-allowed;
39
+ }
40
+
41
+ .vmz-ui-checkbox__label {
42
+ font-weight: 600;
43
+ }
44
+ </style>
45
+
46
+ <script client>
47
+ /**
48
+ * VMZ UI — Checkbox (UI2).
49
+ * Label association via wrapping label + for/id; parent owns checked (no parallel form store).
50
+ * Toggle goes through onChange so live `checked` prop stays authoritative.
51
+ */
52
+ export default class Checkbox {
53
+ public label: string = '';
54
+ public checked: boolean = false;
55
+ public disabled: boolean = false;
56
+ public controlId: string = 'vmz-checkbox';
57
+ public describedBy: string = '';
58
+ public onChange: ((e: { target: { checked: boolean } }) => void) | null = null;
59
+
60
+ onToggle(ev) {
61
+ if (this.disabled) return;
62
+ if (ev && typeof ev.preventDefault === 'function') ev.preventDefault();
63
+ const next = !this.checked;
64
+ if (typeof this.onChange === 'function') {
65
+ this.onChange({ target: { checked: next } });
66
+ }
67
+ }
68
+ }
69
+ </script>
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <figure class="vmz-ui-code-block" data-vmz-ui="code-block">
3
+ <div class="vmz-ui-code-block__bar">
4
+ <figcaption if={caption} class="vmz-ui-code-block__caption">{caption}</figcaption>
5
+ <Button type="button" variant="ghost" onClick={onCopy}>{copyLabel}</Button>
6
+ </div>
7
+ <pre class="vmz-ui-code-block__pre"><code class="vmz-ui-code-block__code">{code}</code></pre>
8
+ </figure>
9
+ </template>
10
+
11
+ <style>
12
+ .vmz-ui-code-block {
13
+ margin: 0;
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: 0.45rem;
17
+ border: 1px solid var(--vmz-line-default);
18
+ border-radius: 2px;
19
+ background: var(--vmz-surface-mist);
20
+ overflow: hidden;
21
+ }
22
+
23
+ .vmz-ui-code-block__bar {
24
+ display: flex;
25
+ flex-wrap: wrap;
26
+ align-items: center;
27
+ justify-content: space-between;
28
+ gap: 0.45rem;
29
+ padding: 0.35rem 0.55rem;
30
+ border-bottom: 1px solid var(--vmz-line-default);
31
+ background: var(--vmz-surface-paper);
32
+ }
33
+
34
+ .vmz-ui-code-block__caption {
35
+ margin: 0;
36
+ font-family: var(--vmz-font-mono);
37
+ font-size: 0.8rem;
38
+ color: var(--vmz-text-steel);
39
+ }
40
+
41
+ .vmz-ui-code-block__pre {
42
+ margin: 0;
43
+ padding: 0.85rem 1rem;
44
+ overflow-x: auto;
45
+ }
46
+
47
+ .vmz-ui-code-block__code {
48
+ font-family: var(--vmz-font-mono);
49
+ font-size: 0.88rem;
50
+ line-height: 1.5;
51
+ color: var(--vmz-text-ink);
52
+ white-space: pre;
53
+ }
54
+ </style>
55
+
56
+ <script client>
57
+ /**
58
+ * VMZ UI — CodeBlock (Document/Product composition).
59
+ * Source is readable in SSR HTML; copy is progressive enhancement via onCopy.
60
+ */
61
+ export default class CodeBlock {
62
+ public code: string = '';
63
+ public caption: string = '';
64
+ public copyLabel: string = 'Copy';
65
+ public onCopy: ((e: Event) => void) | null = null;
66
+ }
67
+ </script>
@@ -0,0 +1,127 @@
1
+ <template>
2
+ <div class="vmz-ui-console-shell" data-vmz-ui="console-shell">
3
+ <aside class="vmz-ui-console-shell__sidebar" data-vmz-shell="sidebar" aria-label={navLabel}>
4
+ <a class="vmz-ui-console-shell__brand" href={homeHref}>{brand}</a>
5
+ <nav class="vmz-ui-console-shell__nav">
6
+ <a each={nav} as="item" key={item.id} href={item.href} data-vmz-nav={item.id}>
7
+ {item.title}
8
+ </a>
9
+ </nav>
10
+ </aside>
11
+ <div class="vmz-ui-console-shell__body">
12
+ <header class="vmz-ui-console-shell__header" data-vmz-shell="header">
13
+ <h1 class="vmz-ui-console-shell__title">{title}</h1>
14
+ </header>
15
+ <main class="vmz-ui-console-shell__main" data-vmz-shell="main">
16
+ <slot />
17
+ </main>
18
+ </div>
19
+ </div>
20
+ </template>
21
+
22
+ <style>
23
+ .vmz-ui-console-shell {
24
+ display: grid;
25
+ grid-template-columns: minmax(10rem, 14rem) minmax(0, 1fr);
26
+ min-height: 100%;
27
+ color: inherit;
28
+ }
29
+
30
+ .vmz-ui-console-shell__sidebar {
31
+ display: flex;
32
+ flex-direction: column;
33
+ gap: 1rem;
34
+ padding: 1rem 0.9rem;
35
+ border-right: 1px solid var(--vmz-action-primary-background);
36
+ }
37
+
38
+ .vmz-ui-console-shell__brand {
39
+ font: inherit;
40
+ font-weight: 700;
41
+ font-size: 1.05rem;
42
+ letter-spacing: 0.04em;
43
+ text-decoration: none;
44
+ color: var(--vmz-action-primary-background);
45
+ }
46
+
47
+ .vmz-ui-console-shell__brand:focus-visible {
48
+ outline: none;
49
+ box-shadow: 0 0 0 2px var(--vmz-focus-ring);
50
+ }
51
+
52
+ .vmz-ui-console-shell__nav {
53
+ display: flex;
54
+ flex-direction: column;
55
+ gap: 0.45rem;
56
+ }
57
+
58
+ .vmz-ui-console-shell__nav a {
59
+ font: inherit;
60
+ font-weight: 600;
61
+ text-decoration: none;
62
+ color: var(--vmz-action-primary-active);
63
+ padding: 0.35rem 0.4rem;
64
+ }
65
+
66
+ .vmz-ui-console-shell__nav a:focus-visible {
67
+ outline: none;
68
+ box-shadow: 0 0 0 2px var(--vmz-focus-ring);
69
+ }
70
+
71
+ .vmz-ui-console-shell__body {
72
+ display: flex;
73
+ flex-direction: column;
74
+ min-width: 0;
75
+ }
76
+
77
+ .vmz-ui-console-shell__header {
78
+ padding: 0.85rem 1.1rem;
79
+ border-bottom: 1px solid var(--vmz-action-primary-background);
80
+ }
81
+
82
+ .vmz-ui-console-shell__title {
83
+ margin: 0;
84
+ font-size: 1.15rem;
85
+ font-weight: 700;
86
+ color: var(--vmz-action-primary-active);
87
+ }
88
+
89
+ .vmz-ui-console-shell__main {
90
+ flex: 1;
91
+ padding: 1rem 1.1rem 1.75rem;
92
+ display: flex;
93
+ flex-direction: column;
94
+ gap: 0.85rem;
95
+ }
96
+
97
+ @media (max-width: 40rem) {
98
+ .vmz-ui-console-shell {
99
+ grid-template-columns: 1fr;
100
+ }
101
+
102
+ .vmz-ui-console-shell__sidebar {
103
+ border-right: 0;
104
+ border-bottom: 1px solid var(--vmz-action-primary-background);
105
+ }
106
+
107
+ .vmz-ui-console-shell__nav {
108
+ flex-direction: row;
109
+ flex-wrap: wrap;
110
+ }
111
+ }
112
+ </style>
113
+
114
+ <script client>
115
+ /**
116
+ * VMZ UI — ConsoleShell (Console composition).
117
+ * Sidebar + header landmarks + main slot. Parent supplies nav; no private theme.
118
+ */
119
+ export default class ConsoleShell {
120
+ public brand: string = 'VMZ';
121
+ public homeHref: string = '/';
122
+ public title: string = 'Console';
123
+ public navLabel: string = 'Console';
124
+ /** @type {{ id: string, title: string, href: string }[]} */
125
+ public nav: { id: string; title: string; href: string }[] = [];
126
+ }
127
+ </script>