@openvoxproject/voxblocks 0.9.0 → 0.10.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 +17 -9
- package/dist/cdn/voxblocks.css +1 -1
- package/dist/cdn/voxblocks.js +1784 -1180
- package/dist/types/components/alert/vox-alert.d.ts +2 -1
- package/dist/types/components/callout/vox-callout.d.ts +2 -1
- package/dist/types/components/header/vox-header.d.ts +9 -1
- package/dist/types/components/icon/icon-paths.d.ts +30 -0
- package/dist/types/components/icon/vox-icon.d.ts +22 -0
- package/dist/types/components/link-hub/vox-link-hub.d.ts +3 -0
- package/dist/types/components/sidenav/vox-sidenav.d.ts +16 -0
- package/dist/types/components/toc/vox-toc.d.ts +43 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/voxblocks.css +1 -1
- package/dist/voxblocks.js +1436 -832
- package/package.json +9 -6
- package/src/components/alert/vox-alert.ts +41 -1
- package/src/components/callout/vox-callout.ts +53 -3
- package/src/components/card/vox-card.ts +5 -0
- package/src/components/header/vox-header.ts +132 -7
- package/src/components/icon/icon-paths.ts +287 -0
- package/src/components/icon/vox-icon.ts +78 -0
- package/src/components/link-hub/vox-link-hub.ts +21 -0
- package/src/components/sidenav/vox-sidenav.ts +148 -3
- package/src/components/toc/vox-toc.ts +140 -0
- package/src/index.ts +6 -0
- package/src/styles/utilities.css +158 -0
- package/src/tokens/palette.css +95 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openvoxproject/voxblocks",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Web components for
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Web components for OpenVox community web sites and apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -35,9 +35,11 @@
|
|
|
35
35
|
"scripts": {
|
|
36
36
|
"prepare": "npm run build",
|
|
37
37
|
"build": "vite build && vite build --config vite.cdn.config.ts && tsc -p tsconfig.build.json",
|
|
38
|
-
"docs:dev": "
|
|
39
|
-
"docs:
|
|
40
|
-
"docs:
|
|
38
|
+
"docs:dev": "rm -rf docs/assets docs/_data/manifest.json && concurrently -n vite,jekyll -c blue,magenta \"npm:docs:assets:watch\" \"npm:docs:jekyll:wait\"",
|
|
39
|
+
"docs:assets:watch": "vite build --config docs/vite.config.js --watch",
|
|
40
|
+
"docs:jekyll:wait": "wait-on docs/_data/manifest.json && npm run docs:jekyll",
|
|
41
|
+
"docs:jekyll": "BUNDLE_GEMFILE=docs/Gemfile bundle exec jekyll serve --livereload --source docs --destination docs/_site",
|
|
42
|
+
"docs:build": "vite build --config docs/vite.config.js && BUNDLE_GEMFILE=docs/Gemfile bundle exec jekyll build --source docs --destination docs/_site --config docs/_config.yml,docs/_config_production.yml",
|
|
41
43
|
"storybook": "storybook dev -p 6006",
|
|
42
44
|
"build-storybook": "storybook build"
|
|
43
45
|
},
|
|
@@ -47,10 +49,11 @@
|
|
|
47
49
|
"devDependencies": {
|
|
48
50
|
"@storybook/web-components": "^10.5.5",
|
|
49
51
|
"@storybook/web-components-vite": "^10.5.5",
|
|
52
|
+
"concurrently": "^9.1.0",
|
|
50
53
|
"storybook": "^10.5.5",
|
|
51
54
|
"typescript": "^5.6.0",
|
|
52
55
|
"vite": "^6.0.0",
|
|
53
|
-
"
|
|
56
|
+
"wait-on": "^9.1.0"
|
|
54
57
|
},
|
|
55
58
|
"allowScripts": {
|
|
56
59
|
"esbuild@0.25.12": true,
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { LitElement, html, css, nothing } from 'lit';
|
|
2
2
|
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
+
import { ICON_PATHS, type IconName } from '../icon/icon-paths.js';
|
|
3
4
|
|
|
4
5
|
export type AlertVariant = 'info' | 'success' | 'warning' | 'danger';
|
|
5
6
|
|
|
7
|
+
const VARIANT_ICON: Record<AlertVariant, IconName> = {
|
|
8
|
+
info: 'info',
|
|
9
|
+
success: 'check-circle',
|
|
10
|
+
warning: 'warning',
|
|
11
|
+
danger: 'x-circle',
|
|
12
|
+
};
|
|
13
|
+
|
|
6
14
|
/**
|
|
7
|
-
* A page-level notification message, optionally dismissible.
|
|
15
|
+
* A page-level notification message, optionally dismissible. Shows an
|
|
16
|
+
* icon matching `variant` automatically — no icon slot to fill in.
|
|
8
17
|
* For static advisory text inside prose, see `<vox-callout>`.
|
|
9
18
|
*
|
|
10
19
|
* @slot - Alert message.
|
|
@@ -43,6 +52,13 @@ export class VoxAlert extends LitElement {
|
|
|
43
52
|
flex: 1 1 auto;
|
|
44
53
|
}
|
|
45
54
|
|
|
55
|
+
.icon {
|
|
56
|
+
flex: none;
|
|
57
|
+
width: 20px;
|
|
58
|
+
height: 20px;
|
|
59
|
+
margin-top: 1px;
|
|
60
|
+
}
|
|
61
|
+
|
|
46
62
|
.heading {
|
|
47
63
|
margin: 0 0 var(--vox-space-1);
|
|
48
64
|
font-size: 14px;
|
|
@@ -56,6 +72,9 @@ export class VoxAlert extends LitElement {
|
|
|
56
72
|
.info .heading {
|
|
57
73
|
color: var(--vox-color-brand-1);
|
|
58
74
|
}
|
|
75
|
+
.info .icon {
|
|
76
|
+
color: var(--vox-color-brand-1);
|
|
77
|
+
}
|
|
59
78
|
|
|
60
79
|
.success {
|
|
61
80
|
background-color: var(--vox-color-tip-soft);
|
|
@@ -64,6 +83,9 @@ export class VoxAlert extends LitElement {
|
|
|
64
83
|
.success .heading {
|
|
65
84
|
color: var(--vox-color-tip-1);
|
|
66
85
|
}
|
|
86
|
+
.success .icon {
|
|
87
|
+
color: var(--vox-color-tip-1);
|
|
88
|
+
}
|
|
67
89
|
|
|
68
90
|
.warning {
|
|
69
91
|
background-color: var(--vox-color-warning-soft);
|
|
@@ -72,6 +94,9 @@ export class VoxAlert extends LitElement {
|
|
|
72
94
|
.warning .heading {
|
|
73
95
|
color: var(--vox-color-warning-1);
|
|
74
96
|
}
|
|
97
|
+
.warning .icon {
|
|
98
|
+
color: var(--vox-color-warning-1);
|
|
99
|
+
}
|
|
75
100
|
|
|
76
101
|
.danger {
|
|
77
102
|
background-color: var(--vox-color-danger-soft);
|
|
@@ -80,6 +105,9 @@ export class VoxAlert extends LitElement {
|
|
|
80
105
|
.danger .heading {
|
|
81
106
|
color: var(--vox-color-danger-1);
|
|
82
107
|
}
|
|
108
|
+
.danger .icon {
|
|
109
|
+
color: var(--vox-color-danger-1);
|
|
110
|
+
}
|
|
83
111
|
|
|
84
112
|
.close {
|
|
85
113
|
flex: none;
|
|
@@ -128,6 +156,18 @@ export class VoxAlert extends LitElement {
|
|
|
128
156
|
render() {
|
|
129
157
|
return html`
|
|
130
158
|
<div class="alert ${this.variant}" role="alert">
|
|
159
|
+
<svg
|
|
160
|
+
class="icon"
|
|
161
|
+
viewBox="0 0 48 48"
|
|
162
|
+
fill="none"
|
|
163
|
+
stroke="currentColor"
|
|
164
|
+
stroke-width="2"
|
|
165
|
+
stroke-linecap="round"
|
|
166
|
+
stroke-linejoin="round"
|
|
167
|
+
aria-hidden="true"
|
|
168
|
+
>
|
|
169
|
+
${ICON_PATHS[VARIANT_ICON[this.variant]]}
|
|
170
|
+
</svg>
|
|
131
171
|
<div class="body">
|
|
132
172
|
${this.heading
|
|
133
173
|
? html`<p class="heading">${this.heading}</p>`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LitElement, html, css } from 'lit';
|
|
2
2
|
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
+
import { ICON_PATHS, type IconName } from '../icon/icon-paths.js';
|
|
3
4
|
|
|
4
5
|
export type CalloutVariant = 'info' | 'tip' | 'warning' | 'danger';
|
|
5
6
|
|
|
@@ -10,9 +11,17 @@ const DEFAULT_HEADINGS: Record<CalloutVariant, string> = {
|
|
|
10
11
|
danger: 'Danger',
|
|
11
12
|
};
|
|
12
13
|
|
|
14
|
+
const VARIANT_ICON: Record<CalloutVariant, IconName> = {
|
|
15
|
+
info: 'info',
|
|
16
|
+
tip: 'check-circle',
|
|
17
|
+
warning: 'warning',
|
|
18
|
+
danger: 'x-circle',
|
|
19
|
+
};
|
|
20
|
+
|
|
13
21
|
/**
|
|
14
22
|
* An admonition block matching the custom containers on the OpenVox docs
|
|
15
|
-
* (::: tip, ::: warning, ...).
|
|
23
|
+
* (::: tip, ::: warning, ...). Shows an icon matching `variant`
|
|
24
|
+
* automatically — no icon slot to fill in.
|
|
16
25
|
*
|
|
17
26
|
* @slot - Callout body content.
|
|
18
27
|
*/
|
|
@@ -29,6 +38,9 @@ export class VoxCallout extends LitElement {
|
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
.callout {
|
|
41
|
+
display: flex;
|
|
42
|
+
gap: var(--vox-space-3);
|
|
43
|
+
align-items: flex-start;
|
|
32
44
|
border-radius: var(--vox-radius-md);
|
|
33
45
|
padding: var(--vox-space-4);
|
|
34
46
|
font-family: var(--vox-font-family-base);
|
|
@@ -37,6 +49,18 @@ export class VoxCallout extends LitElement {
|
|
|
37
49
|
color: var(--vox-color-text-2);
|
|
38
50
|
}
|
|
39
51
|
|
|
52
|
+
.icon {
|
|
53
|
+
flex: none;
|
|
54
|
+
width: 18px;
|
|
55
|
+
height: 18px;
|
|
56
|
+
margin-top: 2px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.content {
|
|
60
|
+
flex: 1 1 auto;
|
|
61
|
+
min-width: 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
40
64
|
.heading {
|
|
41
65
|
margin: 0 0 var(--vox-space-2);
|
|
42
66
|
font-size: 14px;
|
|
@@ -49,6 +73,9 @@ export class VoxCallout extends LitElement {
|
|
|
49
73
|
.info .heading {
|
|
50
74
|
color: var(--vox-color-text-1);
|
|
51
75
|
}
|
|
76
|
+
.info .icon {
|
|
77
|
+
color: var(--vox-color-text-1);
|
|
78
|
+
}
|
|
52
79
|
|
|
53
80
|
.tip {
|
|
54
81
|
background-color: var(--vox-color-tip-soft);
|
|
@@ -56,6 +83,9 @@ export class VoxCallout extends LitElement {
|
|
|
56
83
|
.tip .heading {
|
|
57
84
|
color: var(--vox-color-tip-1);
|
|
58
85
|
}
|
|
86
|
+
.tip .icon {
|
|
87
|
+
color: var(--vox-color-tip-1);
|
|
88
|
+
}
|
|
59
89
|
|
|
60
90
|
.warning {
|
|
61
91
|
background-color: var(--vox-color-warning-soft);
|
|
@@ -63,6 +93,9 @@ export class VoxCallout extends LitElement {
|
|
|
63
93
|
.warning .heading {
|
|
64
94
|
color: var(--vox-color-warning-1);
|
|
65
95
|
}
|
|
96
|
+
.warning .icon {
|
|
97
|
+
color: var(--vox-color-warning-1);
|
|
98
|
+
}
|
|
66
99
|
|
|
67
100
|
.danger {
|
|
68
101
|
background-color: var(--vox-color-danger-soft);
|
|
@@ -70,6 +103,9 @@ export class VoxCallout extends LitElement {
|
|
|
70
103
|
.danger .heading {
|
|
71
104
|
color: var(--vox-color-danger-1);
|
|
72
105
|
}
|
|
106
|
+
.danger .icon {
|
|
107
|
+
color: var(--vox-color-danger-1);
|
|
108
|
+
}
|
|
73
109
|
|
|
74
110
|
::slotted(p:first-child) {
|
|
75
111
|
margin-top: 0;
|
|
@@ -83,8 +119,22 @@ export class VoxCallout extends LitElement {
|
|
|
83
119
|
render() {
|
|
84
120
|
return html`
|
|
85
121
|
<div class="callout ${this.variant}" role="note">
|
|
86
|
-
<
|
|
87
|
-
|
|
122
|
+
<svg
|
|
123
|
+
class="icon"
|
|
124
|
+
viewBox="0 0 48 48"
|
|
125
|
+
fill="none"
|
|
126
|
+
stroke="currentColor"
|
|
127
|
+
stroke-width="2"
|
|
128
|
+
stroke-linecap="round"
|
|
129
|
+
stroke-linejoin="round"
|
|
130
|
+
aria-hidden="true"
|
|
131
|
+
>
|
|
132
|
+
${ICON_PATHS[VARIANT_ICON[this.variant]]}
|
|
133
|
+
</svg>
|
|
134
|
+
<div class="content">
|
|
135
|
+
<p class="heading">${this.heading ?? DEFAULT_HEADINGS[this.variant]}</p>
|
|
136
|
+
<slot></slot>
|
|
137
|
+
</div>
|
|
88
138
|
</div>
|
|
89
139
|
`;
|
|
90
140
|
}
|
|
@@ -32,6 +32,11 @@ export class VoxCard extends LitElement {
|
|
|
32
32
|
border-radius: var(--vox-radius-lg);
|
|
33
33
|
padding: var(--vox-space-6);
|
|
34
34
|
font-family: var(--vox-font-family-base);
|
|
35
|
+
/* Slotted content (e.g. an icon using stroke="currentColor")
|
|
36
|
+
inherits from this element's position in the flat tree — when
|
|
37
|
+
rendered as an <a>, that means the browser's default link color
|
|
38
|
+
without this, since nothing else in .card sets one. */
|
|
39
|
+
color: var(--vox-color-text-1);
|
|
35
40
|
text-decoration: none;
|
|
36
41
|
transition: border-color var(--vox-transition-base);
|
|
37
42
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { LitElement, html, css, nothing } from 'lit';
|
|
2
|
-
import { customElement, property } from 'lit/decorators.js';
|
|
2
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
3
|
+
import { ICON_PATHS } from '../icon/icon-paths.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* A site header with brand, primary navigation, and an actions area.
|
|
6
7
|
* Nav entries are plain links; mark the current page with
|
|
7
|
-
* `aria-current="page"`.
|
|
8
|
+
* `aria-current="page"`. Below 768px, nav and actions collapse behind a
|
|
9
|
+
* menu button.
|
|
8
10
|
*
|
|
9
11
|
* @slot logo - Brand mark shown before the site title.
|
|
10
12
|
* @slot - Navigation links.
|
|
@@ -18,6 +20,8 @@ export class VoxHeader extends LitElement {
|
|
|
18
20
|
/** Where the brand links to. */
|
|
19
21
|
@property() href = '/';
|
|
20
22
|
|
|
23
|
+
@state() private mobileOpen = false;
|
|
24
|
+
|
|
21
25
|
static styles = css`
|
|
22
26
|
:host {
|
|
23
27
|
display: block;
|
|
@@ -52,6 +56,39 @@ export class VoxHeader extends LitElement {
|
|
|
52
56
|
width: auto;
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
.menu-toggle {
|
|
60
|
+
display: none;
|
|
61
|
+
align-items: center;
|
|
62
|
+
justify-content: center;
|
|
63
|
+
width: 36px;
|
|
64
|
+
height: 36px;
|
|
65
|
+
margin-left: auto;
|
|
66
|
+
padding: 0;
|
|
67
|
+
background: none;
|
|
68
|
+
border: none;
|
|
69
|
+
border-radius: var(--vox-radius-sm);
|
|
70
|
+
color: var(--vox-color-text-1);
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.menu-toggle:hover {
|
|
75
|
+
color: var(--vox-color-brand-1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.menu-toggle:focus-visible {
|
|
79
|
+
outline: 2px solid var(--vox-color-brand-1);
|
|
80
|
+
outline-offset: 2px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.menu-icon {
|
|
84
|
+
width: 22px;
|
|
85
|
+
height: 22px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.nav-wrap {
|
|
89
|
+
display: contents;
|
|
90
|
+
}
|
|
91
|
+
|
|
55
92
|
nav {
|
|
56
93
|
display: flex;
|
|
57
94
|
align-items: center;
|
|
@@ -82,8 +119,73 @@ export class VoxHeader extends LitElement {
|
|
|
82
119
|
align-items: center;
|
|
83
120
|
gap: var(--vox-space-3);
|
|
84
121
|
}
|
|
122
|
+
|
|
123
|
+
@media (max-width: 768px) {
|
|
124
|
+
.menu-toggle {
|
|
125
|
+
display: inline-flex;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.nav-wrap {
|
|
129
|
+
display: none;
|
|
130
|
+
width: 100%;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.nav-wrap.open {
|
|
134
|
+
display: flex;
|
|
135
|
+
flex-direction: column;
|
|
136
|
+
align-items: stretch;
|
|
137
|
+
gap: var(--vox-space-4);
|
|
138
|
+
margin-top: var(--vox-space-3);
|
|
139
|
+
padding-top: var(--vox-space-4);
|
|
140
|
+
border-top: 1px solid var(--vox-color-divider);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
nav {
|
|
144
|
+
flex-direction: column;
|
|
145
|
+
align-items: flex-start;
|
|
146
|
+
gap: var(--vox-space-3);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.actions {
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
align-items: stretch;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
85
154
|
`;
|
|
86
155
|
|
|
156
|
+
connectedCallback() {
|
|
157
|
+
super.connectedCallback();
|
|
158
|
+
document.addEventListener('click', this.handleOutsideClick);
|
|
159
|
+
this.addEventListener('keydown', this.handleKeydown);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
disconnectedCallback() {
|
|
163
|
+
super.disconnectedCallback();
|
|
164
|
+
document.removeEventListener('click', this.handleOutsideClick);
|
|
165
|
+
this.removeEventListener('keydown', this.handleKeydown);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private handleOutsideClick = (event: MouseEvent) => {
|
|
169
|
+
if (this.mobileOpen && !event.composedPath().includes(this)) {
|
|
170
|
+
this.mobileOpen = false;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
private handleKeydown = (event: KeyboardEvent) => {
|
|
175
|
+
if (event.key === 'Escape' && this.mobileOpen) {
|
|
176
|
+
this.mobileOpen = false;
|
|
177
|
+
this.renderRoot.querySelector<HTMLElement>('.menu-toggle')?.focus();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
private toggleMobileMenu = () => {
|
|
182
|
+
this.mobileOpen = !this.mobileOpen;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
private closeMobileMenu = () => {
|
|
186
|
+
this.mobileOpen = false;
|
|
187
|
+
};
|
|
188
|
+
|
|
87
189
|
render() {
|
|
88
190
|
return html`
|
|
89
191
|
<header class="header">
|
|
@@ -91,11 +193,34 @@ export class VoxHeader extends LitElement {
|
|
|
91
193
|
<slot name="logo"></slot>
|
|
92
194
|
${this.siteTitle ? html`<span>${this.siteTitle}</span>` : nothing}
|
|
93
195
|
</a>
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
196
|
+
<button
|
|
197
|
+
type="button"
|
|
198
|
+
class="menu-toggle"
|
|
199
|
+
aria-expanded=${this.mobileOpen ? 'true' : 'false'}
|
|
200
|
+
aria-controls="nav-wrap"
|
|
201
|
+
aria-label=${this.mobileOpen ? 'Close menu' : 'Open menu'}
|
|
202
|
+
@click=${this.toggleMobileMenu}
|
|
203
|
+
>
|
|
204
|
+
<svg
|
|
205
|
+
class="menu-icon"
|
|
206
|
+
viewBox="0 0 48 48"
|
|
207
|
+
fill="none"
|
|
208
|
+
stroke="currentColor"
|
|
209
|
+
stroke-width="2"
|
|
210
|
+
stroke-linecap="round"
|
|
211
|
+
stroke-linejoin="round"
|
|
212
|
+
aria-hidden="true"
|
|
213
|
+
>
|
|
214
|
+
${this.mobileOpen ? ICON_PATHS.close : ICON_PATHS.menu}
|
|
215
|
+
</svg>
|
|
216
|
+
</button>
|
|
217
|
+
<div id="nav-wrap" class="nav-wrap${this.mobileOpen ? ' open' : ''}">
|
|
218
|
+
<nav aria-label="Main">
|
|
219
|
+
<slot @click=${this.closeMobileMenu}></slot>
|
|
220
|
+
</nav>
|
|
221
|
+
<div class="actions">
|
|
222
|
+
<slot name="actions"></slot>
|
|
223
|
+
</div>
|
|
99
224
|
</div>
|
|
100
225
|
</header>
|
|
101
226
|
`;
|