@vmz/ui 0.0.3 → 0.1.0
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.
- package/README.md +28 -1
- package/conformance/fixtures/button-control-motion.json +22 -0
- package/conformance/fixtures/dialog-overlay-motion.json +27 -0
- package/conformance/fixtures/drawer-overlay-motion.json +27 -0
- package/conformance/pack.v0.json +7 -0
- package/contracts/token-requirements.v0.json +643 -2
- package/package.json +50 -2
- package/presets/web-surface.v0.json +34 -0
- package/src/components/Accordion.vmz +112 -0
- package/src/components/Alert.vmz +84 -0
- package/src/components/AppShell.vmz +97 -0
- package/src/components/Autocomplete.vmz +176 -0
- package/src/components/Badge.vmz +63 -0
- package/src/components/Breadcrumb.vmz +66 -0
- package/src/components/BulkActions.vmz +52 -0
- package/src/components/Button.vmz +78 -5
- package/src/components/Callout.vmz +80 -0
- package/src/components/Card.vmz +80 -0
- package/src/components/Checkbox.vmz +69 -0
- package/src/components/CodeBlock.vmz +67 -0
- package/src/components/ConsoleShell.vmz +127 -0
- package/src/components/DataTable.vmz +220 -0
- package/src/components/DatePicker.vmz +591 -0
- package/src/components/Dialog.vmz +442 -0
- package/src/components/Drawer.vmz +435 -0
- package/src/components/Empty.vmz +52 -0
- package/src/components/Field.vmz +88 -0
- package/src/components/FilterBar.vmz +27 -0
- package/src/components/Form.vmz +72 -0
- package/src/components/FormItem.vmz +78 -0
- package/src/components/Link.vmz +48 -0
- package/src/components/List.vmz +116 -0
- package/src/components/Menu.vmz +270 -0
- package/src/components/Notification.vmz +83 -0
- package/src/components/Pagination.vmz +81 -0
- package/src/components/Popover.vmz +253 -0
- package/src/components/Prose.vmz +83 -0
- package/src/components/QueryForm.vmz +39 -0
- package/src/components/RadioGroup.vmz +135 -0
- package/src/components/Result.vmz +84 -0
- package/src/components/Select.vmz +355 -0
- package/src/components/Skeleton.vmz +80 -0
- package/src/components/Spinner.vmz +45 -0
- package/src/components/Steps.vmz +107 -0
- package/src/components/Switch.vmz +109 -0
- package/src/components/Table.vmz +134 -0
- package/src/components/Tabs.vmz +132 -0
- package/src/components/TextArea.vmz +91 -0
- package/src/components/Timeline.vmz +67 -0
- package/src/components/Toc.vmz +77 -0
- package/src/components/Tooltip.vmz +68 -0
- package/src/components/Tree.vmz +215 -0
- package/src/components/Upload.vmz +1233 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="vmz-ui-table-wrap" data-vmz-ui="table" data-sort-key={sortKey} data-sort-dir={sortDir}>
|
|
3
|
+
<table class="vmz-ui-table">
|
|
4
|
+
<thead onClick={onHeadClick}>
|
|
5
|
+
<tr>
|
|
6
|
+
<th each={columns} as="col" key={col.id} scope="col" data-vmz-col={col.id}>
|
|
7
|
+
<button type="button" class="vmz-ui-table__sort" data-vmz-sort={col.id}>
|
|
8
|
+
{col.title}
|
|
9
|
+
</button>
|
|
10
|
+
</th>
|
|
11
|
+
<th if={hasRowAction} scope="col">{actionTitle}</th>
|
|
12
|
+
</tr>
|
|
13
|
+
</thead>
|
|
14
|
+
<tbody onClick={onBodyClick}>
|
|
15
|
+
<tr each={rows} as="row" key={row.id} data-vmz-row={row.id}>
|
|
16
|
+
<td each={row.cells} as="cell">{cell}</td>
|
|
17
|
+
<td if={hasRowAction}>
|
|
18
|
+
<button
|
|
19
|
+
type="button"
|
|
20
|
+
class="vmz-ui-table__row-action"
|
|
21
|
+
data-vmz-row-action={row.id}
|
|
22
|
+
disabled={rowActionDisabled}
|
|
23
|
+
>
|
|
24
|
+
{actionLabel}
|
|
25
|
+
</button>
|
|
26
|
+
</td>
|
|
27
|
+
</tr>
|
|
28
|
+
</tbody>
|
|
29
|
+
</table>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<style>
|
|
34
|
+
.vmz-ui-table-wrap {
|
|
35
|
+
overflow-x: auto;
|
|
36
|
+
border: 1px solid var(--vmz-action-primary-background);
|
|
37
|
+
border-radius: 2px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.vmz-ui-table {
|
|
41
|
+
width: 100%;
|
|
42
|
+
border-collapse: collapse;
|
|
43
|
+
font: inherit;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.vmz-ui-table th,
|
|
47
|
+
.vmz-ui-table td {
|
|
48
|
+
padding: var(--vmz-density-compact-padding-y) var(--vmz-density-compact-padding-x);
|
|
49
|
+
text-align: left;
|
|
50
|
+
border-bottom: 1px solid var(--vmz-action-primary-background);
|
|
51
|
+
vertical-align: middle;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.vmz-ui-table th {
|
|
55
|
+
font-weight: 700;
|
|
56
|
+
color: var(--vmz-action-primary-active);
|
|
57
|
+
background: transparent;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.vmz-ui-table__sort {
|
|
61
|
+
font: inherit;
|
|
62
|
+
font-weight: 700;
|
|
63
|
+
padding: 0;
|
|
64
|
+
border: 0;
|
|
65
|
+
background: transparent;
|
|
66
|
+
color: inherit;
|
|
67
|
+
cursor: pointer;
|
|
68
|
+
outline: none;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.vmz-ui-table__sort:focus-visible {
|
|
72
|
+
box-shadow: 0 0 0 2px var(--vmz-focus-ring);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.vmz-ui-table__row-action {
|
|
76
|
+
font: inherit;
|
|
77
|
+
font-weight: 600;
|
|
78
|
+
padding: 0.3rem 0.55rem;
|
|
79
|
+
border: 1px solid var(--vmz-action-primary-background);
|
|
80
|
+
border-radius: 2px;
|
|
81
|
+
background: transparent;
|
|
82
|
+
color: var(--vmz-action-primary-active);
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
outline: none;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.vmz-ui-table__row-action:focus-visible {
|
|
88
|
+
box-shadow: 0 0 0 2px var(--vmz-focus-ring);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.vmz-ui-table__row-action:disabled {
|
|
92
|
+
opacity: 0.55;
|
|
93
|
+
cursor: not-allowed;
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
96
|
+
|
|
97
|
+
<script client>
|
|
98
|
+
/**
|
|
99
|
+
* VMZ UI — Table (UI5 Console Surface).
|
|
100
|
+
* Column header click → onSort; row action → onRowAction; permission via rowActionDisabled.
|
|
101
|
+
*/
|
|
102
|
+
export default class Table {
|
|
103
|
+
/** @type {{ id: string, title: string }[]} */
|
|
104
|
+
public columns: { id: string; title: string }[] = [];
|
|
105
|
+
/** @type {{ id: string, cells: string[] }[]} */
|
|
106
|
+
public rows: { id: string; cells: string[] }[] = [];
|
|
107
|
+
public hasRowAction: boolean = false;
|
|
108
|
+
public rowActionDisabled: boolean = false;
|
|
109
|
+
public actionTitle: string = 'Action';
|
|
110
|
+
public actionLabel: string = 'Open';
|
|
111
|
+
public sortKey: string = '';
|
|
112
|
+
public sortDir: string = 'asc';
|
|
113
|
+
public onRowAction: ((rowId: string) => void) | null = null;
|
|
114
|
+
public onSort: ((columnId: string) => void) | null = null;
|
|
115
|
+
|
|
116
|
+
onBodyClick(ev) {
|
|
117
|
+
const t = ev && ev.target;
|
|
118
|
+
const el =
|
|
119
|
+
t && typeof t.closest === 'function' ? t.closest('[data-vmz-row-action]') : null;
|
|
120
|
+
if (!el || el.disabled) return;
|
|
121
|
+
const id = el.getAttribute('data-vmz-row-action');
|
|
122
|
+
if (id && typeof this.onRowAction === 'function') this.onRowAction(id);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
onHeadClick(ev) {
|
|
126
|
+
const t = ev && ev.target;
|
|
127
|
+
const el =
|
|
128
|
+
t && typeof t.closest === 'function' ? t.closest('[data-vmz-sort]') : null;
|
|
129
|
+
if (!el) return;
|
|
130
|
+
const id = el.getAttribute('data-vmz-sort');
|
|
131
|
+
if (id && typeof this.onSort === 'function') this.onSort(id);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
</script>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="vmz-ui-tabs" data-vmz-ui="tabs">
|
|
3
|
+
<div
|
|
4
|
+
class="vmz-ui-tabs__list"
|
|
5
|
+
role="tablist"
|
|
6
|
+
aria-label={label}
|
|
7
|
+
onKeyDown={onListKeyDown}
|
|
8
|
+
onClick={onListClick}
|
|
9
|
+
>
|
|
10
|
+
<button
|
|
11
|
+
each={items}
|
|
12
|
+
as="item"
|
|
13
|
+
key={item.id}
|
|
14
|
+
type="button"
|
|
15
|
+
class="vmz-ui-tabs__tab"
|
|
16
|
+
role="tab"
|
|
17
|
+
id={item.id}
|
|
18
|
+
data-vmz-tab={item.id}
|
|
19
|
+
aria-selected={selected === item.id ? 'true' : 'false'}
|
|
20
|
+
aria-controls={item.controls}
|
|
21
|
+
tabindex={selected === item.id ? 0 : -1}
|
|
22
|
+
>
|
|
23
|
+
{item.title}
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
<div
|
|
27
|
+
class="vmz-ui-tabs__panel"
|
|
28
|
+
role="tabpanel"
|
|
29
|
+
id={panelId}
|
|
30
|
+
data-vmz-tab-panel={selected}
|
|
31
|
+
aria-labelledby={selected}
|
|
32
|
+
tabindex="0"
|
|
33
|
+
>
|
|
34
|
+
<slot />
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<style>
|
|
40
|
+
.vmz-ui-tabs {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
gap: 0.65rem;
|
|
44
|
+
max-width: 36rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.vmz-ui-tabs__list {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-wrap: wrap;
|
|
50
|
+
gap: 0.25rem;
|
|
51
|
+
border-bottom: 1px solid var(--vmz-action-primary-background);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.vmz-ui-tabs__tab {
|
|
55
|
+
font: inherit;
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
padding: 0.45rem 0.75rem;
|
|
58
|
+
border: 1px solid transparent;
|
|
59
|
+
border-bottom: none;
|
|
60
|
+
border-radius: 2px 2px 0 0;
|
|
61
|
+
background: transparent;
|
|
62
|
+
color: inherit;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
outline: none;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.vmz-ui-tabs__tab[aria-selected='true'] {
|
|
68
|
+
background: var(--vmz-action-primary-background);
|
|
69
|
+
color: var(--vmz-action-primary-foreground);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.vmz-ui-tabs__tab:focus-visible {
|
|
73
|
+
box-shadow: 0 0 0 2px var(--vmz-focus-ring);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.vmz-ui-tabs__panel {
|
|
77
|
+
min-height: 2.5rem;
|
|
78
|
+
outline: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.vmz-ui-tabs__panel:focus-visible {
|
|
82
|
+
box-shadow: 0 0 0 2px var(--vmz-focus-ring);
|
|
83
|
+
}
|
|
84
|
+
</style>
|
|
85
|
+
|
|
86
|
+
<script client>
|
|
87
|
+
/**
|
|
88
|
+
* VMZ UI — Tabs (UI2).
|
|
89
|
+
* Parent owns `selected` id; keyboard Left/Right/Home/End on the tablist.
|
|
90
|
+
* Panel content is the default slot (page chooses which body to show).
|
|
91
|
+
*/
|
|
92
|
+
export default class Tabs {
|
|
93
|
+
public label: string = 'Tabs';
|
|
94
|
+
public selected: string = '';
|
|
95
|
+
public panelId: string = '';
|
|
96
|
+
/** @type {{ id: string, title: string, controls?: string }[]} */
|
|
97
|
+
public items: { id: string; title: string; controls?: string }[] = [];
|
|
98
|
+
public onSelect: ((id: string) => void) | null = null;
|
|
99
|
+
|
|
100
|
+
select(id) {
|
|
101
|
+
if (typeof this.onSelect === 'function') this.onSelect(id);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
onListClick(ev) {
|
|
105
|
+
const t = ev && ev.target;
|
|
106
|
+
const el =
|
|
107
|
+
t && typeof t.closest === 'function' ? t.closest('[data-vmz-tab]') : null;
|
|
108
|
+
const id = el && typeof el.getAttribute === 'function' ? el.getAttribute('data-vmz-tab') : null;
|
|
109
|
+
if (id) this.select(id);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
onListKeyDown(ev) {
|
|
113
|
+
if (!ev || !this.items?.length) return;
|
|
114
|
+
const ids = this.items.map((it) => it.id);
|
|
115
|
+
const idx = Math.max(0, ids.indexOf(this.selected));
|
|
116
|
+
let next = idx;
|
|
117
|
+
if (ev.key === 'ArrowRight') next = (idx + 1) % ids.length;
|
|
118
|
+
else if (ev.key === 'ArrowLeft') next = (idx - 1 + ids.length) % ids.length;
|
|
119
|
+
else if (ev.key === 'Home') next = 0;
|
|
120
|
+
else if (ev.key === 'End') next = ids.length - 1;
|
|
121
|
+
else return;
|
|
122
|
+
ev.preventDefault();
|
|
123
|
+
this.select(ids[next]);
|
|
124
|
+
const root = this.__vmzDomRoot;
|
|
125
|
+
const btn =
|
|
126
|
+
root && typeof root.querySelector === 'function'
|
|
127
|
+
? root.querySelector(`[data-vmz-tab="${ids[next]}"]`)
|
|
128
|
+
: null;
|
|
129
|
+
if (btn && typeof btn.focus === 'function') btn.focus();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
</script>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="vmz-ui-textarea" data-vmz-ui="textarea" data-invalid={invalidAttr}>
|
|
3
|
+
<label if={label} class="vmz-ui-textarea__label" for={controlId}>{label}</label>
|
|
4
|
+
<textarea
|
|
5
|
+
id={controlId}
|
|
6
|
+
class="vmz-ui-textarea__control"
|
|
7
|
+
rows={rows}
|
|
8
|
+
value={value}
|
|
9
|
+
disabled={disabled}
|
|
10
|
+
readonly={readonly}
|
|
11
|
+
aria-invalid={error ? 'true' : 'false'}
|
|
12
|
+
aria-describedby={describedBy}
|
|
13
|
+
onInput={onInput}
|
|
14
|
+
></textarea>
|
|
15
|
+
<p if={description} id={descriptionId} class="vmz-ui-textarea__description">{description}</p>
|
|
16
|
+
<p if={error} id={errorId} class="vmz-ui-textarea__error" role="alert">{error}</p>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<style>
|
|
21
|
+
.vmz-ui-textarea {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
gap: 0.35rem;
|
|
25
|
+
max-width: 28rem;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.vmz-ui-textarea__label {
|
|
29
|
+
font: inherit;
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.vmz-ui-textarea__control {
|
|
34
|
+
font: inherit;
|
|
35
|
+
min-height: 6rem;
|
|
36
|
+
padding: var(--vmz-density-control-padding-y) var(--vmz-density-control-padding-x);
|
|
37
|
+
border: 1px solid var(--vmz-action-primary-background);
|
|
38
|
+
border-radius: 2px;
|
|
39
|
+
background: transparent;
|
|
40
|
+
color: inherit;
|
|
41
|
+
outline: none;
|
|
42
|
+
resize: vertical;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.vmz-ui-textarea[data-invalid='true'] .vmz-ui-textarea__control {
|
|
46
|
+
border-color: var(--vmz-status-danger-accent);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.vmz-ui-textarea__control:focus-visible {
|
|
50
|
+
box-shadow: 0 0 0 2px var(--vmz-focus-ring);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.vmz-ui-textarea__control:disabled {
|
|
54
|
+
opacity: 0.55;
|
|
55
|
+
cursor: not-allowed;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.vmz-ui-textarea__description {
|
|
59
|
+
margin: 0;
|
|
60
|
+
opacity: 0.75;
|
|
61
|
+
font-size: 0.9em;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.vmz-ui-textarea__error {
|
|
65
|
+
margin: 0;
|
|
66
|
+
color: var(--vmz-status-danger-foreground);
|
|
67
|
+
font-size: 0.9em;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
70
|
+
|
|
71
|
+
<script client>
|
|
72
|
+
/**
|
|
73
|
+
* VMZ UI — TextArea.
|
|
74
|
+
* Same label/description/error ownership contract as Field; parent owns value.
|
|
75
|
+
*/
|
|
76
|
+
export default class TextArea {
|
|
77
|
+
public label: string = '';
|
|
78
|
+
public description: string = '';
|
|
79
|
+
public error: string = '';
|
|
80
|
+
public value: string = '';
|
|
81
|
+
public rows: number = 4;
|
|
82
|
+
public disabled: boolean = false;
|
|
83
|
+
public readonly: boolean = false;
|
|
84
|
+
public invalidAttr: string = 'false';
|
|
85
|
+
public controlId: string = 'vmz-textarea';
|
|
86
|
+
public descriptionId: string = 'vmz-textarea-desc';
|
|
87
|
+
public errorId: string = 'vmz-textarea-err';
|
|
88
|
+
public describedBy: string = '';
|
|
89
|
+
public onInput: ((e: Event) => void) | null = null;
|
|
90
|
+
}
|
|
91
|
+
</script>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ol class="vmz-ui-timeline" data-vmz-ui="timeline" aria-label={label}>
|
|
3
|
+
<li each={items} as="item" key={item.id} class="vmz-ui-timeline__item" data-vmz-audit={item.id}>
|
|
4
|
+
<p class="vmz-ui-timeline__time">{item.time}</p>
|
|
5
|
+
<p class="vmz-ui-timeline__title">{item.title}</p>
|
|
6
|
+
<p if={item.detail} class="vmz-ui-timeline__detail">{item.detail}</p>
|
|
7
|
+
</li>
|
|
8
|
+
</ol>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
.vmz-ui-timeline {
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 0;
|
|
15
|
+
list-style: none;
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: var(--vmz-density-compact-gap);
|
|
19
|
+
border-left: 2px solid var(--vmz-action-primary-background);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.vmz-ui-timeline__item {
|
|
23
|
+
position: relative;
|
|
24
|
+
padding: 0.15rem 0 0.15rem 0.85rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.vmz-ui-timeline__item::before {
|
|
28
|
+
content: '';
|
|
29
|
+
position: absolute;
|
|
30
|
+
left: -0.35rem;
|
|
31
|
+
top: 0.45rem;
|
|
32
|
+
width: 0.55rem;
|
|
33
|
+
height: 0.55rem;
|
|
34
|
+
border-radius: 999px;
|
|
35
|
+
background: var(--vmz-action-primary-background);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.vmz-ui-timeline__time {
|
|
39
|
+
margin: 0;
|
|
40
|
+
font-size: 0.85em;
|
|
41
|
+
opacity: 0.75;
|
|
42
|
+
color: var(--vmz-action-primary-active);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.vmz-ui-timeline__title {
|
|
46
|
+
margin: 0.15rem 0 0;
|
|
47
|
+
font-weight: 700;
|
|
48
|
+
color: var(--vmz-action-primary-active);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.vmz-ui-timeline__detail {
|
|
52
|
+
margin: 0.2rem 0 0;
|
|
53
|
+
opacity: 0.9;
|
|
54
|
+
}
|
|
55
|
+
</style>
|
|
56
|
+
|
|
57
|
+
<script client>
|
|
58
|
+
/**
|
|
59
|
+
* VMZ UI — Timeline (UI5 Console audit trail).
|
|
60
|
+
* Parent owns audit items; stable ids for list identity.
|
|
61
|
+
*/
|
|
62
|
+
export default class Timeline {
|
|
63
|
+
public label: string = 'Audit';
|
|
64
|
+
/** @type {{ id: string, time: string, title: string, detail?: string }[]} */
|
|
65
|
+
public items: { id: string; time: string; title: string; detail?: string }[] = [];
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<aside class="vmz-ui-toc" data-vmz-ui="toc" aria-label={label}>
|
|
3
|
+
<p class="vmz-ui-toc__title">{label}</p>
|
|
4
|
+
<nav class="vmz-ui-toc__nav">
|
|
5
|
+
<a
|
|
6
|
+
each={items}
|
|
7
|
+
as="item"
|
|
8
|
+
key={item.id}
|
|
9
|
+
class="vmz-ui-toc__link"
|
|
10
|
+
href={item.href}
|
|
11
|
+
data-vmz-toc={item.id}
|
|
12
|
+
>
|
|
13
|
+
{item.title}
|
|
14
|
+
</a>
|
|
15
|
+
</nav>
|
|
16
|
+
</aside>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
.vmz-ui-toc {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
gap: 0.55rem;
|
|
24
|
+
min-width: 11rem;
|
|
25
|
+
padding: 0.85rem 0.75rem;
|
|
26
|
+
border: 1px solid var(--vmz-line-default);
|
|
27
|
+
border-radius: 2px;
|
|
28
|
+
background: var(--vmz-surface-mist);
|
|
29
|
+
color: var(--vmz-text-ink);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.vmz-ui-toc__title {
|
|
33
|
+
margin: 0;
|
|
34
|
+
font-family: var(--vmz-font-display);
|
|
35
|
+
font-size: 0.8rem;
|
|
36
|
+
font-weight: 700;
|
|
37
|
+
letter-spacing: 0.04em;
|
|
38
|
+
text-transform: uppercase;
|
|
39
|
+
color: var(--vmz-text-steel);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.vmz-ui-toc__nav {
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
gap: 0.35rem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.vmz-ui-toc__link {
|
|
49
|
+
font: inherit;
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
font-size: 0.92rem;
|
|
52
|
+
text-decoration: none;
|
|
53
|
+
color: var(--vmz-action-primary-active);
|
|
54
|
+
outline: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.vmz-ui-toc__link:hover {
|
|
58
|
+
color: var(--vmz-action-primary-background);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.vmz-ui-toc__link:focus-visible {
|
|
62
|
+
box-shadow: 0 0 0 2px var(--vmz-focus-ring);
|
|
63
|
+
border-radius: 2px;
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
66
|
+
|
|
67
|
+
<script client>
|
|
68
|
+
/**
|
|
69
|
+
* VMZ UI — Toc (Document/Product composition).
|
|
70
|
+
* In-page outline; parent supplies items. Readable without JS.
|
|
71
|
+
*/
|
|
72
|
+
export default class Toc {
|
|
73
|
+
public label: string = 'On this page';
|
|
74
|
+
/** @type {{ id: string, title: string, href: string }[]} */
|
|
75
|
+
public items: { id: string; title: string; href: string }[] = [];
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="vmz-ui-tooltip"
|
|
4
|
+
data-vmz-ui="tooltip"
|
|
5
|
+
data-open={openAttr}
|
|
6
|
+
>
|
|
7
|
+
<div class="vmz-ui-tooltip__trigger" data-vmz-tooltip="trigger">
|
|
8
|
+
<slot />
|
|
9
|
+
</div>
|
|
10
|
+
<div
|
|
11
|
+
if={open}
|
|
12
|
+
class="vmz-ui-tooltip__bubble"
|
|
13
|
+
role="tooltip"
|
|
14
|
+
id={tooltipId}
|
|
15
|
+
data-vmz-tooltip="bubble"
|
|
16
|
+
>
|
|
17
|
+
{content}
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<style>
|
|
23
|
+
.vmz-ui-tooltip {
|
|
24
|
+
position: relative;
|
|
25
|
+
display: inline-flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
align-items: flex-start;
|
|
28
|
+
gap: 0.35rem;
|
|
29
|
+
max-width: 28rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.vmz-ui-tooltip__trigger {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: 0.35rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.vmz-ui-tooltip__bubble {
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
max-width: 18rem;
|
|
41
|
+
padding: 0.4rem 0.65rem;
|
|
42
|
+
border: 1px solid var(--vmz-line-default);
|
|
43
|
+
border-radius: 2px;
|
|
44
|
+
background: var(--vmz-surface-paper);
|
|
45
|
+
color: var(--vmz-text-ink);
|
|
46
|
+
font-size: 0.88rem;
|
|
47
|
+
line-height: 1.4;
|
|
48
|
+
box-shadow: 0 1px 0 color-mix(in srgb, var(--vmz-line-default) 70%, transparent);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.vmz-ui-tooltip[data-open='true'] .vmz-ui-tooltip__trigger :where(button, a, [tabindex]):focus-visible {
|
|
52
|
+
box-shadow: 0 0 0 2px var(--vmz-focus-ring);
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
55
|
+
|
|
56
|
+
<script client>
|
|
57
|
+
/**
|
|
58
|
+
* VMZ UI — Tooltip.
|
|
59
|
+
* Parent owns live `open` + content string. Not a second overlay runtime;
|
|
60
|
+
* Escape/outside policies stay with richer Popover/Dialog when needed.
|
|
61
|
+
*/
|
|
62
|
+
export default class Tooltip {
|
|
63
|
+
public open: boolean = false;
|
|
64
|
+
public openAttr: string = 'false';
|
|
65
|
+
public content: string = '';
|
|
66
|
+
public tooltipId: string = 'vmz-tooltip';
|
|
67
|
+
}
|
|
68
|
+
</script>
|