@openvoxproject/voxblocks 0.9.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.
Files changed (97) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +66 -0
  3. package/dist/cdn/voxblocks.css +1 -0
  4. package/dist/cdn/voxblocks.js +5057 -0
  5. package/dist/types/components/accordion/vox-accordion.d.ts +32 -0
  6. package/dist/types/components/alert/vox-alert.d.ts +23 -0
  7. package/dist/types/components/avatar/vox-avatar.d.ts +18 -0
  8. package/dist/types/components/badge/vox-badge.d.ts +17 -0
  9. package/dist/types/components/billboard/vox-billboard.d.ts +22 -0
  10. package/dist/types/components/breadcrumbs/vox-breadcrumbs.d.ts +24 -0
  11. package/dist/types/components/button/vox-button.d.ts +24 -0
  12. package/dist/types/components/calendar-tile/vox-calendar-tile.d.ts +18 -0
  13. package/dist/types/components/callout/vox-callout.d.ts +20 -0
  14. package/dist/types/components/card/vox-card.d.ts +28 -0
  15. package/dist/types/components/checkbox/vox-checkbox.d.ts +20 -0
  16. package/dist/types/components/cta/vox-cta.d.ts +17 -0
  17. package/dist/types/components/cta-band/vox-cta-band.d.ts +22 -0
  18. package/dist/types/components/dialog/vox-dialog.d.ts +30 -0
  19. package/dist/types/components/disclosure/vox-disclosure.d.ts +19 -0
  20. package/dist/types/components/dropdown/vox-dropdown.d.ts +29 -0
  21. package/dist/types/components/empty-state/vox-empty-state.d.ts +18 -0
  22. package/dist/types/components/file-input/vox-file-input.d.ts +22 -0
  23. package/dist/types/components/footer/vox-footer.d.ts +28 -0
  24. package/dist/types/components/grid/vox-grid.d.ts +22 -0
  25. package/dist/types/components/header/vox-header.d.ts +23 -0
  26. package/dist/types/components/hero/vox-hero.d.ts +21 -0
  27. package/dist/types/components/input/vox-input.d.ts +24 -0
  28. package/dist/types/components/input-group/vox-input-group.d.ts +25 -0
  29. package/dist/types/components/link-hub/vox-link-hub.d.ts +27 -0
  30. package/dist/types/components/loader/vox-loader.d.ts +16 -0
  31. package/dist/types/components/pagination/vox-pagination.d.ts +25 -0
  32. package/dist/types/components/quote/vox-quote.d.ts +18 -0
  33. package/dist/types/components/radio/vox-radio-group.d.ts +23 -0
  34. package/dist/types/components/radio/vox-radio.d.ts +21 -0
  35. package/dist/types/components/select/vox-select.d.ts +27 -0
  36. package/dist/types/components/series-nav/vox-series-nav.d.ts +18 -0
  37. package/dist/types/components/sidenav/vox-sidenav.d.ts +50 -0
  38. package/dist/types/components/sponsor/vox-sponsor.d.ts +35 -0
  39. package/dist/types/components/stat/vox-stat.d.ts +17 -0
  40. package/dist/types/components/step-indicator/vox-step-indicator.d.ts +28 -0
  41. package/dist/types/components/subnav/vox-subnav.d.ts +18 -0
  42. package/dist/types/components/switch/vox-switch.d.ts +20 -0
  43. package/dist/types/components/tabs/vox-tabs.d.ts +57 -0
  44. package/dist/types/components/textarea/vox-textarea.d.ts +22 -0
  45. package/dist/types/components/theme-toggle/vox-theme-toggle.d.ts +35 -0
  46. package/dist/types/components/timeline/vox-timeline.d.ts +27 -0
  47. package/dist/types/index.d.ts +51 -0
  48. package/dist/types/internal/field.d.ts +27 -0
  49. package/dist/voxblocks.css +1 -0
  50. package/dist/voxblocks.js +4350 -0
  51. package/package.json +59 -0
  52. package/src/components/accordion/vox-accordion.ts +150 -0
  53. package/src/components/alert/vox-alert.ts +155 -0
  54. package/src/components/avatar/vox-avatar.ts +80 -0
  55. package/src/components/badge/vox-badge.ts +67 -0
  56. package/src/components/billboard/vox-billboard.ts +109 -0
  57. package/src/components/breadcrumbs/vox-breadcrumbs.ts +68 -0
  58. package/src/components/button/vox-button.ts +165 -0
  59. package/src/components/calendar-tile/vox-calendar-tile.ts +74 -0
  60. package/src/components/callout/vox-callout.ts +97 -0
  61. package/src/components/card/vox-card.ts +147 -0
  62. package/src/components/checkbox/vox-checkbox.ts +101 -0
  63. package/src/components/cta/vox-cta.ts +69 -0
  64. package/src/components/cta-band/vox-cta-band.ts +105 -0
  65. package/src/components/dialog/vox-dialog.ts +176 -0
  66. package/src/components/disclosure/vox-disclosure.ts +98 -0
  67. package/src/components/dropdown/vox-dropdown.ts +186 -0
  68. package/src/components/empty-state/vox-empty-state.ts +76 -0
  69. package/src/components/file-input/vox-file-input.ts +127 -0
  70. package/src/components/footer/vox-footer.ts +109 -0
  71. package/src/components/grid/vox-grid.ts +54 -0
  72. package/src/components/header/vox-header.ts +109 -0
  73. package/src/components/hero/vox-hero.ts +100 -0
  74. package/src/components/input/vox-input.ts +72 -0
  75. package/src/components/input-group/vox-input-group.ts +85 -0
  76. package/src/components/link-hub/vox-link-hub.ts +108 -0
  77. package/src/components/loader/vox-loader.ts +82 -0
  78. package/src/components/pagination/vox-pagination.ts +76 -0
  79. package/src/components/quote/vox-quote.ts +82 -0
  80. package/src/components/radio/vox-radio-group.ts +117 -0
  81. package/src/components/radio/vox-radio.ts +102 -0
  82. package/src/components/select/vox-select.ts +89 -0
  83. package/src/components/series-nav/vox-series-nav.ts +94 -0
  84. package/src/components/sidenav/vox-sidenav.ts +192 -0
  85. package/src/components/sponsor/vox-sponsor.ts +187 -0
  86. package/src/components/stat/vox-stat.ts +55 -0
  87. package/src/components/step-indicator/vox-step-indicator.ts +152 -0
  88. package/src/components/subnav/vox-subnav.ts +64 -0
  89. package/src/components/switch/vox-switch.ts +90 -0
  90. package/src/components/tabs/vox-tabs.ts +201 -0
  91. package/src/components/textarea/vox-textarea.ts +75 -0
  92. package/src/components/theme-toggle/vox-theme-toggle.ts +164 -0
  93. package/src/components/timeline/vox-timeline.ts +100 -0
  94. package/src/index.ts +65 -0
  95. package/src/internal/field.ts +165 -0
  96. package/src/styles/utilities.css +409 -0
  97. package/src/tokens/tokens.css +161 -0
