@openvoxproject/voxblocks 0.11.0 → 0.12.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 +1 -1
- package/dist/cdn/voxblocks.css +1 -1
- package/dist/cdn/voxblocks.js +2011 -1475
- package/dist/types/components/accordion/vox-accordion.d.ts +0 -2
- package/dist/types/components/code-block/languages.d.ts +28 -0
- package/dist/types/components/code-block/vox-code-block.d.ts +34 -0
- package/dist/types/components/disclosure/vox-disclosure.d.ts +0 -2
- package/dist/types/components/dropdown/vox-dropdown.d.ts +0 -3
- package/dist/types/components/header/vox-header.d.ts +0 -2
- package/dist/types/components/pagination/vox-pagination.d.ts +4 -6
- package/dist/types/components/sidenav/vox-sidenav.d.ts +0 -4
- package/dist/types/index.d.ts +2 -0
- package/dist/types/internal/field.d.ts +0 -4
- package/dist/voxblocks.css +1 -1
- package/dist/voxblocks.js +1491 -955
- package/package.json +1 -1
- package/src/components/accordion/vox-accordion.ts +1 -6
- package/src/components/alert/vox-alert.ts +1 -5
- package/src/components/avatar/vox-avatar.ts +2 -9
- package/src/components/card/vox-card.ts +1 -1
- package/src/components/checkbox/vox-checkbox.ts +3 -4
- package/src/components/code-block/languages.ts +259 -0
- package/src/components/code-block/vox-code-block.ts +302 -0
- package/src/components/dialog/vox-dialog.ts +2 -2
- package/src/components/disclosure/vox-disclosure.ts +1 -6
- package/src/components/dropdown/vox-dropdown.ts +8 -32
- package/src/components/empty-state/vox-empty-state.ts +1 -1
- package/src/components/file-input/vox-file-input.ts +1 -4
- package/src/components/header/vox-header.ts +0 -10
- package/src/components/input/vox-input.ts +0 -2
- package/src/components/link-hub/vox-link-hub.ts +1 -1
- package/src/components/pagination/vox-pagination.ts +6 -9
- package/src/components/radio/vox-radio-group.ts +6 -17
- package/src/components/select/vox-select.ts +0 -3
- package/src/components/sidenav/vox-sidenav.ts +1 -16
- package/src/components/step-indicator/vox-step-indicator.ts +2 -16
- package/src/components/tabs/vox-tabs.ts +2 -26
- package/src/components/textarea/vox-textarea.ts +0 -2
- package/src/index.ts +2 -0
- package/src/internal/field.ts +2 -20
- package/src/tokens/tokens.css +24 -7
|
@@ -9,13 +9,9 @@ import { customElement, property } from 'lit/decorators.js';
|
|
|
9
9
|
*/
|
|
10
10
|
@customElement('vox-disclosure')
|
|
11
11
|
export class VoxDisclosure extends LitElement {
|
|
12
|
-
private static nextId = 0;
|
|
13
|
-
|
|
14
12
|
@property() summary = 'Show details';
|
|
15
13
|
@property({ type: Boolean, reflect: true }) open = false;
|
|
16
14
|
|
|
17
|
-
private readonly panelId = `vox-disclosure-panel-${VoxDisclosure.nextId++}`;
|
|
18
|
-
|
|
19
15
|
static styles = css`
|
|
20
16
|
:host {
|
|
21
17
|
display: block;
|
|
@@ -81,7 +77,6 @@ export class VoxDisclosure extends LitElement {
|
|
|
81
77
|
<button
|
|
82
78
|
class="trigger"
|
|
83
79
|
aria-expanded=${this.open ? 'true' : 'false'}
|
|
84
|
-
aria-controls=${this.panelId}
|
|
85
80
|
@click=${this.toggle}
|
|
86
81
|
>
|
|
87
82
|
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
@@ -89,7 +84,7 @@ export class VoxDisclosure extends LitElement {
|
|
|
89
84
|
</svg>
|
|
90
85
|
${this.summary}
|
|
91
86
|
</button>
|
|
92
|
-
<div
|
|
87
|
+
<div class="panel" ?hidden=${!this.open}>
|
|
93
88
|
<slot></slot>
|
|
94
89
|
</div>
|
|
95
90
|
`;
|
|
@@ -122,14 +122,12 @@ export class VoxDropdown extends LitElement {
|
|
|
122
122
|
super.connectedCallback();
|
|
123
123
|
document.addEventListener('click', this.handleOutsideClick);
|
|
124
124
|
this.addEventListener('keydown', this.handleKeydown);
|
|
125
|
-
this.addEventListener('focusout', this.handleFocusOut);
|
|
126
125
|
}
|
|
127
126
|
|
|
128
127
|
disconnectedCallback() {
|
|
129
128
|
super.disconnectedCallback();
|
|
130
129
|
document.removeEventListener('click', this.handleOutsideClick);
|
|
131
130
|
this.removeEventListener('keydown', this.handleKeydown);
|
|
132
|
-
this.removeEventListener('focusout', this.handleFocusOut);
|
|
133
131
|
}
|
|
134
132
|
|
|
135
133
|
private handleOutsideClick = (event: MouseEvent) => {
|
|
@@ -138,20 +136,6 @@ export class VoxDropdown extends LitElement {
|
|
|
138
136
|
}
|
|
139
137
|
};
|
|
140
138
|
|
|
141
|
-
/** Close when focus leaves the component entirely (e.g. Tab past the last item). */
|
|
142
|
-
private handleFocusOut = (event: FocusEvent) => {
|
|
143
|
-
const next = event.relatedTarget as Node | null;
|
|
144
|
-
if (this.open && !(next && this.contains(next))) {
|
|
145
|
-
this.open = false;
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
private focusItem(index: number) {
|
|
150
|
-
const items = [...this.querySelectorAll<HTMLElement>('a, button')];
|
|
151
|
-
if (items.length === 0) return;
|
|
152
|
-
items[(index + items.length) % items.length].focus();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
139
|
private handleKeydown = (event: KeyboardEvent) => {
|
|
156
140
|
if (event.key === 'Escape' && this.open) {
|
|
157
141
|
this.open = false;
|
|
@@ -159,27 +143,20 @@ export class VoxDropdown extends LitElement {
|
|
|
159
143
|
return;
|
|
160
144
|
}
|
|
161
145
|
|
|
162
|
-
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
146
|
+
if ((event.key === 'ArrowDown' || event.key === 'ArrowUp') && this.open) {
|
|
163
147
|
event.preventDefault();
|
|
164
|
-
const delta = event.key === 'ArrowDown' ? 1 : -1;
|
|
165
|
-
if (!this.open) {
|
|
166
|
-
this.open = true;
|
|
167
|
-
this.updateComplete.then(() =>
|
|
168
|
-
this.focusItem(event.key === 'ArrowDown' ? 0 : -1),
|
|
169
|
-
);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
148
|
const items = [...this.querySelectorAll<HTMLElement>('a, button')];
|
|
173
|
-
|
|
174
|
-
|
|
149
|
+
if (items.length === 0) return;
|
|
150
|
+
const active = document.activeElement as HTMLElement;
|
|
151
|
+
const index = items.indexOf(active);
|
|
152
|
+
const delta = event.key === 'ArrowDown' ? 1 : -1;
|
|
153
|
+
const next = items[(Math.max(index, 0) + delta + items.length) % items.length];
|
|
154
|
+
next.focus();
|
|
175
155
|
}
|
|
176
156
|
};
|
|
177
157
|
|
|
178
158
|
private toggle() {
|
|
179
159
|
this.open = !this.open;
|
|
180
|
-
if (this.open) {
|
|
181
|
-
this.updateComplete.then(() => this.focusItem(0));
|
|
182
|
-
}
|
|
183
160
|
}
|
|
184
161
|
|
|
185
162
|
render() {
|
|
@@ -188,7 +165,6 @@ export class VoxDropdown extends LitElement {
|
|
|
188
165
|
class="trigger"
|
|
189
166
|
aria-expanded=${this.open ? 'true' : 'false'}
|
|
190
167
|
aria-haspopup="true"
|
|
191
|
-
aria-controls="menu"
|
|
192
168
|
@click=${this.toggle}
|
|
193
169
|
>
|
|
194
170
|
${this.label}
|
|
@@ -196,7 +172,7 @@ export class VoxDropdown extends LitElement {
|
|
|
196
172
|
<path d="m6 9 6 6 6-6" />
|
|
197
173
|
</svg>
|
|
198
174
|
</button>
|
|
199
|
-
<div
|
|
175
|
+
<div class="menu">
|
|
200
176
|
<slot @click=${() => (this.open = false)}></slot>
|
|
201
177
|
</div>
|
|
202
178
|
`;
|
|
@@ -60,7 +60,7 @@ export class VoxEmptyState extends LitElement {
|
|
|
60
60
|
render() {
|
|
61
61
|
return html`
|
|
62
62
|
<div class="empty">
|
|
63
|
-
<div class="icon"
|
|
63
|
+
<div class="icon"><slot name="icon"></slot></div>
|
|
64
64
|
<h3 class="heading">${this.heading}</h3>
|
|
65
65
|
<div class="body"><slot></slot></div>
|
|
66
66
|
<div class="actions"><slot name="actions"></slot></div>
|
|
@@ -84,9 +84,8 @@ export class VoxFileInput extends VoxFieldElement {
|
|
|
84
84
|
for (const file of files) data.append(this.name, file);
|
|
85
85
|
this.internals.setFormValue(files.length > 0 ? data : null);
|
|
86
86
|
|
|
87
|
-
this.invalid = this.required && files.length === 0;
|
|
88
87
|
this.internals.setValidity(
|
|
89
|
-
this.
|
|
88
|
+
this.required && files.length === 0 ? { valueMissing: true } : {},
|
|
90
89
|
'Please select a file.',
|
|
91
90
|
this.inputEl,
|
|
92
91
|
);
|
|
@@ -104,8 +103,6 @@ export class VoxFileInput extends VoxFieldElement {
|
|
|
104
103
|
accept=${ifDefined(this.accept)}
|
|
105
104
|
?multiple=${this.multiple}
|
|
106
105
|
?disabled=${this.disabled}
|
|
107
|
-
aria-describedby=${ifDefined(this.noteId)}
|
|
108
|
-
aria-invalid=${this.invalid ? 'true' : 'false'}
|
|
109
106
|
@change=${this.handleChange}
|
|
110
107
|
/>
|
|
111
108
|
<span class="picker">
|
|
@@ -157,14 +157,12 @@ export class VoxHeader extends LitElement {
|
|
|
157
157
|
super.connectedCallback();
|
|
158
158
|
document.addEventListener('click', this.handleOutsideClick);
|
|
159
159
|
this.addEventListener('keydown', this.handleKeydown);
|
|
160
|
-
this.addEventListener('focusout', this.handleFocusOut);
|
|
161
160
|
}
|
|
162
161
|
|
|
163
162
|
disconnectedCallback() {
|
|
164
163
|
super.disconnectedCallback();
|
|
165
164
|
document.removeEventListener('click', this.handleOutsideClick);
|
|
166
165
|
this.removeEventListener('keydown', this.handleKeydown);
|
|
167
|
-
this.removeEventListener('focusout', this.handleFocusOut);
|
|
168
166
|
}
|
|
169
167
|
|
|
170
168
|
private handleOutsideClick = (event: MouseEvent) => {
|
|
@@ -173,14 +171,6 @@ export class VoxHeader extends LitElement {
|
|
|
173
171
|
}
|
|
174
172
|
};
|
|
175
173
|
|
|
176
|
-
/** Close the mobile menu once focus tabs past its last item. */
|
|
177
|
-
private handleFocusOut = (event: FocusEvent) => {
|
|
178
|
-
const next = event.relatedTarget as Node | null;
|
|
179
|
-
if (this.mobileOpen && !(next && this.contains(next))) {
|
|
180
|
-
this.mobileOpen = false;
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
|
|
184
174
|
private handleKeydown = (event: KeyboardEvent) => {
|
|
185
175
|
if (event.key === 'Escape' && this.mobileOpen) {
|
|
186
176
|
this.mobileOpen = false;
|
|
@@ -56,8 +56,6 @@ export class VoxInput extends VoxFieldElement {
|
|
|
56
56
|
?readonly=${this.readonly}
|
|
57
57
|
?disabled=${this.disabled}
|
|
58
58
|
aria-label=${this.label ? nothing : 'text input'}
|
|
59
|
-
aria-describedby=${ifDefined(this.noteId)}
|
|
60
|
-
aria-invalid=${this.invalid ? 'true' : 'false'}
|
|
61
59
|
@input=${this.handleInput}
|
|
62
60
|
@change=${this.handleChange}
|
|
63
61
|
/>
|
|
@@ -106,7 +106,7 @@ export class VoxLinkHubItem extends LitElement {
|
|
|
106
106
|
return html`
|
|
107
107
|
<a href=${this.href}>
|
|
108
108
|
<span class="heading">
|
|
109
|
-
<span class="icon ${this.hasIcon ? 'has-icon' : ''}"
|
|
109
|
+
<span class="icon ${this.hasIcon ? 'has-icon' : ''}">
|
|
110
110
|
<slot name="icon" @slotchange=${this.handleIconSlotChange}></slot>
|
|
111
111
|
</span>
|
|
112
112
|
${this.heading}
|
|
@@ -2,20 +2,18 @@ import { LitElement, html, css } from 'lit';
|
|
|
2
2
|
import { customElement, property } from 'lit/decorators.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Page-number navigation. Children are plain links
|
|
6
|
-
* page
|
|
7
|
-
* `<vox-breadcrumbs>`, it shouldn't be a link to itself:
|
|
5
|
+
* Page-number navigation. Children are plain links; mark the current
|
|
6
|
+
* page with `aria-current="page"`:
|
|
8
7
|
*
|
|
9
8
|
* ```html
|
|
10
9
|
* <vox-pagination>
|
|
11
10
|
* <a href="?page=1">1</a>
|
|
12
|
-
* <
|
|
11
|
+
* <a href="?page=2" aria-current="page">2</a>
|
|
13
12
|
* <a href="?page=3">3</a>
|
|
14
13
|
* </vox-pagination>
|
|
15
14
|
* ```
|
|
16
15
|
*
|
|
17
|
-
* @slot - `<a>` elements (including any prev/next links)
|
|
18
|
-
* current-page `<span>`.
|
|
16
|
+
* @slot - `<a>` elements (including any prev/next links).
|
|
19
17
|
*/
|
|
20
18
|
@customElement('vox-pagination')
|
|
21
19
|
export class VoxPagination extends LitElement {
|
|
@@ -33,8 +31,7 @@ export class VoxPagination extends LitElement {
|
|
|
33
31
|
flex-wrap: wrap;
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
::slotted(a)
|
|
37
|
-
::slotted(span) {
|
|
34
|
+
::slotted(a) {
|
|
38
35
|
display: inline-flex;
|
|
39
36
|
align-items: center;
|
|
40
37
|
justify-content: center;
|
|
@@ -56,7 +53,7 @@ export class VoxPagination extends LitElement {
|
|
|
56
53
|
background-color: var(--vox-color-brand-soft);
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
::slotted([aria-current='page']) {
|
|
56
|
+
::slotted(a[aria-current='page']) {
|
|
60
57
|
background-color: var(--vox-color-brand-3);
|
|
61
58
|
color: var(--vox-color-text-inverse);
|
|
62
59
|
font-weight: 600;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { html, css
|
|
1
|
+
import { html, css } from 'lit';
|
|
2
2
|
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
-
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
4
3
|
import { VoxFieldElement, fieldStyles } from '../../internal/field.js';
|
|
5
4
|
import type { VoxRadio } from './vox-radio.js';
|
|
6
5
|
|
|
@@ -36,9 +35,8 @@ export class VoxRadioGroup extends VoxFieldElement {
|
|
|
36
35
|
|
|
37
36
|
updated() {
|
|
38
37
|
this.internals.setFormValue(this.value || null);
|
|
39
|
-
this.invalid = this.required && !this.value;
|
|
40
38
|
this.internals.setValidity(
|
|
41
|
-
this.
|
|
39
|
+
this.required && !this.value ? { valueMissing: true } : {},
|
|
42
40
|
'Please select an option.',
|
|
43
41
|
this,
|
|
44
42
|
);
|
|
@@ -48,12 +46,10 @@ export class VoxRadioGroup extends VoxFieldElement {
|
|
|
48
46
|
private syncRadios() {
|
|
49
47
|
const radios = this.radios;
|
|
50
48
|
const anyChecked = radios.some((r) => r.value === this.value && this.value !== '');
|
|
51
|
-
|
|
52
|
-
radios.forEach((radio) => {
|
|
49
|
+
radios.forEach((radio, index) => {
|
|
53
50
|
radio.checked = this.value !== '' && radio.value === this.value;
|
|
54
|
-
// Roving tabindex: the checked radio is tabbable, else the first
|
|
55
|
-
|
|
56
|
-
const tabbable = anyChecked ? radio.checked : radio === firstEnabled;
|
|
51
|
+
// Roving tabindex: the checked radio is tabbable, else the first one.
|
|
52
|
+
const tabbable = anyChecked ? radio.checked : index === 0;
|
|
57
53
|
radio.shadowRoot
|
|
58
54
|
?.querySelector('.radio')
|
|
59
55
|
?.setAttribute('tabindex', tabbable ? '0' : '-1');
|
|
@@ -93,14 +89,7 @@ export class VoxRadioGroup extends VoxFieldElement {
|
|
|
93
89
|
|
|
94
90
|
render() {
|
|
95
91
|
return html`
|
|
96
|
-
<div
|
|
97
|
-
class="field"
|
|
98
|
-
role="radiogroup"
|
|
99
|
-
aria-label=${this.label || nothing}
|
|
100
|
-
aria-describedby=${ifDefined(this.noteId)}
|
|
101
|
-
aria-invalid=${this.invalid ? 'true' : 'false'}
|
|
102
|
-
aria-required=${this.required ? 'true' : 'false'}
|
|
103
|
-
>
|
|
92
|
+
<div class="field" role="radiogroup" aria-label=${this.label}>
|
|
104
93
|
${this.label
|
|
105
94
|
? html`<span class="label">
|
|
106
95
|
${this.label}${this.required
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { html, css } from 'lit';
|
|
2
2
|
import { customElement, property, query } from 'lit/decorators.js';
|
|
3
|
-
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
4
3
|
import { VoxFieldElement, fieldStyles } from '../../internal/field.js';
|
|
5
4
|
|
|
6
5
|
/**
|
|
@@ -74,8 +73,6 @@ export class VoxSelect extends VoxFieldElement {
|
|
|
74
73
|
class="control"
|
|
75
74
|
?required=${this.required}
|
|
76
75
|
?disabled=${this.disabled}
|
|
77
|
-
aria-describedby=${ifDefined(this.noteId)}
|
|
78
|
-
aria-invalid=${this.invalid ? 'true' : 'false'}
|
|
79
76
|
@change=${this.handleChange}
|
|
80
77
|
></select>
|
|
81
78
|
${this.renderNote()}
|
|
@@ -94,14 +94,12 @@ export class VoxSidenav extends LitElement {
|
|
|
94
94
|
super.connectedCallback();
|
|
95
95
|
document.addEventListener('click', this.handleOutsideClick);
|
|
96
96
|
this.addEventListener('keydown', this.handleKeydown);
|
|
97
|
-
this.addEventListener('focusout', this.handleFocusOut);
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
disconnectedCallback() {
|
|
101
100
|
super.disconnectedCallback();
|
|
102
101
|
document.removeEventListener('click', this.handleOutsideClick);
|
|
103
102
|
this.removeEventListener('keydown', this.handleKeydown);
|
|
104
|
-
this.removeEventListener('focusout', this.handleFocusOut);
|
|
105
103
|
}
|
|
106
104
|
|
|
107
105
|
private handleOutsideClick = (event: MouseEvent) => {
|
|
@@ -110,14 +108,6 @@ export class VoxSidenav extends LitElement {
|
|
|
110
108
|
}
|
|
111
109
|
};
|
|
112
110
|
|
|
113
|
-
/** Close the mobile menu once focus tabs past its last item. */
|
|
114
|
-
private handleFocusOut = (event: FocusEvent) => {
|
|
115
|
-
const next = event.relatedTarget as Node | null;
|
|
116
|
-
if (this.mobileOpen && !(next && this.contains(next))) {
|
|
117
|
-
this.mobileOpen = false;
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
111
|
private handleKeydown = (event: KeyboardEvent) => {
|
|
122
112
|
if (event.key === 'Escape' && this.mobileOpen) {
|
|
123
113
|
this.mobileOpen = false;
|
|
@@ -180,13 +170,9 @@ export class VoxSidenav extends LitElement {
|
|
|
180
170
|
*/
|
|
181
171
|
@customElement('vox-sidenav-group')
|
|
182
172
|
export class VoxSidenavGroup extends LitElement {
|
|
183
|
-
private static nextId = 0;
|
|
184
|
-
|
|
185
173
|
@property() heading = '';
|
|
186
174
|
@property({ type: Boolean, reflect: true }) open = false;
|
|
187
175
|
|
|
188
|
-
private readonly itemsId = `vox-sidenav-group-items-${VoxSidenavGroup.nextId++}`;
|
|
189
|
-
|
|
190
176
|
static styles = css`
|
|
191
177
|
:host {
|
|
192
178
|
display: block;
|
|
@@ -254,7 +240,6 @@ export class VoxSidenavGroup extends LitElement {
|
|
|
254
240
|
<button
|
|
255
241
|
class="trigger"
|
|
256
242
|
aria-expanded=${this.open ? 'true' : 'false'}
|
|
257
|
-
aria-controls=${this.itemsId}
|
|
258
243
|
@click=${this.toggle}
|
|
259
244
|
>
|
|
260
245
|
${this.heading}
|
|
@@ -262,7 +247,7 @@ export class VoxSidenavGroup extends LitElement {
|
|
|
262
247
|
<path d="m9 6 6 6-6 6" />
|
|
263
248
|
</svg>
|
|
264
249
|
</button>
|
|
265
|
-
<div
|
|
250
|
+
<div class="items"><slot></slot></div>
|
|
266
251
|
`;
|
|
267
252
|
}
|
|
268
253
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LitElement, html, css
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
2
|
import { customElement, property } from 'lit/decorators.js';
|
|
3
3
|
|
|
4
4
|
export type StepState = 'complete' | 'current' | 'upcoming';
|
|
@@ -122,15 +122,6 @@ export class VoxStep extends LitElement {
|
|
|
122
122
|
font-weight: 600;
|
|
123
123
|
color: var(--vox-color-text-1);
|
|
124
124
|
}
|
|
125
|
-
|
|
126
|
-
.visually-hidden {
|
|
127
|
-
position: absolute;
|
|
128
|
-
width: 1px;
|
|
129
|
-
height: 1px;
|
|
130
|
-
overflow: hidden;
|
|
131
|
-
clip: rect(0 0 0 0);
|
|
132
|
-
white-space: nowrap;
|
|
133
|
-
}
|
|
134
125
|
`;
|
|
135
126
|
|
|
136
127
|
render() {
|
|
@@ -147,12 +138,7 @@ export class VoxStep extends LitElement {
|
|
|
147
138
|
</svg>`
|
|
148
139
|
: this.number}
|
|
149
140
|
</span>
|
|
150
|
-
<span class="label">
|
|
151
|
-
${this.label}
|
|
152
|
-
${this.state !== 'current'
|
|
153
|
-
? html`<span class="visually-hidden"> (${this.state})</span>`
|
|
154
|
-
: nothing}
|
|
155
|
-
</span>
|
|
141
|
+
<span class="label">${this.label}</span>
|
|
156
142
|
</div>
|
|
157
143
|
`;
|
|
158
144
|
}
|
|
@@ -65,18 +65,11 @@ export class VoxTabs extends LitElement {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
private handleKeydown(event: KeyboardEvent) {
|
|
68
|
-
const tabs = this.tabs;
|
|
69
|
-
if (event.key === 'Home' || event.key === 'End') {
|
|
70
|
-
event.preventDefault();
|
|
71
|
-
const target = tabs[event.key === 'Home' ? 0 : tabs.length - 1];
|
|
72
|
-
target.select();
|
|
73
|
-
target.focusTab();
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
68
|
const deltas: Record<string, number> = { ArrowRight: 1, ArrowLeft: -1 };
|
|
77
69
|
const delta = deltas[event.key];
|
|
78
70
|
if (!delta) return;
|
|
79
71
|
event.preventDefault();
|
|
72
|
+
const tabs = this.tabs;
|
|
80
73
|
const index = tabs.findIndex((t) => t.selected);
|
|
81
74
|
const next = tabs[(index + delta + tabs.length) % tabs.length];
|
|
82
75
|
next.select();
|
|
@@ -157,11 +150,9 @@ export class VoxTab extends LitElement {
|
|
|
157
150
|
render() {
|
|
158
151
|
return html`
|
|
159
152
|
<button
|
|
160
|
-
id="tab-${this.panel}"
|
|
161
153
|
class="tab"
|
|
162
154
|
role="tab"
|
|
163
155
|
aria-selected=${this.selected ? 'true' : 'false'}
|
|
164
|
-
aria-controls="panel-${this.panel}"
|
|
165
156
|
tabindex=${this.selected ? '0' : '-1'}
|
|
166
157
|
@click=${this.select}
|
|
167
158
|
>
|
|
@@ -194,25 +185,10 @@ export class VoxTabPanel extends LitElement {
|
|
|
194
185
|
:host([active]) {
|
|
195
186
|
display: block;
|
|
196
187
|
}
|
|
197
|
-
|
|
198
|
-
div:focus-visible {
|
|
199
|
-
outline: 2px solid var(--vox-color-brand-1);
|
|
200
|
-
outline-offset: 2px;
|
|
201
|
-
border-radius: var(--vox-radius-sm);
|
|
202
|
-
}
|
|
203
188
|
`;
|
|
204
189
|
|
|
205
190
|
render() {
|
|
206
|
-
return html
|
|
207
|
-
<div
|
|
208
|
-
id="panel-${this.name}"
|
|
209
|
-
role="tabpanel"
|
|
210
|
-
aria-labelledby="tab-${this.name}"
|
|
211
|
-
tabindex="0"
|
|
212
|
-
>
|
|
213
|
-
<slot></slot>
|
|
214
|
-
</div>
|
|
215
|
-
`;
|
|
191
|
+
return html`<div role="tabpanel"><slot></slot></div>`;
|
|
216
192
|
}
|
|
217
193
|
}
|
|
218
194
|
|
|
@@ -59,8 +59,6 @@ export class VoxTextarea extends VoxFieldElement {
|
|
|
59
59
|
?required=${this.required}
|
|
60
60
|
?readonly=${this.readonly}
|
|
61
61
|
?disabled=${this.disabled}
|
|
62
|
-
aria-describedby=${ifDefined(this.noteId)}
|
|
63
|
-
aria-invalid=${this.invalid ? 'true' : 'false'}
|
|
64
62
|
@input=${this.handleInput}
|
|
65
63
|
@change=${this.handleChange}
|
|
66
64
|
></textarea>
|
package/src/index.ts
CHANGED
|
@@ -46,6 +46,7 @@ export { VoxBadge } from './components/badge/vox-badge.js';
|
|
|
46
46
|
export { VoxCalendarTile } from './components/calendar-tile/vox-calendar-tile.js';
|
|
47
47
|
export { VoxCallout } from './components/callout/vox-callout.js';
|
|
48
48
|
export { VoxCard } from './components/card/vox-card.js';
|
|
49
|
+
export { VoxCodeBlock } from './components/code-block/vox-code-block.js';
|
|
49
50
|
export { VoxCtaBand } from './components/cta-band/vox-cta-band.js';
|
|
50
51
|
export { VoxEmptyState } from './components/empty-state/vox-empty-state.js';
|
|
51
52
|
export { VoxFooter, VoxFooterColumn } from './components/footer/vox-footer.js';
|
|
@@ -64,6 +65,7 @@ export { VoxTimeline, VoxTimelineItem } from './components/timeline/vox-timeline
|
|
|
64
65
|
export type { IconName, IconSize } from './components/icon/vox-icon.js';
|
|
65
66
|
export type { ButtonVariant, ButtonSize } from './components/button/vox-button.js';
|
|
66
67
|
export type { CalloutVariant } from './components/callout/vox-callout.js';
|
|
68
|
+
export type { CodeBlockLanguage } from './components/code-block/vox-code-block.js';
|
|
67
69
|
export type { BadgeVariant } from './components/badge/vox-badge.js';
|
|
68
70
|
export type { AlertVariant } from './components/alert/vox-alert.js';
|
|
69
71
|
export type { AvatarSize } from './components/avatar/vox-avatar.js';
|
package/src/internal/field.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LitElement, html, css, nothing } from 'lit';
|
|
2
|
-
import { property
|
|
2
|
+
import { property } from 'lit/decorators.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Shared base for form-associated VoxBlocks controls. Uses ElementInternals
|
|
@@ -10,9 +10,6 @@ export class VoxFieldElement extends LitElement {
|
|
|
10
10
|
|
|
11
11
|
protected internals: ElementInternals;
|
|
12
12
|
|
|
13
|
-
/** Whether the control currently fails validation; drives `aria-invalid`. */
|
|
14
|
-
@state() protected invalid = false;
|
|
15
|
-
|
|
16
13
|
@property() name = '';
|
|
17
14
|
@property() label = '';
|
|
18
15
|
@property() note = '';
|
|
@@ -49,7 +46,6 @@ export class VoxFieldElement extends LitElement {
|
|
|
49
46
|
inner: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,
|
|
50
47
|
) {
|
|
51
48
|
this.internals.setValidity(inner.validity, inner.validationMessage, inner);
|
|
52
|
-
this.invalid = !inner.validity.valid;
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
protected renderLabel(forId: string) {
|
|
@@ -63,13 +59,8 @@ export class VoxFieldElement extends LitElement {
|
|
|
63
59
|
`;
|
|
64
60
|
}
|
|
65
61
|
|
|
66
|
-
/** `note`'s id when present, for `aria-describedby` on the native control. */
|
|
67
|
-
protected get noteId(): string | undefined {
|
|
68
|
-
return this.note ? 'note' : undefined;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
62
|
protected renderNote() {
|
|
72
|
-
return this.note ? html`<p class="note"
|
|
63
|
+
return this.note ? html`<p class="note">${this.note}</p>` : nothing;
|
|
73
64
|
}
|
|
74
65
|
}
|
|
75
66
|
|
|
@@ -128,15 +119,6 @@ export const fieldStyles = css`
|
|
|
128
119
|
box-shadow: 0 0 0 3px var(--vox-color-brand-soft);
|
|
129
120
|
}
|
|
130
121
|
|
|
131
|
-
.control[aria-invalid='true'] {
|
|
132
|
-
border-color: var(--vox-color-danger-1);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.control[aria-invalid='true']:focus {
|
|
136
|
-
border-color: var(--vox-color-danger-1);
|
|
137
|
-
box-shadow: 0 0 0 3px var(--vox-color-danger-soft);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
122
|
/* Corner flattening when placed inside a <vox-input-group>. */
|
|
141
123
|
:host([data-vox-group]) .control {
|
|
142
124
|
border-radius: 0;
|
package/src/tokens/tokens.css
CHANGED
|
@@ -29,10 +29,19 @@
|
|
|
29
29
|
--vox-color-warning-1: #915930;
|
|
30
30
|
--vox-color-warning-soft: rgba(234, 179, 8, 0.14);
|
|
31
31
|
--vox-color-danger-1: #b8272c;
|
|
32
|
-
--vox-color-danger-2: #
|
|
33
|
-
--vox-color-danger-3: #
|
|
32
|
+
--vox-color-danger-2: #d5393e;
|
|
33
|
+
--vox-color-danger-3: #e0575b;
|
|
34
34
|
--vox-color-danger-soft: rgba(244, 63, 94, 0.14);
|
|
35
35
|
|
|
36
|
+
/* Code syntax highlighting — one palette hue per token role */
|
|
37
|
+
--vox-code-comment: var(--vox-palette-gray-700);
|
|
38
|
+
--vox-code-keyword: var(--vox-palette-purple-700);
|
|
39
|
+
--vox-code-string: var(--vox-palette-green-800);
|
|
40
|
+
--vox-code-number: var(--vox-palette-gold-800);
|
|
41
|
+
--vox-code-function: var(--vox-palette-blue-700);
|
|
42
|
+
--vox-code-property: var(--vox-palette-teal-800);
|
|
43
|
+
--vox-code-tag: var(--vox-palette-red-700);
|
|
44
|
+
|
|
36
45
|
/* Surfaces */
|
|
37
46
|
--vox-color-bg: #f5faf9;
|
|
38
47
|
--vox-color-bg-alt: #e8f1ef;
|
|
@@ -42,12 +51,12 @@
|
|
|
42
51
|
/* Text */
|
|
43
52
|
--vox-color-text-1: #12262c;
|
|
44
53
|
--vox-color-text-2: #4c6469;
|
|
45
|
-
--vox-color-text-3: #
|
|
54
|
+
--vox-color-text-3: #86999c;
|
|
46
55
|
--vox-color-text-inverse: #ffffff;
|
|
47
56
|
|
|
48
57
|
/* Lines */
|
|
49
58
|
--vox-color-divider: rgba(18, 38, 44, 0.12);
|
|
50
|
-
--vox-color-border: rgba(18, 38, 44, 0.
|
|
59
|
+
--vox-color-border: rgba(18, 38, 44, 0.28);
|
|
51
60
|
|
|
52
61
|
/* Typography */
|
|
53
62
|
--vox-font-family-base: 'Inter', ui-sans-serif, system-ui, -apple-system,
|
|
@@ -92,10 +101,18 @@
|
|
|
92
101
|
--vox-color-warning-1: #f9b44e;
|
|
93
102
|
--vox-color-warning-soft: rgba(234, 179, 8, 0.16);
|
|
94
103
|
--vox-color-danger-1: #f66f81;
|
|
95
|
-
--vox-color-danger-2: #
|
|
104
|
+
--vox-color-danger-2: #f14158;
|
|
96
105
|
--vox-color-danger-3: #b62a3c;
|
|
97
106
|
--vox-color-danger-soft: rgba(244, 63, 94, 0.16);
|
|
98
107
|
|
|
108
|
+
--vox-code-comment: var(--vox-palette-gray-400);
|
|
109
|
+
--vox-code-keyword: var(--vox-palette-purple-300);
|
|
110
|
+
--vox-code-string: var(--vox-palette-green-400);
|
|
111
|
+
--vox-code-number: var(--vox-palette-gold-400);
|
|
112
|
+
--vox-code-function: var(--vox-palette-blue-300);
|
|
113
|
+
--vox-code-property: var(--vox-palette-teal-400);
|
|
114
|
+
--vox-code-tag: var(--vox-palette-red-300);
|
|
115
|
+
|
|
99
116
|
--vox-color-bg: #0d1417;
|
|
100
117
|
--vox-color-bg-alt: #131b1f;
|
|
101
118
|
--vox-color-bg-soft: #131b1f;
|
|
@@ -103,11 +120,11 @@
|
|
|
103
120
|
|
|
104
121
|
--vox-color-text-1: #dff3f0;
|
|
105
122
|
--vox-color-text-2: #9fb8bb;
|
|
106
|
-
--vox-color-text-3: #
|
|
123
|
+
--vox-color-text-3: #5f7275;
|
|
107
124
|
--vox-color-text-inverse: #ffffff;
|
|
108
125
|
|
|
109
126
|
--vox-color-divider: rgba(159, 184, 187, 0.24);
|
|
110
|
-
--vox-color-border: rgba(159, 184, 187, 0.
|
|
127
|
+
--vox-color-border: rgba(159, 184, 187, 0.5);
|
|
111
128
|
|
|
112
129
|
--vox-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.2), 0 1px 2px rgba(0, 0, 0, 0.24);
|
|
113
130
|
--vox-shadow-2: 0 3px 12px rgba(0, 0, 0, 0.26), 0 1px 4px rgba(0, 0, 0, 0.26);
|