@orange-halo/components 1.0.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/custom-elements.json +186 -0
- package/dist/base.d.ts +30 -0
- package/dist/base.d.ts.map +1 -0
- package/dist/base.js +78 -0
- package/dist/base.js.map +1 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/oh-accordion.d.ts +47 -0
- package/dist/oh-accordion.d.ts.map +1 -0
- package/dist/oh-accordion.js +387 -0
- package/dist/oh-accordion.js.map +1 -0
- package/dist/oh-alert.d.ts +48 -0
- package/dist/oh-alert.d.ts.map +1 -0
- package/dist/oh-alert.js +343 -0
- package/dist/oh-alert.js.map +1 -0
- package/dist/oh-badge.d.ts +38 -0
- package/dist/oh-badge.d.ts.map +1 -0
- package/dist/oh-badge.js +176 -0
- package/dist/oh-badge.js.map +1 -0
- package/dist/oh-breadcrumb.d.ts +39 -0
- package/dist/oh-breadcrumb.d.ts.map +1 -0
- package/dist/oh-breadcrumb.js +277 -0
- package/dist/oh-breadcrumb.js.map +1 -0
- package/dist/oh-button.d.ts +62 -0
- package/dist/oh-button.d.ts.map +1 -0
- package/dist/oh-button.figma.d.ts +2 -0
- package/dist/oh-button.figma.d.ts.map +1 -0
- package/dist/oh-button.figma.js +20 -0
- package/dist/oh-button.figma.js.map +1 -0
- package/dist/oh-button.js +490 -0
- package/dist/oh-button.js.map +1 -0
- package/dist/oh-card.d.ts +44 -0
- package/dist/oh-card.d.ts.map +1 -0
- package/dist/oh-card.js +340 -0
- package/dist/oh-card.js.map +1 -0
- package/dist/oh-checkbox-group.d.ts +39 -0
- package/dist/oh-checkbox-group.d.ts.map +1 -0
- package/dist/oh-checkbox-group.figma.d.ts +2 -0
- package/dist/oh-checkbox-group.figma.d.ts.map +1 -0
- package/dist/oh-checkbox-group.figma.js +24 -0
- package/dist/oh-checkbox-group.figma.js.map +1 -0
- package/dist/oh-checkbox-group.js +309 -0
- package/dist/oh-checkbox-group.js.map +1 -0
- package/dist/oh-checkbox.d.ts +50 -0
- package/dist/oh-checkbox.d.ts.map +1 -0
- package/dist/oh-checkbox.js +315 -0
- package/dist/oh-checkbox.js.map +1 -0
- package/dist/oh-cluster.d.ts +38 -0
- package/dist/oh-cluster.d.ts.map +1 -0
- package/dist/oh-cluster.js +141 -0
- package/dist/oh-cluster.js.map +1 -0
- package/dist/oh-container.d.ts +38 -0
- package/dist/oh-container.d.ts.map +1 -0
- package/dist/oh-container.js +116 -0
- package/dist/oh-container.js.map +1 -0
- package/dist/oh-drawer.d.ts +66 -0
- package/dist/oh-drawer.d.ts.map +1 -0
- package/dist/oh-drawer.js +284 -0
- package/dist/oh-drawer.js.map +1 -0
- package/dist/oh-footer.d.ts +40 -0
- package/dist/oh-footer.d.ts.map +1 -0
- package/dist/oh-footer.js +112 -0
- package/dist/oh-footer.js.map +1 -0
- package/dist/oh-grid.d.ts +40 -0
- package/dist/oh-grid.d.ts.map +1 -0
- package/dist/oh-grid.js +168 -0
- package/dist/oh-grid.js.map +1 -0
- package/dist/oh-hero.d.ts +28 -0
- package/dist/oh-hero.d.ts.map +1 -0
- package/dist/oh-hero.js +242 -0
- package/dist/oh-hero.js.map +1 -0
- package/dist/oh-icon.d.ts +44 -0
- package/dist/oh-icon.d.ts.map +1 -0
- package/dist/oh-icon.js +321 -0
- package/dist/oh-icon.js.map +1 -0
- package/dist/oh-input.d.ts +56 -0
- package/dist/oh-input.d.ts.map +1 -0
- package/dist/oh-input.figma.d.ts +2 -0
- package/dist/oh-input.figma.d.ts.map +1 -0
- package/dist/oh-input.figma.js +36 -0
- package/dist/oh-input.figma.js.map +1 -0
- package/dist/oh-input.js +320 -0
- package/dist/oh-input.js.map +1 -0
- package/dist/oh-link.d.ts +45 -0
- package/dist/oh-link.d.ts.map +1 -0
- package/dist/oh-link.js +392 -0
- package/dist/oh-link.js.map +1 -0
- package/dist/oh-modal.d.ts +54 -0
- package/dist/oh-modal.d.ts.map +1 -0
- package/dist/oh-modal.js +450 -0
- package/dist/oh-modal.js.map +1 -0
- package/dist/oh-navbar.d.ts +49 -0
- package/dist/oh-navbar.d.ts.map +1 -0
- package/dist/oh-navbar.js +185 -0
- package/dist/oh-navbar.js.map +1 -0
- package/dist/oh-progress.d.ts +56 -0
- package/dist/oh-progress.d.ts.map +1 -0
- package/dist/oh-progress.js +299 -0
- package/dist/oh-progress.js.map +1 -0
- package/dist/oh-radio-group.d.ts +43 -0
- package/dist/oh-radio-group.d.ts.map +1 -0
- package/dist/oh-radio-group.figma.d.ts +2 -0
- package/dist/oh-radio-group.figma.d.ts.map +1 -0
- package/dist/oh-radio-group.figma.js +22 -0
- package/dist/oh-radio-group.figma.js.map +1 -0
- package/dist/oh-radio-group.js +324 -0
- package/dist/oh-radio-group.js.map +1 -0
- package/dist/oh-radio.d.ts +50 -0
- package/dist/oh-radio.d.ts.map +1 -0
- package/dist/oh-radio.js +314 -0
- package/dist/oh-radio.js.map +1 -0
- package/dist/oh-select.d.ts +82 -0
- package/dist/oh-select.d.ts.map +1 -0
- package/dist/oh-select.js +638 -0
- package/dist/oh-select.js.map +1 -0
- package/dist/oh-skip-link.d.ts +37 -0
- package/dist/oh-skip-link.d.ts.map +1 -0
- package/dist/oh-skip-link.js +145 -0
- package/dist/oh-skip-link.js.map +1 -0
- package/dist/oh-stack.d.ts +36 -0
- package/dist/oh-stack.d.ts.map +1 -0
- package/dist/oh-stack.js +112 -0
- package/dist/oh-stack.js.map +1 -0
- package/dist/oh-tabs.d.ts +85 -0
- package/dist/oh-tabs.d.ts.map +1 -0
- package/dist/oh-tabs.js +417 -0
- package/dist/oh-tabs.js.map +1 -0
- package/dist/oh-toggle.d.ts +51 -0
- package/dist/oh-toggle.d.ts.map +1 -0
- package/dist/oh-toggle.js +219 -0
- package/dist/oh-toggle.js.map +1 -0
- package/dist/styles/typography.d.ts +15 -0
- package/dist/styles/typography.d.ts.map +1 -0
- package/dist/styles/typography.js +235 -0
- package/dist/styles/typography.js.map +1 -0
- package/package.json +140 -0
package/dist/oh-tabs.js
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* oh-tabs
|
|
3
|
+
*
|
|
4
|
+
* Accessible tabbed interface with full keyboard support.
|
|
5
|
+
*
|
|
6
|
+
* ACCESSIBILITY FEATURES:
|
|
7
|
+
* ───────────────────────
|
|
8
|
+
* - Full ARIA support (tablist, tab, tabpanel roles)
|
|
9
|
+
* - Roving tabindex for keyboard navigation
|
|
10
|
+
* - Manual activation (arrow keys move focus, Enter/Space activates)
|
|
11
|
+
* - Home/End jump to first/last tab
|
|
12
|
+
* - Focus moves to panel content after activation
|
|
13
|
+
* - Focus indicators meet WCAG requirements
|
|
14
|
+
* - Panels properly hidden from assistive tech when inactive
|
|
15
|
+
*
|
|
16
|
+
* KEYBOARD INTERACTIONS:
|
|
17
|
+
* ──────────────────────
|
|
18
|
+
* - Left Arrow: Move focus to previous tab (wraps to last)
|
|
19
|
+
* - Right Arrow: Move focus to next tab (wraps to first)
|
|
20
|
+
* - Home: Move focus to first tab
|
|
21
|
+
* - End: Move focus to last tab
|
|
22
|
+
* - Enter/Space: Activate focused tab and move focus to panel
|
|
23
|
+
* - Tab: Move focus out of tablist
|
|
24
|
+
*
|
|
25
|
+
* @element oh-tabs
|
|
26
|
+
*/
|
|
27
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
28
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
29
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
30
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
31
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
32
|
+
};
|
|
33
|
+
import { html, css } from "lit";
|
|
34
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
35
|
+
import { OhElement, baseStyles } from "./base.js";
|
|
36
|
+
let OhTabs = class OhTabs extends OhElement {
|
|
37
|
+
constructor() {
|
|
38
|
+
super(...arguments);
|
|
39
|
+
/**
|
|
40
|
+
* Array of tab items with id, label, and optional content
|
|
41
|
+
*/
|
|
42
|
+
this.tabs = [];
|
|
43
|
+
/**
|
|
44
|
+
* ID of the currently selected tab
|
|
45
|
+
*/
|
|
46
|
+
this.selected = "";
|
|
47
|
+
/**
|
|
48
|
+
* Orientation of the tabs (affects arrow key behavior)
|
|
49
|
+
*/
|
|
50
|
+
this.orientation = "horizontal";
|
|
51
|
+
/**
|
|
52
|
+
* Index of the currently focused tab (for roving tabindex)
|
|
53
|
+
*/
|
|
54
|
+
this._focusedIndex = 0;
|
|
55
|
+
/**
|
|
56
|
+
* Unique ID prefix for ARIA relationships
|
|
57
|
+
*/
|
|
58
|
+
this._idPrefix = `oh-tabs-${Math.random().toString(36).substr(2, 9)}`;
|
|
59
|
+
}
|
|
60
|
+
static { this.styles = [
|
|
61
|
+
baseStyles,
|
|
62
|
+
css `
|
|
63
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
64
|
+
ORANGE HALO TABS
|
|
65
|
+
Chunky • Playful • Accessible
|
|
66
|
+
═══════════════════════════════════════════════════════════════ */
|
|
67
|
+
|
|
68
|
+
:host {
|
|
69
|
+
display: block;
|
|
70
|
+
font-family: var(--oh-font-family-sans);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
74
|
+
TAB LIST CONTAINER
|
|
75
|
+
═══════════════════════════════════════════════════════════════ */
|
|
76
|
+
|
|
77
|
+
.tablist {
|
|
78
|
+
display: flex;
|
|
79
|
+
gap: var(--oh-spacing-1);
|
|
80
|
+
padding-bottom: 0;
|
|
81
|
+
margin-bottom: 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
85
|
+
INDIVIDUAL TAB BUTTONS
|
|
86
|
+
Clean inactive state, chunky selected state
|
|
87
|
+
═══════════════════════════════════════════════════════════════ */
|
|
88
|
+
|
|
89
|
+
.tab {
|
|
90
|
+
position: relative;
|
|
91
|
+
padding: var(--oh-spacing-3) var(--oh-spacing-5);
|
|
92
|
+
font-family: inherit;
|
|
93
|
+
font-size: var(--oh-font-size-base);
|
|
94
|
+
font-weight: var(--oh-font-weight-medium);
|
|
95
|
+
color: var(--oh-color-brand-primary);
|
|
96
|
+
background: transparent;
|
|
97
|
+
border: var(--oh-input-base-border-width) solid transparent;
|
|
98
|
+
border-bottom: none;
|
|
99
|
+
border-radius: var(--oh-radius-md) var(--oh-radius-md) 0 0;
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
|
|
102
|
+
/* No shadow for inactive tabs */
|
|
103
|
+
box-shadow: none;
|
|
104
|
+
|
|
105
|
+
/* Position above the border line */
|
|
106
|
+
margin-bottom: -2.5px;
|
|
107
|
+
|
|
108
|
+
transition:
|
|
109
|
+
color var(--oh-motion-snappy) ease,
|
|
110
|
+
background-color var(--oh-motion-snappy) ease,
|
|
111
|
+
border-color var(--oh-motion-snappy) ease,
|
|
112
|
+
box-shadow var(--oh-motion-snappy) ease;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Dashed underline pseudo-element for animation */
|
|
116
|
+
.tab__label {
|
|
117
|
+
position: relative;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.tab:not([aria-selected="true"]) .tab__label::after {
|
|
121
|
+
content: '';
|
|
122
|
+
position: absolute;
|
|
123
|
+
left: 0;
|
|
124
|
+
right: 0;
|
|
125
|
+
bottom: -3px;
|
|
126
|
+
height: 2px;
|
|
127
|
+
background: repeating-linear-gradient(
|
|
128
|
+
to right,
|
|
129
|
+
var(--oh-color-brand-primary) 0,
|
|
130
|
+
var(--oh-color-brand-primary) 4px,
|
|
131
|
+
transparent 4px,
|
|
132
|
+
transparent 8px
|
|
133
|
+
);
|
|
134
|
+
transform-origin: left;
|
|
135
|
+
transform: scaleX(0);
|
|
136
|
+
transition: transform var(--oh-motion-relaxed) ease;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.tab:hover:not([aria-selected="true"]) .tab__label::after {
|
|
140
|
+
transform: scaleX(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.tab:active:not([aria-selected="true"]) .tab__label::after {
|
|
144
|
+
transform: scaleX(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
148
|
+
SELECTED TAB STATE
|
|
149
|
+
Uses grey for a softer look (4.3:1 contrast against cream bg)
|
|
150
|
+
═══════════════════════════════════════════════════════════════ */
|
|
151
|
+
|
|
152
|
+
.tab[aria-selected="true"] {
|
|
153
|
+
color: var(--oh-color-text-primary);
|
|
154
|
+
background: var(--oh-color-surface-base);
|
|
155
|
+
border-color: var(--oh-color-border-emphasis);
|
|
156
|
+
border-bottom-color: transparent;
|
|
157
|
+
z-index: 1;
|
|
158
|
+
/* Extend down to overlap the content panel's top border */
|
|
159
|
+
margin-bottom: calc(-1 * var(--oh-input-base-border-width));
|
|
160
|
+
padding-bottom: calc(var(--oh-spacing-3) + var(--oh-input-base-border-width));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
164
|
+
FOCUS STATE
|
|
165
|
+
Accessible underline indicator
|
|
166
|
+
═══════════════════════════════════════════════════════════════ */
|
|
167
|
+
|
|
168
|
+
.tab:focus {
|
|
169
|
+
outline: none;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.tab:focus-visible {
|
|
173
|
+
outline: none;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.tab:focus-visible .tab__label {
|
|
177
|
+
text-decoration: underline;
|
|
178
|
+
text-underline-offset: var(--oh-spacing-1);
|
|
179
|
+
text-decoration-thickness: 2px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.tab__label {
|
|
183
|
+
position: relative;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
187
|
+
TAB PANEL
|
|
188
|
+
Content area with warm styling, rounded top-right corner
|
|
189
|
+
═══════════════════════════════════════════════════════════════ */
|
|
190
|
+
|
|
191
|
+
.tabpanel {
|
|
192
|
+
padding: var(--oh-spacing-6);
|
|
193
|
+
background: var(--oh-color-surface-base);
|
|
194
|
+
border: var(--oh-input-base-border-width) solid var(--oh-color-border-emphasis);
|
|
195
|
+
border-radius: 0 var(--oh-radius-md) var(--oh-radius-md) var(--oh-radius-md);
|
|
196
|
+
color: var(--oh-color-text-primary);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* When first tab is not selected, round all corners */
|
|
200
|
+
.tabpanel--rounded-top-left {
|
|
201
|
+
border-radius: var(--oh-radius-md);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.tabpanel:focus {
|
|
205
|
+
outline: none;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.tabpanel:focus-visible {
|
|
209
|
+
outline: 2px solid var(--oh-color-border-emphasis);
|
|
210
|
+
outline-offset: -2px;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* Hidden panels - properly hidden from assistive tech */
|
|
214
|
+
.tabpanel[hidden] {
|
|
215
|
+
display: none;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
219
|
+
REDUCED MOTION
|
|
220
|
+
═══════════════════════════════════════════════════════════════ */
|
|
221
|
+
|
|
222
|
+
@media (prefers-reduced-motion: reduce) {
|
|
223
|
+
.tab {
|
|
224
|
+
transition-duration: 0.01ms !important;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
`,
|
|
228
|
+
]; }
|
|
229
|
+
connectedCallback() {
|
|
230
|
+
super.connectedCallback();
|
|
231
|
+
// Set initial selected tab if not specified
|
|
232
|
+
if (!this.selected && this.tabs.length > 0) {
|
|
233
|
+
this.selected = this.tabs[0].id;
|
|
234
|
+
}
|
|
235
|
+
// Set initial focused index to match selected
|
|
236
|
+
this._focusedIndex = Math.max(0, this.tabs.findIndex(t => t.id === this.selected));
|
|
237
|
+
}
|
|
238
|
+
updated(changedProperties) {
|
|
239
|
+
if (changedProperties.has("tabs") && this.tabs.length > 0 && !this.selected) {
|
|
240
|
+
this.selected = this.tabs[0].id;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Handle keyboard navigation within the tablist
|
|
245
|
+
* Uses manual activation: arrows move focus, Enter/Space activates
|
|
246
|
+
*/
|
|
247
|
+
async _handleKeyDown(e) {
|
|
248
|
+
const { key } = e;
|
|
249
|
+
const tabCount = this.tabs.length;
|
|
250
|
+
if (tabCount === 0)
|
|
251
|
+
return;
|
|
252
|
+
let newIndex = this._focusedIndex;
|
|
253
|
+
let handled = false;
|
|
254
|
+
let shouldActivate = false;
|
|
255
|
+
switch (key) {
|
|
256
|
+
case "ArrowLeft":
|
|
257
|
+
if (this.orientation === "horizontal") {
|
|
258
|
+
newIndex = this._focusedIndex === 0 ? tabCount - 1 : this._focusedIndex - 1;
|
|
259
|
+
handled = true;
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
case "ArrowRight":
|
|
263
|
+
if (this.orientation === "horizontal") {
|
|
264
|
+
newIndex = this._focusedIndex === tabCount - 1 ? 0 : this._focusedIndex + 1;
|
|
265
|
+
handled = true;
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
case "ArrowUp":
|
|
269
|
+
if (this.orientation === "vertical") {
|
|
270
|
+
newIndex = this._focusedIndex === 0 ? tabCount - 1 : this._focusedIndex - 1;
|
|
271
|
+
handled = true;
|
|
272
|
+
}
|
|
273
|
+
break;
|
|
274
|
+
case "ArrowDown":
|
|
275
|
+
if (this.orientation === "vertical") {
|
|
276
|
+
newIndex = this._focusedIndex === tabCount - 1 ? 0 : this._focusedIndex + 1;
|
|
277
|
+
handled = true;
|
|
278
|
+
}
|
|
279
|
+
break;
|
|
280
|
+
case "Home":
|
|
281
|
+
newIndex = 0;
|
|
282
|
+
handled = true;
|
|
283
|
+
break;
|
|
284
|
+
case "End":
|
|
285
|
+
newIndex = tabCount - 1;
|
|
286
|
+
handled = true;
|
|
287
|
+
break;
|
|
288
|
+
case "Enter":
|
|
289
|
+
case " ": // Space
|
|
290
|
+
shouldActivate = true;
|
|
291
|
+
handled = true;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
if (handled) {
|
|
295
|
+
e.preventDefault();
|
|
296
|
+
if (shouldActivate) {
|
|
297
|
+
// Activate the currently focused tab
|
|
298
|
+
this._selectTab(this.tabs[this._focusedIndex].id);
|
|
299
|
+
// Wait for render to complete, then move focus to panel
|
|
300
|
+
await this.updateComplete;
|
|
301
|
+
this._focusPanel(this.tabs[this._focusedIndex].id);
|
|
302
|
+
}
|
|
303
|
+
else if (newIndex !== this._focusedIndex) {
|
|
304
|
+
// Just move focus to the new tab (don't activate)
|
|
305
|
+
this._focusedIndex = newIndex;
|
|
306
|
+
this._focusTab(newIndex);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Focus the panel for a given tab
|
|
312
|
+
*/
|
|
313
|
+
_focusPanel(tabId) {
|
|
314
|
+
const panelId = `${this._idPrefix}-panel-${tabId}`;
|
|
315
|
+
const panel = this.shadowRoot?.getElementById(panelId);
|
|
316
|
+
panel?.focus();
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Focus a specific tab button
|
|
320
|
+
*/
|
|
321
|
+
_focusTab(index) {
|
|
322
|
+
const tabButtons = this.shadowRoot?.querySelectorAll('.tab');
|
|
323
|
+
tabButtons?.[index]?.focus();
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Select a tab and dispatch change event
|
|
327
|
+
*/
|
|
328
|
+
_selectTab(tabId) {
|
|
329
|
+
if (this.selected !== tabId) {
|
|
330
|
+
this.selected = tabId;
|
|
331
|
+
this._focusedIndex = this.tabs.findIndex(t => t.id === tabId);
|
|
332
|
+
this.dispatchEvent(new CustomEvent("tab-change", {
|
|
333
|
+
detail: { tabId },
|
|
334
|
+
bubbles: true,
|
|
335
|
+
composed: true,
|
|
336
|
+
}));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Handle click on a tab
|
|
341
|
+
*/
|
|
342
|
+
_handleTabClick(tabId, index) {
|
|
343
|
+
this._focusedIndex = index;
|
|
344
|
+
this._selectTab(tabId);
|
|
345
|
+
}
|
|
346
|
+
render() {
|
|
347
|
+
return html `
|
|
348
|
+
<div class="tabs-container">
|
|
349
|
+
<!-- Tab List -->
|
|
350
|
+
<div
|
|
351
|
+
class="tablist"
|
|
352
|
+
role="tablist"
|
|
353
|
+
aria-orientation=${this.orientation}
|
|
354
|
+
@keydown=${this._handleKeyDown}
|
|
355
|
+
>
|
|
356
|
+
${this.tabs.map((tab, index) => {
|
|
357
|
+
const isSelected = tab.id === this.selected;
|
|
358
|
+
const tabId = `${this._idPrefix}-tab-${tab.id}`;
|
|
359
|
+
const panelId = `${this._idPrefix}-panel-${tab.id}`;
|
|
360
|
+
return html `
|
|
361
|
+
<button
|
|
362
|
+
class="tab"
|
|
363
|
+
role="tab"
|
|
364
|
+
id=${tabId}
|
|
365
|
+
aria-selected=${isSelected ? "true" : "false"}
|
|
366
|
+
aria-controls=${panelId}
|
|
367
|
+
tabindex=${index === this._focusedIndex ? "0" : "-1"}
|
|
368
|
+
@click=${() => this._handleTabClick(tab.id, index)}
|
|
369
|
+
>
|
|
370
|
+
<span class="tab__label">${tab.label}</span>
|
|
371
|
+
</button>
|
|
372
|
+
`;
|
|
373
|
+
})}
|
|
374
|
+
</div>
|
|
375
|
+
|
|
376
|
+
<!-- Tab Panels -->
|
|
377
|
+
${this.tabs.map((tab) => {
|
|
378
|
+
const isSelected = tab.id === this.selected;
|
|
379
|
+
const tabId = `${this._idPrefix}-tab-${tab.id}`;
|
|
380
|
+
const panelId = `${this._idPrefix}-panel-${tab.id}`;
|
|
381
|
+
const isFirstTabSelected = this.tabs.length > 0 && this.selected === this.tabs[0].id;
|
|
382
|
+
return html `
|
|
383
|
+
<div
|
|
384
|
+
class="tabpanel ${!isFirstTabSelected ? 'tabpanel--rounded-top-left' : ''}"
|
|
385
|
+
role="tabpanel"
|
|
386
|
+
id=${panelId}
|
|
387
|
+
aria-labelledby=${tabId}
|
|
388
|
+
?hidden=${!isSelected}
|
|
389
|
+
tabindex=${isSelected ? "0" : "-1"}
|
|
390
|
+
>
|
|
391
|
+
${tab.content
|
|
392
|
+
? html `${tab.content}`
|
|
393
|
+
: html `<slot name=${tab.id}></slot>`}
|
|
394
|
+
</div>
|
|
395
|
+
`;
|
|
396
|
+
})}
|
|
397
|
+
</div>
|
|
398
|
+
`;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
__decorate([
|
|
402
|
+
property({ type: Array })
|
|
403
|
+
], OhTabs.prototype, "tabs", void 0);
|
|
404
|
+
__decorate([
|
|
405
|
+
property({ type: String, reflect: true, attribute: "selected" })
|
|
406
|
+
], OhTabs.prototype, "selected", void 0);
|
|
407
|
+
__decorate([
|
|
408
|
+
property({ type: String })
|
|
409
|
+
], OhTabs.prototype, "orientation", void 0);
|
|
410
|
+
__decorate([
|
|
411
|
+
state()
|
|
412
|
+
], OhTabs.prototype, "_focusedIndex", void 0);
|
|
413
|
+
OhTabs = __decorate([
|
|
414
|
+
customElement("oh-tabs")
|
|
415
|
+
], OhTabs);
|
|
416
|
+
export { OhTabs };
|
|
417
|
+
//# sourceMappingURL=oh-tabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oh-tabs.js","sourceRoot":"","sources":["../src/oh-tabs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* oh-toggle
|
|
3
|
+
*
|
|
4
|
+
* A toggle/switch component for binary on/off states.
|
|
5
|
+
*
|
|
6
|
+
* DESIGN DECISIONS:
|
|
7
|
+
* ─────────────────
|
|
8
|
+
* 1. CHUNKY 3D FEEL
|
|
9
|
+
* - Thumb offset up-left from center to appear "popping out"
|
|
10
|
+
* - Shadow flips horizontally when checked (light source stays consistent)
|
|
11
|
+
* - Smooth slide animation
|
|
12
|
+
*
|
|
13
|
+
* 2. VISUAL STATES
|
|
14
|
+
* - Off: Neutral track, thumb on left, shadow bottom-right
|
|
15
|
+
* - On: Brand primary track, thumb on right, shadow bottom-left (flipped)
|
|
16
|
+
*
|
|
17
|
+
* 3. ACCESSIBILITY
|
|
18
|
+
* - Uses role="switch" with aria-checked
|
|
19
|
+
* - Keyboard: Space/Enter to toggle
|
|
20
|
+
* - Focus ring matches design system
|
|
21
|
+
* - Label association via aria-labelledby
|
|
22
|
+
*
|
|
23
|
+
* @element oh-toggle
|
|
24
|
+
*
|
|
25
|
+
* @fires oh-change - Fired when the toggle state changes
|
|
26
|
+
*/
|
|
27
|
+
import { OhElement } from "./base.js";
|
|
28
|
+
export declare class OhToggle extends OhElement {
|
|
29
|
+
static styles: import("lit").CSSResult[];
|
|
30
|
+
/** Whether the toggle is checked/on */
|
|
31
|
+
checked: boolean;
|
|
32
|
+
/** Label text displayed next to the toggle */
|
|
33
|
+
label?: string;
|
|
34
|
+
/** Accessible label when no visible label is provided */
|
|
35
|
+
ariaLabel: string | null;
|
|
36
|
+
/** Name for form submission */
|
|
37
|
+
name?: string;
|
|
38
|
+
/** Value for form submission */
|
|
39
|
+
value: string;
|
|
40
|
+
private _toggle;
|
|
41
|
+
private _handleKeydown;
|
|
42
|
+
private _handleClick;
|
|
43
|
+
private readonly _labelId;
|
|
44
|
+
render(): import("lit").TemplateResult<1>;
|
|
45
|
+
}
|
|
46
|
+
declare global {
|
|
47
|
+
interface HTMLElementTagNameMap {
|
|
48
|
+
"oh-toggle": OhToggle;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=oh-toggle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oh-toggle.d.ts","sourceRoot":"","sources":["../src/oh-toggle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,EAAE,SAAS,EAAc,MAAM,WAAW,CAAC;AAElD,qBACa,QAAS,SAAQ,SAAS;IACrC,OAAgB,MAAM,4BAkHpB;IAEF,uCAAuC;IAEvC,OAAO,UAAS;IAEhB,8CAA8C;IAE9C,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,yDAAyD;IAEzD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEhC,+BAA+B;IAE/B,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,gCAAgC;IAEhC,KAAK,SAAQ;IAEb,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA+D;IAExF,MAAM;CAqBP;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,WAAW,EAAE,QAAQ,CAAC;KACvB;CACF"}
|