@@ -0,0 +1,94 @@
1
+ import { LitElement, html, css, nothing } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+
4
+ /**
5
+ * Previous/next navigation between pages in a series, like the links at
6
+ * the bottom of a docs page.
7
+ */
8
+ @customElement('vox-series-nav')
9
+ export class VoxSeriesNav extends LitElement {
10
+ @property({ attribute: 'previous-href' }) previousHref?: string;
11
+ @property({ attribute: 'previous-label' }) previousLabel = '';
12
+ @property({ attribute: 'next-href' }) nextHref?: string;
13
+ @property({ attribute: 'next-label' }) nextLabel = '';
14
+
15
+ static styles = css`
16
+ :host {
17
+ display: block;
18
+ font-family: var(--vox-font-family-base);
19
+ }
20
+
21
+ nav {
22
+ display: flex;
23
+ justify-content: space-between;
24
+ gap: var(--vox-space-4);
25
+ }
26
+
27
+ a {
28
+ display: flex;
29
+ flex-direction: column;
30
+ gap: 2px;
31
+ flex: 0 1 48%;
32
+ padding: var(--vox-space-3) var(--vox-space-4);
33
+ border: 1px solid var(--vox-color-divider);
34
+ border-radius: var(--vox-radius-md);
35
+ text-decoration: none;
36
+ transition: border-color var(--vox-transition-base);
37
+ }
38
+
39
+ a:hover,
40
+ a:focus-visible {
41
+ border-color: var(--vox-color-brand-1);
42
+ }
43
+
44
+ a:focus-visible {
45
+ outline: 2px solid var(--vox-color-brand-1);
46
+ outline-offset: 2px;
47
+ }
48
+
49
+ .next {
50
+ margin-left: auto;
51
+ text-align: right;
52
+ }
53
+
54
+ .direction {
55
+ font-size: 12px;
56
+ color: var(--vox-color-text-3);
57
+ }
58
+
59
+ .title {
60
+ font-size: 14px;
61
+ font-weight: 600;
62
+ color: var(--vox-color-brand-1);
63
+ }
64
+ `;
65
+
66
+ render() {
67
+ return html`
68
+ <nav aria-label="Series">
69
+ ${this.previousHref
70
+ ? html`
71
+ <a href=${this.previousHref} rel="prev">
72
+ <span class="direction">← Previous</span>
73
+ <span class="title">${this.previousLabel}</span>
74
+ </a>
75
+ `
76
+ : nothing}
77
+ ${this.nextHref
78
+ ? html`
79
+ <a class="next" href=${this.nextHref} rel="next">
80
+ <span class="direction">Next →</span>
81
+ <span class="title">${this.nextLabel}</span>
82
+ </a>
83
+ `
84
+ : nothing}
85
+ </nav>
86
+ `;
87
+ }
88
+ }
89
+
90
+ declare global {
91
+ interface HTMLElementTagNameMap {
92
+ 'vox-series-nav': VoxSeriesNav;
93
+ }
94
+ }
@@ -0,0 +1,192 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+
4
+ /**
5
+ * A vertical section navigation, in the style of the OpenVox docs sidebar.
6
+ *
7
+ * ```html
8
+ * <vox-sidenav>
9
+ * <vox-sidenav-item href="/" current>Overview</vox-sidenav-item>
10
+ * <vox-sidenav-group heading="Installation" open>
11
+ * <vox-sidenav-item href="/install/deb">Debian</vox-sidenav-item>
12
+ * </vox-sidenav-group>
13
+ * </vox-sidenav>
14
+ * ```
15
+ *
16
+ * @slot - `<vox-sidenav-item>` and `<vox-sidenav-group>` elements.
17
+ */
18
+ @customElement('vox-sidenav')
19
+ export class VoxSidenav extends LitElement {
20
+ @property() label = 'Section';
21
+
22
+ static styles = css`
23
+ :host {
24
+ display: block;
25
+ font-family: var(--vox-font-family-base);
26
+ }
27
+
28
+ nav {
29
+ display: flex;
30
+ flex-direction: column;
31
+ gap: 2px;
32
+ }
33
+ `;
34
+
35
+ render() {
36
+ return html`
37
+ <nav aria-label=${this.label}>
38
+ <slot></slot>
39
+ </nav>
40
+ `;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * A collapsible group of sidenav items.
46
+ *
47
+ * @slot - `<vox-sidenav-item>` elements.
48
+ */
49
+ @customElement('vox-sidenav-group')
50
+ export class VoxSidenavGroup extends LitElement {
51
+ @property() heading = '';
52
+ @property({ type: Boolean, reflect: true }) open = false;
53
+
54
+ static styles = css`
55
+ :host {
56
+ display: block;
57
+ font-family: var(--vox-font-family-base);
58
+ }
59
+
60
+ .trigger {
61
+ display: flex;
62
+ align-items: center;
63
+ justify-content: space-between;
64
+ gap: var(--vox-space-2);
65
+ width: 100%;
66
+ padding: var(--vox-space-2) var(--vox-space-3);
67
+ background: none;
68
+ border: none;
69
+ border-radius: var(--vox-radius-sm);
70
+ color: var(--vox-color-text-1);
71
+ font-family: inherit;
72
+ font-size: 14px;
73
+ font-weight: 600;
74
+ text-align: left;
75
+ cursor: pointer;
76
+ }
77
+
78
+ .trigger:hover {
79
+ color: var(--vox-color-brand-1);
80
+ }
81
+
82
+ .trigger:focus-visible {
83
+ outline: 2px solid var(--vox-color-brand-1);
84
+ outline-offset: -2px;
85
+ }
86
+
87
+ .chevron {
88
+ flex: none;
89
+ width: 14px;
90
+ height: 14px;
91
+ transition: transform var(--vox-transition-fast);
92
+ }
93
+
94
+ :host([open]) .chevron {
95
+ transform: rotate(90deg);
96
+ }
97
+
98
+ .items {
99
+ display: none;
100
+ flex-direction: column;
101
+ gap: 2px;
102
+ padding-left: var(--vox-space-3);
103
+ border-left: 1px solid var(--vox-color-divider);
104
+ margin-left: var(--vox-space-3);
105
+ }
106
+
107
+ :host([open]) .items {
108
+ display: flex;
109
+ }
110
+ `;
111
+
112
+ private toggle() {
113
+ this.open = !this.open;
114
+ }
115
+
116
+ render() {
117
+ return html`
118
+ <button
119
+ class="trigger"
120
+ aria-expanded=${this.open ? 'true' : 'false'}
121
+ @click=${this.toggle}
122
+ >
123
+ ${this.heading}
124
+ <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">
125
+ <path d="m9 6 6 6-6 6" />
126
+ </svg>
127
+ </button>
128
+ <div class="items"><slot></slot></div>
129
+ `;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * One sidenav link. Set `current` on the active page's item.
135
+ *
136
+ * @slot - Link text.
137
+ */
138
+ @customElement('vox-sidenav-item')
139
+ export class VoxSidenavItem extends LitElement {
140
+ @property() href = '#';
141
+ @property({ type: Boolean, reflect: true }) current = false;
142
+
143
+ static styles = css`
144
+ :host {
145
+ display: block;
146
+ font-family: var(--vox-font-family-base);
147
+ }
148
+
149
+ a {
150
+ display: block;
151
+ padding: var(--vox-space-2) var(--vox-space-3);
152
+ border-radius: var(--vox-radius-sm);
153
+ color: var(--vox-color-text-2);
154
+ font-size: 14px;
155
+ text-decoration: none;
156
+ transition:
157
+ color var(--vox-transition-fast),
158
+ background-color var(--vox-transition-fast);
159
+ }
160
+
161
+ a:hover {
162
+ color: var(--vox-color-text-1);
163
+ }
164
+
165
+ a:focus-visible {
166
+ outline: 2px solid var(--vox-color-brand-1);
167
+ outline-offset: -2px;
168
+ }
169
+
170
+ :host([current]) a {
171
+ background-color: var(--vox-color-brand-soft);
172
+ color: var(--vox-color-brand-1);
173
+ font-weight: 600;
174
+ }
175
+ `;
176
+
177
+ render() {
178
+ return html`
179
+ <a href=${this.href} aria-current=${this.current ? 'page' : 'false'}>
180
+ <slot></slot>
181
+ </a>
182
+ `;
183
+ }
184
+ }
185
+
186
+ declare global {
187
+ interface HTMLElementTagNameMap {
188
+ 'vox-sidenav': VoxSidenav;
189
+ 'vox-sidenav-group': VoxSidenavGroup;
190
+ 'vox-sidenav-item': VoxSidenavItem;
191
+ }
192
+ }
@@ -0,0 +1,187 @@
1
+ import { LitElement, html, css, nothing } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+
4
+ /**
5
+ * A group of sponsors under a shared tier, for sponsorship/funding pages.
6
+ *
7
+ * @slot description - Optional text under the heading, e.g. what the tier means.
8
+ * @slot - `<vox-sponsor>` elements.
9
+ */
10
+ @customElement('vox-sponsor-tier')
11
+ export class VoxSponsorTier extends LitElement {
12
+ @property() heading = '';
13
+
14
+ static styles = css`
15
+ :host {
16
+ display: block;
17
+ font-family: var(--vox-font-family-base);
18
+ }
19
+
20
+ .heading {
21
+ margin: 0;
22
+ font-size: 22px;
23
+ font-weight: 700;
24
+ line-height: 1.3;
25
+ color: var(--vox-color-text-1);
26
+ }
27
+
28
+ .description {
29
+ margin: var(--vox-space-2) 0 0;
30
+ font-size: 15px;
31
+ line-height: 1.7;
32
+ color: var(--vox-color-text-2);
33
+ }
34
+
35
+ .description:not(.has-content) {
36
+ display: none;
37
+ }
38
+
39
+ .grid {
40
+ display: grid;
41
+ grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
42
+ gap: var(--vox-space-4);
43
+ margin-top: var(--vox-space-6);
44
+ }
45
+ `;
46
+
47
+ private hasDescription = false;
48
+
49
+ private handleDescriptionSlotChange(event: Event) {
50
+ const slot = event.target as HTMLSlotElement;
51
+ this.hasDescription = slot.assignedNodes({ flatten: true }).length > 0;
52
+ this.requestUpdate();
53
+ }
54
+
55
+ render() {
56
+ return html`
57
+ <h2 class="heading">${this.heading}</h2>
58
+ <div class="description ${this.hasDescription ? 'has-content' : ''}">
59
+ <slot
60
+ name="description"
61
+ @slotchange=${this.handleDescriptionSlotChange}
62
+ ></slot>
63
+ </div>
64
+ <div class="grid">
65
+ <slot></slot>
66
+ </div>
67
+ `;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * One sponsor card in a `<vox-sponsor-tier>`, linking to the sponsor's site.
73
+ *
74
+ * @slot - Optional description, e.g. contribution type and amount.
75
+ */
76
+ @customElement('vox-sponsor')
77
+ export class VoxSponsor extends LitElement {
78
+ @property() name = '';
79
+ @property() href?: string;
80
+ @property() logo?: string;
81
+ @property() target?: string;
82
+
83
+ static styles = css`
84
+ :host {
85
+ display: block;
86
+ }
87
+
88
+ .sponsor {
89
+ display: flex;
90
+ flex-direction: column;
91
+ height: 100%;
92
+ box-sizing: border-box;
93
+ gap: var(--vox-space-3);
94
+ padding: var(--vox-space-4);
95
+ background-color: var(--vox-color-bg-soft);
96
+ border: 1px solid var(--vox-color-bg-soft);
97
+ border-radius: var(--vox-radius-lg);
98
+ font-family: var(--vox-font-family-base);
99
+ text-decoration: none;
100
+ transition: border-color var(--vox-transition-base);
101
+ }
102
+
103
+ a.sponsor:hover,
104
+ a.sponsor:focus-visible {
105
+ border-color: var(--vox-color-brand-1);
106
+ }
107
+
108
+ a.sponsor:focus-visible {
109
+ outline: 2px solid var(--vox-color-brand-1);
110
+ outline-offset: 2px;
111
+ }
112
+
113
+ .logo {
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ height: 64px;
118
+ background-color: var(--vox-color-bg-elv);
119
+ border-radius: var(--vox-radius-md);
120
+ }
121
+
122
+ .logo:not(.has-logo) {
123
+ display: none;
124
+ }
125
+
126
+ .logo img {
127
+ max-width: 80%;
128
+ max-height: 40px;
129
+ object-fit: contain;
130
+ }
131
+
132
+ .name {
133
+ font-size: 15px;
134
+ font-weight: 600;
135
+ line-height: 1.4;
136
+ color: var(--vox-color-text-1);
137
+ }
138
+
139
+ .body {
140
+ font-size: 13px;
141
+ line-height: 1.6;
142
+ color: var(--vox-color-text-2);
143
+ }
144
+
145
+ .body:not(.has-content) {
146
+ display: none;
147
+ }
148
+ `;
149
+
150
+ private hasBody = false;
151
+
152
+ private handleBodySlotChange(event: Event) {
153
+ const slot = event.target as HTMLSlotElement;
154
+ this.hasBody = slot.assignedNodes({ flatten: true }).length > 0;
155
+ this.requestUpdate();
156
+ }
157
+
158
+ render() {
159
+ const inner = html`
160
+ <div class="logo ${this.logo ? 'has-logo' : ''}">
161
+ ${this.logo
162
+ ? html`<img src=${this.logo} alt=${this.name} />`
163
+ : nothing}
164
+ </div>
165
+ <span class="name">${this.name}</span>
166
+ <div class="body ${this.hasBody ? 'has-content' : ''}">
167
+ <slot @slotchange=${this.handleBodySlotChange}></slot>
168
+ </div>
169
+ `;
170
+
171
+ return this.href !== undefined
172
+ ? html`<a
173
+ class="sponsor"
174
+ href=${this.href}
175
+ target=${this.target ?? nothing}
176
+ >${inner}</a
177
+ >`
178
+ : html`<div class="sponsor">${inner}</div>`;
179
+ }
180
+ }
181
+
182
+ declare global {
183
+ interface HTMLElementTagNameMap {
184
+ 'vox-sponsor-tier': VoxSponsorTier;
185
+ 'vox-sponsor': VoxSponsor;
186
+ }
187
+ }
@@ -0,0 +1,55 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+
4
+ /**
5
+ * A large statistic with a label, e.g. module counts or contributors.
6
+ *
7
+ * @slot - Optional supporting description.
8
+ */
9
+ @customElement('vox-stat')
10
+ export class VoxStat extends LitElement {
11
+ @property() value = '';
12
+ @property() label = '';
13
+
14
+ static styles = css`
15
+ :host {
16
+ display: block;
17
+ font-family: var(--vox-font-family-base);
18
+ }
19
+
20
+ .value {
21
+ font-size: 40px;
22
+ font-weight: 700;
23
+ line-height: 1.1;
24
+ color: var(--vox-color-brand-1);
25
+ }
26
+
27
+ .label {
28
+ margin-top: var(--vox-space-1);
29
+ font-size: 14px;
30
+ font-weight: 600;
31
+ color: var(--vox-color-text-1);
32
+ }
33
+
34
+ .description {
35
+ margin-top: var(--vox-space-1);
36
+ font-size: 13px;
37
+ line-height: 1.6;
38
+ color: var(--vox-color-text-2);
39
+ }
40
+ `;
41
+
42
+ render() {
43
+ return html`
44
+ <div class="value">${this.value}</div>
45
+ <div class="label">${this.label}</div>
46
+ <div class="description"><slot></slot></div>
47
+ `;
48
+ }
49
+ }
50
+
51
+ declare global {
52
+ interface HTMLElementTagNameMap {
53
+ 'vox-stat': VoxStat;
54
+ }
55
+ }
@@ -0,0 +1,152 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+
4
+ export type StepState = 'complete' | 'current' | 'upcoming';
5
+
6
+ /**
7
+ * Shows progress through a multi-step process.
8
+ *
9
+ * @slot - `<vox-step>` elements.
10
+ */
11
+ @customElement('vox-step-indicator')
12
+ export class VoxStepIndicator extends LitElement {
13
+ static styles = css`
14
+ :host {
15
+ display: block;
16
+ }
17
+
18
+ .steps {
19
+ display: flex;
20
+ align-items: flex-start;
21
+ }
22
+
23
+ ::slotted(vox-step) {
24
+ flex: 1 1 0;
25
+ }
26
+ `;
27
+
28
+ private numberSteps() {
29
+ this.querySelectorAll('vox-step').forEach((step, index) => {
30
+ (step as VoxStep).number = index + 1;
31
+ });
32
+ }
33
+
34
+ render() {
35
+ return html`
36
+ <div class="steps" role="list" aria-label="Progress">
37
+ <slot @slotchange=${this.numberSteps}></slot>
38
+ </div>
39
+ `;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * One step in a `<vox-step-indicator>`.
45
+ */
46
+ @customElement('vox-step')
47
+ export class VoxStep extends LitElement {
48
+ @property() label = '';
49
+ @property({ reflect: true }) state: StepState = 'upcoming';
50
+ @property({ type: Number }) number = 1;
51
+
52
+ static styles = css`
53
+ :host {
54
+ display: block;
55
+ font-family: var(--vox-font-family-base);
56
+ }
57
+
58
+ .step {
59
+ position: relative;
60
+ display: flex;
61
+ flex-direction: column;
62
+ align-items: center;
63
+ text-align: center;
64
+ gap: var(--vox-space-2);
65
+ padding: 0 var(--vox-space-2);
66
+ }
67
+
68
+ /* Connector line to the previous step. */
69
+ :host(:not(:first-child)) .step::before {
70
+ content: '';
71
+ position: absolute;
72
+ top: 14px;
73
+ right: 50%;
74
+ width: 100%;
75
+ height: 2px;
76
+ background-color: var(--vox-color-divider);
77
+ z-index: 0;
78
+ }
79
+
80
+ :host([state='complete']) .step::before {
81
+ background-color: var(--vox-color-brand-3);
82
+ }
83
+
84
+ .marker {
85
+ position: relative;
86
+ z-index: 1;
87
+ display: flex;
88
+ align-items: center;
89
+ justify-content: center;
90
+ width: 28px;
91
+ height: 28px;
92
+ border-radius: 50%;
93
+ border: 2px solid var(--vox-color-divider);
94
+ background-color: var(--vox-color-bg);
95
+ color: var(--vox-color-text-2);
96
+ font-size: 13px;
97
+ font-weight: 600;
98
+ }
99
+
100
+ :host([state='complete']) .marker {
101
+ background-color: var(--vox-color-brand-3);
102
+ border-color: var(--vox-color-brand-3);
103
+ color: var(--vox-color-text-inverse);
104
+ }
105
+
106
+ :host([state='current']) .marker {
107
+ border-color: var(--vox-color-brand-3);
108
+ color: var(--vox-color-brand-1);
109
+ }
110
+
111
+ .marker svg {
112
+ width: 14px;
113
+ height: 14px;
114
+ }
115
+
116
+ .label {
117
+ font-size: 13px;
118
+ color: var(--vox-color-text-2);
119
+ }
120
+
121
+ :host([state='current']) .label {
122
+ font-weight: 600;
123
+ color: var(--vox-color-text-1);
124
+ }
125
+ `;
126
+
127
+ render() {
128
+ return html`
129
+ <div
130
+ class="step"
131
+ role="listitem"
132
+ aria-current=${this.state === 'current' ? 'step' : 'false'}
133
+ >
134
+ <span class="marker">
135
+ ${this.state === 'complete'
136
+ ? html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
137
+ <path d="m4 12 5 5L20 6" />
138
+ </svg>`
139
+ : this.number}
140
+ </span>
141
+ <span class="label">${this.label}</span>
142
+ </div>
143
+ `;
144
+ }
145
+ }
146
+
147
+ declare global {
148
+ interface HTMLElementTagNameMap {
149
+ 'vox-step-indicator': VoxStepIndicator;
150
+ 'vox-step': VoxStep;
151
+ }
152
+ }