@nuvia-ui/components 4.0.1
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/package.json +27 -0
- package/src/ds-accordion/ds-accordion-item.js +288 -0
- package/src/ds-accordion/ds-accordion-item.stories.js +82 -0
- package/src/ds-accordion/ds-accordion.a11y.test.js +92 -0
- package/src/ds-accordion/ds-accordion.js +68 -0
- package/src/ds-accordion/ds-accordion.stories.js +118 -0
- package/src/ds-accordion/ds-accordion.test.js +146 -0
- package/src/ds-accordion/index.js +2 -0
- package/src/ds-action-bar/ds-action-bar.js +116 -0
- package/src/ds-action-bar/ds-action-bar.stories.js +86 -0
- package/src/ds-action-bar/ds-action-bar.test.js +64 -0
- package/src/ds-action-bar/index.js +1 -0
- package/src/ds-alert/ds-alert.a11y.test.js +151 -0
- package/src/ds-alert/ds-alert.js +223 -0
- package/src/ds-alert/ds-alert.mdx +142 -0
- package/src/ds-alert/ds-alert.stories.js +166 -0
- package/src/ds-alert/ds-alert.test.js +256 -0
- package/src/ds-alert/index.js +1 -0
- package/src/ds-avatar/ds-avatar.a11y.test.js +45 -0
- package/src/ds-avatar/ds-avatar.js +216 -0
- package/src/ds-avatar/ds-avatar.stories.js +120 -0
- package/src/ds-avatar/ds-avatar.test.js +83 -0
- package/src/ds-avatar/index.js +1 -0
- package/src/ds-avatar-extended/ds-avatar-extended.a11y.test.js +29 -0
- package/src/ds-avatar-extended/ds-avatar-extended.js +108 -0
- package/src/ds-avatar-extended/ds-avatar-extended.stories.js +93 -0
- package/src/ds-avatar-extended/ds-avatar-extended.test.js +66 -0
- package/src/ds-avatar-extended/index.js +1 -0
- package/src/ds-banner/ds-banner.a11y.test.js +51 -0
- package/src/ds-banner/ds-banner.js +233 -0
- package/src/ds-banner/ds-banner.stories.js +185 -0
- package/src/ds-banner/ds-banner.test.js +116 -0
- package/src/ds-banner/index.js +1 -0
- package/src/ds-breadcrumb-item/ds-breadcrumb-item.js +135 -0
- package/src/ds-breadcrumb-item/ds-breadcrumb-item.stories.js +49 -0
- package/src/ds-breadcrumb-item/ds-breadcrumb-item.test.js +55 -0
- package/src/ds-breadcrumbs/ds-breadcrumbs.js +194 -0
- package/src/ds-breadcrumbs/ds-breadcrumbs.stories.js +54 -0
- package/src/ds-breadcrumbs/ds-breadcrumbs.test.js +33 -0
- package/src/ds-button/ds-button.a11y.test.js +49 -0
- package/src/ds-button/ds-button.js +205 -0
- package/src/ds-button/ds-button.mdx +141 -0
- package/src/ds-button/ds-button.stories.js +152 -0
- package/src/ds-button/ds-button.test.js +62 -0
- package/src/ds-button/index.js +1 -0
- package/src/ds-button-group/ds-button-group.js +82 -0
- package/src/ds-button-group/ds-button-group.mdx +39 -0
- package/src/ds-button-group/ds-button-group.stories.js +47 -0
- package/src/ds-button-group/ds-button-group.test.js +47 -0
- package/src/ds-button-group/index.js +1 -0
- package/src/ds-checkbox/ds-checkbox.a11y.test.js +79 -0
- package/src/ds-checkbox/ds-checkbox.js +271 -0
- package/src/ds-checkbox/ds-checkbox.stories.js +77 -0
- package/src/ds-checkbox/ds-checkbox.test.js +191 -0
- package/src/ds-checkbox/index.js +1 -0
- package/src/ds-checkbox-group/ds-checkbox-group.a11y.test.js +146 -0
- package/src/ds-checkbox-group/ds-checkbox-group.js +235 -0
- package/src/ds-checkbox-group/ds-checkbox-group.stories.js +210 -0
- package/src/ds-checkbox-group/ds-checkbox-group.test.js +150 -0
- package/src/ds-checkbox-group/index.js +1 -0
- package/src/ds-dialog/ds-dialog.js +466 -0
- package/src/ds-dialog/ds-dialog.stories.js +274 -0
- package/src/ds-dialog/ds-dialog.test.js +441 -0
- package/src/ds-dialog/index.js +1 -0
- package/src/ds-dropdown/ds-dropdown.a11y.test.js +80 -0
- package/src/ds-dropdown/ds-dropdown.js +891 -0
- package/src/ds-dropdown/ds-dropdown.stories.js +259 -0
- package/src/ds-dropdown/ds-dropdown.test.js +268 -0
- package/src/ds-dropdown/index.js +1 -0
- package/src/ds-dropdown-group/ds-dropdown-group.js +55 -0
- package/src/ds-dropdown-panel/ds-dropdown-panel.js +34 -0
- package/src/ds-file-uploaded/ds-file-uploaded.a11y.test.js +40 -0
- package/src/ds-file-uploaded/ds-file-uploaded.js +135 -0
- package/src/ds-file-uploaded/ds-file-uploaded.mdx +33 -0
- package/src/ds-file-uploaded/ds-file-uploaded.stories.js +81 -0
- package/src/ds-file-uploaded/ds-file-uploaded.test.js +85 -0
- package/src/ds-file-uploader/ds-file-uploader.a11y.test.js +61 -0
- package/src/ds-file-uploader/ds-file-uploader.js +442 -0
- package/src/ds-file-uploader/ds-file-uploader.mdx +44 -0
- package/src/ds-file-uploader/ds-file-uploader.stories.js +76 -0
- package/src/ds-file-uploader/ds-file-uploader.test.js +142 -0
- package/src/ds-header/ds-header.a11y.test.js +38 -0
- package/src/ds-header/ds-header.js +149 -0
- package/src/ds-header/ds-header.stories.js +63 -0
- package/src/ds-header/ds-header.test.js +52 -0
- package/src/ds-header/index.js +1 -0
- package/src/ds-header-nav/ds-header-nav.a11y.test.js +69 -0
- package/src/ds-header-nav/ds-header-nav.js +114 -0
- package/src/ds-header-nav/ds-header-nav.stories.js +17 -0
- package/src/ds-header-nav/ds-header-nav.test.js +93 -0
- package/src/ds-header-nav-item/ds-header-nav-item.a11y.test.js +71 -0
- package/src/ds-header-nav-item/ds-header-nav-item.js +124 -0
- package/src/ds-header-nav-item/ds-header-nav-item.stories.js +43 -0
- package/src/ds-header-nav-item/ds-header-nav-item.test.js +61 -0
- package/src/ds-icon/ds-icon.a11y.test.js +49 -0
- package/src/ds-icon/ds-icon.js +75 -0
- package/src/ds-icon/ds-icon.mdx +36 -0
- package/src/ds-icon/ds-icon.stories.js +88 -0
- package/src/ds-icon/ds-icon.test.js +97 -0
- package/src/ds-icon/index.js +1 -0
- package/src/ds-icon-button/ds-icon-button.a11y.test.js +55 -0
- package/src/ds-icon-button/ds-icon-button.js +224 -0
- package/src/ds-icon-button/ds-icon-button.mdx +131 -0
- package/src/ds-icon-button/ds-icon-button.stories.js +128 -0
- package/src/ds-icon-button/ds-icon-button.test.js +90 -0
- package/src/ds-icon-button/index.js +1 -0
- package/src/ds-input/ds-input.a11y.test.js +145 -0
- package/src/ds-input/ds-input.js +645 -0
- package/src/ds-input/ds-input.mdx +251 -0
- package/src/ds-input/ds-input.stories.js +298 -0
- package/src/ds-input/ds-input.test.js +792 -0
- package/src/ds-input/index.js +1 -0
- package/src/ds-link/ds-link.js +111 -0
- package/src/ds-link/ds-link.stories.js +56 -0
- package/src/ds-link/ds-link.test.js +74 -0
- package/src/ds-list-item/ds-list-item.a11y.test.js +39 -0
- package/src/ds-list-item/ds-list-item.js +292 -0
- package/src/ds-list-item/ds-list-item.stories.js +101 -0
- package/src/ds-list-item/ds-list-item.test.js +63 -0
- package/src/ds-menu/ds-menu.js +30 -0
- package/src/ds-menu/ds-menu.stories.js +120 -0
- package/src/ds-menu/ds-menu.test.js +123 -0
- package/src/ds-menu-group/ds-menu-group.js +101 -0
- package/src/ds-menu-group/ds-menu-group.stories.js +99 -0
- package/src/ds-nav-item/ds-nav-item.a11y.test.js +91 -0
- package/src/ds-nav-item/ds-nav-item.js +307 -0
- package/src/ds-nav-item/ds-nav-item.stories.js +99 -0
- package/src/ds-nav-item/ds-nav-item.test.js +169 -0
- package/src/ds-nav-item/index.js +1 -0
- package/src/ds-nav-vertical/ds-nav-vertical.a11y.test.js +69 -0
- package/src/ds-nav-vertical/ds-nav-vertical.js +173 -0
- package/src/ds-nav-vertical/ds-nav-vertical.stories.js +124 -0
- package/src/ds-nav-vertical/ds-nav-vertical.test.js +176 -0
- package/src/ds-nav-vertical/index.js +1 -0
- package/src/ds-pagination/ds-pagination.a11y.test.js +50 -0
- package/src/ds-pagination/ds-pagination.js +232 -0
- package/src/ds-pagination/ds-pagination.stories.js +63 -0
- package/src/ds-pagination/ds-pagination.test.js +141 -0
- package/src/ds-pagination/index.js +1 -0
- package/src/ds-progress-bar/ds-progress-bar.a11y.test.js +25 -0
- package/src/ds-progress-bar/ds-progress-bar.js +81 -0
- package/src/ds-progress-bar/ds-progress-bar.stories.js +69 -0
- package/src/ds-progress-bar/ds-progress-bar.test.js +60 -0
- package/src/ds-radio/ds-radio.a11y.test.js +69 -0
- package/src/ds-radio/ds-radio.js +240 -0
- package/src/ds-radio/ds-radio.stories.js +102 -0
- package/src/ds-radio/ds-radio.test.js +114 -0
- package/src/ds-radio/index.js +1 -0
- package/src/ds-radio-group/ds-radio-group.a11y.test.js +164 -0
- package/src/ds-radio-group/ds-radio-group.js +257 -0
- package/src/ds-radio-group/ds-radio-group.stories.js +247 -0
- package/src/ds-radio-group/ds-radio-group.test.js +194 -0
- package/src/ds-radio-group/index.js +1 -0
- package/src/ds-rich-list/ds-rich-list.js +246 -0
- package/src/ds-rich-list/ds-rich-list.stories.js +368 -0
- package/src/ds-rich-list/ds-rich-list.test.js +293 -0
- package/src/ds-rich-list-item/ds-rich-list-item.js +579 -0
- package/src/ds-rich-list-item/ds-rich-list-item.stories.js +197 -0
- package/src/ds-rich-list-item/ds-rich-list-item.test.js +434 -0
- package/src/ds-slider/ds-slider.js +399 -0
- package/src/ds-slider/ds-slider.stories.js +107 -0
- package/src/ds-slider/ds-slider.test.js +308 -0
- package/src/ds-spinner/ds-spinner.js +173 -0
- package/src/ds-spinner/ds-spinner.stories.js +52 -0
- package/src/ds-spinner/ds-spinner.test.js +50 -0
- package/src/ds-status-border/ds-status-border.js +88 -0
- package/src/ds-status-border/ds-status-border.stories.js +242 -0
- package/src/ds-status-border/ds-status-border.test.js +168 -0
- package/src/ds-stepper/ds-stepper.a11y.test.js +198 -0
- package/src/ds-stepper/ds-stepper.js +207 -0
- package/src/ds-stepper/ds-stepper.stories.js +530 -0
- package/src/ds-stepper/ds-stepper.test.js +311 -0
- package/src/ds-stepper-item/ds-stepper-item.js +485 -0
- package/src/ds-stepper-item/ds-stepper-item.stories.js +288 -0
- package/src/ds-switch/ds-switch.js +348 -0
- package/src/ds-switch/ds-switch.stories.js +145 -0
- package/src/ds-switch/ds-switch.test.js +226 -0
- package/src/ds-switch/index.js +1 -0
- package/src/ds-tab-item/ds-tab-item.js +341 -0
- package/src/ds-tab-item/ds-tab-item.stories.js +69 -0
- package/src/ds-tabs/ds-tab-panel.js +48 -0
- package/src/ds-tabs/ds-tabs.a11y.test.js +56 -0
- package/src/ds-tabs/ds-tabs.js +180 -0
- package/src/ds-tabs/ds-tabs.stories.js +152 -0
- package/src/ds-tabs/ds-tabs.test.js +306 -0
- package/src/ds-tabs/index.js +3 -0
- package/src/ds-tag-action/ds-tag-action.a11y.test.js +32 -0
- package/src/ds-tag-action/ds-tag-action.js +185 -0
- package/src/ds-tag-action/ds-tag-action.stories.js +55 -0
- package/src/ds-tag-action/ds-tag-action.test.js +44 -0
- package/src/ds-tag-removable/ds-tag-removable.a11y.test.js +24 -0
- package/src/ds-tag-removable/ds-tag-removable.js +146 -0
- package/src/ds-tag-removable/ds-tag-removable.stories.js +52 -0
- package/src/ds-tag-removable/ds-tag-removable.test.js +46 -0
- package/src/ds-tag-status/ds-tag-status.a11y.test.js +93 -0
- package/src/ds-tag-status/ds-tag-status.js +164 -0
- package/src/ds-tag-status/ds-tag-status.stories.js +200 -0
- package/src/ds-tag-status/ds-tag-status.test.js +140 -0
- package/src/ds-tag-status/index.js +1 -0
- package/src/ds-textarea/ds-textarea-clearable.test.js +89 -0
- package/src/ds-textarea/ds-textarea.a11y.test.js +66 -0
- package/src/ds-textarea/ds-textarea.js +505 -0
- package/src/ds-textarea/ds-textarea.stories.js +335 -0
- package/src/ds-textarea/ds-textarea.test.js +218 -0
- package/src/ds-textarea/index.js +1 -0
- package/src/ds-thumbnail/ds-thumbnail.js +207 -0
- package/src/ds-thumbnail/ds-thumbnail.stories.js +217 -0
- package/src/ds-thumbnail/ds-thumbnail.test.js +220 -0
- package/src/ds-toast/ds-toast-provider.js +110 -0
- package/src/ds-toast/ds-toast.a11y.test.js +34 -0
- package/src/ds-toast/ds-toast.js +243 -0
- package/src/ds-toast/ds-toast.stories.js +143 -0
- package/src/ds-toast/ds-toast.test.js +93 -0
- package/src/ds-toast/index.js +2 -0
- package/src/ds-tooltip/ds-tooltip.a11y.test.js +110 -0
- package/src/ds-tooltip/ds-tooltip.js +217 -0
- package/src/ds-tooltip/ds-tooltip.mdx +75 -0
- package/src/ds-tooltip/ds-tooltip.stories.js +72 -0
- package/src/ds-tooltip/ds-tooltip.test.js +191 -0
- package/src/ds-tooltip/index.js +1 -0
- package/src/ds-tooltip/positioner.js +117 -0
- package/src/index.js +50 -0
- package/src/mixins/field-label.mixin.js +113 -0
- package/src/mixins/field-message.mixin.js +66 -0
- package/src/token-provider/index.js +1 -0
- package/src/token-provider/token-provider.a11y.test.js +44 -0
- package/src/token-provider/token-provider.js +85 -0
- package/src/token-provider/token-provider.stories.js +105 -0
- package/src/token-provider/token-provider.test.js +134 -0
- package/src/utils/number-input.utils.js +42 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import axe from 'axe-core';
|
|
3
|
+
import './ds-header.js';
|
|
4
|
+
|
|
5
|
+
describe('ds-header a11y', () => {
|
|
6
|
+
let container;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
container = document.createElement('div');
|
|
10
|
+
document.body.appendChild(container);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
container.remove();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should pass axe accessibility checks', async () => {
|
|
18
|
+
container.innerHTML = `
|
|
19
|
+
<ds-header>
|
|
20
|
+
<button slot="menu-trigger" aria-label="Menu">☰</button>
|
|
21
|
+
<h1 slot="app-name">My App</h1>
|
|
22
|
+
<nav slot="nav" aria-label="Main">
|
|
23
|
+
<a href="#">Link 1</a>
|
|
24
|
+
</nav>
|
|
25
|
+
</ds-header>
|
|
26
|
+
`;
|
|
27
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
28
|
+
|
|
29
|
+
const results = await axe.run(container, {
|
|
30
|
+
rules: { 'color-contrast': { enabled: false } }
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (results.violations.length > 0) {
|
|
34
|
+
console.log('Violations:', JSON.stringify(results.violations, null, 2));
|
|
35
|
+
}
|
|
36
|
+
expect(results.violations).toHaveLength(0);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Header component for global application navigation and branding.
|
|
5
|
+
*
|
|
6
|
+
* @element ds-header
|
|
7
|
+
*
|
|
8
|
+
* @slot menu-trigger - Slot for the hamburger menu button (leftmost)
|
|
9
|
+
* @slot logo - Slot for the application logo
|
|
10
|
+
* @slot nav - Slot for main horizontal navigation
|
|
11
|
+
* @slot app-name - Slot for the application name text
|
|
12
|
+
* @slot nav - Slot for main horizontal navigation
|
|
13
|
+
* @slot actions - Slot for global actions (rightmost)
|
|
14
|
+
*
|
|
15
|
+
* @csspart header - The main header container
|
|
16
|
+
* @csspart top-bar - The colored top bar
|
|
17
|
+
* @csspart content - The content wrapper
|
|
18
|
+
* @csspart divider - The vertical divider
|
|
19
|
+
*/
|
|
20
|
+
export class DsHeader extends LitElement {
|
|
21
|
+
static properties = {
|
|
22
|
+
logoHref: { type: String, attribute: 'logo-href' }
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
static styles = css`
|
|
26
|
+
:host {
|
|
27
|
+
display: block;
|
|
28
|
+
width: 100%;
|
|
29
|
+
position: relative;
|
|
30
|
+
z-index: var(--ds-z-index-sticky, 100);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
/* Top Bar */
|
|
36
|
+
.top-bar {
|
|
37
|
+
position: absolute;
|
|
38
|
+
top: 0;
|
|
39
|
+
left: 0;
|
|
40
|
+
right: 0;
|
|
41
|
+
height: 4px;
|
|
42
|
+
width: 100%;
|
|
43
|
+
background-color: var(--ds-color-bg-brand);
|
|
44
|
+
z-index: 10;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Main Header Content */
|
|
48
|
+
.header {
|
|
49
|
+
height: 64px;
|
|
50
|
+
background-color: var(--ds-color-bg-header);
|
|
51
|
+
color: var(--ds-color-text-header);
|
|
52
|
+
padding: 0 var(--ds-space-md);
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
justify-content: space-between;
|
|
56
|
+
box-sizing: border-box;
|
|
57
|
+
border-bottom: 1px solid var(--ds-color-border-header);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Left Section: Menu, Logo, App Name */
|
|
61
|
+
.left-section {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: var(--ds-space-md);
|
|
65
|
+
height: 100%;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.logo-link {
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
text-decoration: none;
|
|
72
|
+
color: inherit;
|
|
73
|
+
border-radius: var(--ds-radius-container);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.logo-link:focus-visible {
|
|
77
|
+
outline: 2px solid var(--ds-color-border-focus);
|
|
78
|
+
outline-offset: 2px;
|
|
79
|
+
border-radius: var(--ds-radius-container);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Slots handling */
|
|
83
|
+
::slotted([slot="menu-trigger"]) {
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
::slotted([slot="logo"]) {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
height: 32px; /* Fixed height for logo container */
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.divider {
|
|
95
|
+
width: 1px;
|
|
96
|
+
height: 32px;
|
|
97
|
+
background-color: var(--ds-color-border-header);
|
|
98
|
+
background-color: var(--ds-color-border-header);
|
|
99
|
+
opacity: 1; /* Reset opacity since valid border colors are used */
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
::slotted([slot="app-name"]) {
|
|
103
|
+
font: var(--ds-typo-content-body-bold);
|
|
104
|
+
color: var(--ds-color-text-header);
|
|
105
|
+
white-space: nowrap;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
/* Right Section: Actions */
|
|
111
|
+
.actions-section {
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
gap: var(--ds-space-sm); /* 8px */
|
|
115
|
+
height: 100%;
|
|
116
|
+
}
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
constructor() {
|
|
120
|
+
super();
|
|
121
|
+
this.logoHref = '/';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
render() {
|
|
125
|
+
return html`
|
|
126
|
+
<div class="top-bar" part="top-bar"></div>
|
|
127
|
+
<header class="header" part="header" role="banner">
|
|
128
|
+
<div class="left-section">
|
|
129
|
+
<slot name="menu-trigger"></slot>
|
|
130
|
+
|
|
131
|
+
<a href="${this.logoHref}" class="logo-link" part="logo-link" aria-label="Go to homepage">
|
|
132
|
+
<slot name="logo"></slot>
|
|
133
|
+
</a>
|
|
134
|
+
|
|
135
|
+
<div class="divider" part="divider"></div>
|
|
136
|
+
<slot name="app-name"></slot>
|
|
137
|
+
|
|
138
|
+
<slot name="nav" style="display: flex; gap: 8px; margin-left: 16px; height: 100%;"></slot>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div class="actions-section">
|
|
142
|
+
<slot name="actions"></slot>
|
|
143
|
+
</div>
|
|
144
|
+
</header>
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
customElements.define('ds-header', DsHeader);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import './ds-header.js';
|
|
3
|
+
import '../ds-icon-button/ds-icon-button.js';
|
|
4
|
+
import '../ds-icon/ds-icon.js';
|
|
5
|
+
import '../ds-header-nav-item/ds-header-nav-item.js';
|
|
6
|
+
import '../ds-header-nav/ds-header-nav.js';
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
title: 'Components/Header',
|
|
10
|
+
component: 'ds-header',
|
|
11
|
+
parameters: {
|
|
12
|
+
layout: 'fullscreen',
|
|
13
|
+
},
|
|
14
|
+
argTypes: {
|
|
15
|
+
appName: { control: 'text' }
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const Template = (args) => html`
|
|
20
|
+
<ds-header>
|
|
21
|
+
<ds-icon-button slot="menu-trigger" icon="menu" variant="action"></ds-icon-button>
|
|
22
|
+
|
|
23
|
+
<div slot="logo" style="display: flex; align-items: center; height: 100%;">
|
|
24
|
+
<svg width="55" height="32" viewBox="0 0 55 32" fill="none" xmlns="http://www.w3.org/2000/svg" style="color: var(--ds-color-text-header);">
|
|
25
|
+
<path d="M16.0539 12.04C15.2681 12.04 14.4999 12.2723 13.8465 12.7074C13.193 13.1425 12.6838 13.761 12.383 14.4846C12.0823 15.2082 12.0036 16.0044 12.1569 16.7726C12.3102 17.5407 12.6887 18.2463 13.2444 18.8001C13.8 19.354 14.508 19.7311 15.2788 19.8839C16.0495 20.0367 16.8484 19.9583 17.5745 19.6586C18.3005 19.3588 18.9211 18.8513 19.3577 18.2001C19.7943 17.5488 20.0273 16.7832 20.0273 16C20.0273 15.48 19.9246 14.965 19.7249 14.4845C19.5253 14.0041 19.2326 13.5675 18.8636 13.1998C18.4946 12.832 18.0566 12.5403 17.5745 12.3414C17.0924 12.1424 16.5757 12.04 16.0539 12.04ZM16.0539 18.656C15.5269 18.656 15.0116 18.5002 14.5734 18.2084C14.1351 17.9165 13.7935 17.5017 13.5918 17.0164C13.3901 16.5311 13.3374 15.9971 13.4402 15.4818C13.543 14.9666 13.7968 14.4934 14.1695 14.1219C14.5422 13.7505 15.0171 13.4975 15.534 13.395C16.051 13.2926 16.5868 13.3452 17.0738 13.5462C17.5607 13.7472 17.9769 14.0876 18.2698 14.5244C18.5626 14.9612 18.7189 15.4747 18.7189 16C18.7189 16.7044 18.4381 17.38 17.9383 17.8781C17.4386 18.3762 16.7607 18.656 16.0539 18.656ZM38.5339 1.46675e-05C34.3296 -0.00566465 30.2919 1.63815 27.2939 4.57601C24.296 1.63815 20.2583 -0.00566465 16.0539 1.46675e-05C7.18767 1.46675e-05 0 7.16353 0 16C0 24.8365 7.18767 32 16.0539 32C20.2583 32.0057 24.296 30.3619 27.2939 27.424C30.2919 30.3619 34.3296 32.0057 38.5339 32C47.4005 32 54.5879 24.8365 54.5879 16C54.5879 7.16353 47.4005 1.46675e-05 38.5339 1.46675e-05ZM38.5339 30.752C34.496 30.752 30.8325 29.1306 28.1592 26.5088C29.3205 25.1794 30.2535 23.668 30.9205 22.0355H29.5518C28.972 23.3187 28.2117 24.5129 27.2939 25.5824C26.3756 24.5131 25.6149 23.3188 25.0348 22.0355H23.6657C24.3328 23.668 25.2657 25.1794 26.427 26.5088C23.754 29.1306 20.0902 30.752 16.0523 30.752C7.89051 30.752 1.2506 24.1344 1.2506 16C1.2506 7.86561 7.89051 1.24801 16.0523 1.24801C20.0902 1.24801 23.754 2.86945 26.427 5.49121C25.2657 6.8206 24.3328 8.33204 23.6657 9.96449H25.0345C25.6147 8.68119 26.3755 7.48688 27.2939 6.41761C28.2122 7.48698 28.9728 8.68127 29.553 9.96449H30.9221C30.2551 8.33204 29.3221 6.8206 28.1608 5.49121C30.8342 2.86945 34.4977 1.24801 38.5355 1.24801C46.6974 1.24801 53.3373 7.86561 53.3373 16C53.3373 24.1344 46.6958 30.752 38.5339 30.752ZM48.1207 16.7398C48.9446 16.3763 49.5209 15.5878 49.5434 14.6797C49.5668 13.7197 49.0201 12.8922 48.1567 12.4842C47.6157 12.2282 47.04 12.1798 46.4511 12.1798H43.8654V19.8195H45.2101V17.0749H46.8425L48.4858 19.8202H50.0642L48.1207 16.7398ZM48.1878 14.5936C48.1878 14.9747 48.0292 15.3373 47.7242 15.5718C47.4269 15.8006 47.0534 15.8918 46.6839 15.9011C46.3374 15.911 45.3553 15.9011 45.2111 15.9011V13.3603H46.2707C46.7523 13.3603 47.2756 13.3414 47.694 13.6205C48.0231 13.8413 48.1878 14.2048 48.1878 14.5936ZM15.6343 10.4499C15.6343 10.5955 15.591 10.7378 15.5098 10.8588C15.4287 10.9799 15.3133 11.0742 15.1784 11.1299C15.0435 11.1856 14.895 11.2002 14.7517 11.1718C14.6085 11.1434 14.4769 11.0733 14.3736 10.9704C14.2703 10.8674 14.2 10.7363 14.1715 10.5935C14.143 10.4507 14.1576 10.3028 14.2135 10.1683C14.2694 10.0338 14.3641 9.91884 14.4855 9.83797C14.607 9.75709 14.7497 9.71393 14.8958 9.71393C15.0917 9.71393 15.2795 9.79147 15.418 9.9295C15.5565 10.0675 15.6343 10.2547 15.6343 10.4499ZM17.9502 10.4499C17.9502 10.5955 17.9069 10.7378 17.8258 10.8588C17.7446 10.9799 17.6293 11.0742 17.4944 11.1299C17.3594 11.1856 17.2109 11.2002 17.0677 11.1718C16.9244 11.1434 16.7928 11.0733 16.6896 10.9704C16.5863 10.8674 16.5159 10.7363 16.4875 10.5935C16.459 10.4507 16.4736 10.3028 16.5295 10.1683C16.5854 10.0338 16.68 9.91884 16.8015 9.83797C16.9229 9.75709 17.0657 9.71393 17.2117 9.71393C17.3088 9.71388 17.4048 9.73289 17.4944 9.76986C17.5841 9.80683 17.6655 9.86103 17.7341 9.92938C17.8027 9.99773 17.8572 10.0789 17.8943 10.1682C17.9314 10.2575 17.9502 10.3532 17.9502 10.4499ZM25.8488 16.7398C26.6726 16.3763 27.249 15.5878 27.2715 14.6797C27.2949 13.7197 26.7481 12.8922 25.8847 12.4842C25.3437 12.2282 24.768 12.1798 24.1792 12.1798H21.5948V19.8195H22.9404V17.0749H24.5728L26.2161 19.8202H27.7945L25.8488 16.7398ZM25.9159 14.5936C25.9159 14.9747 25.7573 15.3373 25.4522 15.5718C25.1549 15.8006 24.7815 15.8918 24.4119 15.9011C24.0655 15.911 23.0833 15.9011 22.9391 15.9011V13.3603H23.9987C24.4803 13.3603 25.0037 13.3414 25.422 13.6205C25.7528 13.8413 25.9172 14.2048 25.9172 14.5936H25.9159ZM34.8945 16.4867C34.7359 16.2335 34.5188 16.0219 34.2613 15.8694C34.4964 15.7261 34.6935 15.5285 34.8361 15.2934C35.0053 15.0102 35.0907 14.6822 35.0907 14.3194C35.0907 13.6522 34.8563 13.1219 34.3939 12.7434C33.938 12.3699 33.3135 12.1805 32.5372 12.1805H29.3809V19.8208H32.5779C33.3604 19.8208 33.9955 19.6288 34.4649 19.2483C34.9411 18.8643 35.1825 18.3018 35.1825 17.5805C35.1825 17.1552 35.0859 16.7872 34.8958 16.4867H34.8945ZM33.5303 18.3056C33.3106 18.4938 32.9899 18.5894 32.5766 18.5894H30.6858V16.5414H32.4954C32.9825 16.5414 33.3357 16.6333 33.5457 16.8147C33.7556 16.9962 33.8549 17.2387 33.8549 17.561C33.8558 17.8749 33.7499 18.1187 33.5315 18.3056H33.5303ZM33.4798 15.0128C33.291 15.1949 32.9838 15.2874 32.5667 15.2874H30.6858V13.3994H32.5359C32.9501 13.3994 33.2634 13.4819 33.467 13.6448C33.6706 13.8077 33.7627 14.024 33.7627 14.3168C33.764 14.6022 33.6715 14.8294 33.4811 15.0128H33.4798ZM38.313 13.4531V15.3533H41.457V16.6262H38.313V18.5366H42.0992V19.8198H37.0066V12.1802H42.1005V13.4531H38.313ZM8.04077 16L11.8738 19.8189H10.1429L6.72596 16.44V19.8192H5.38032V12.1795H6.72596V15.5773L10.1435 12.1795H11.8745L8.04077 16Z" fill="currentColor"/>
|
|
26
|
+
</svg>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<span slot="app-name">${args.appName || 'Design System'}</span>
|
|
30
|
+
|
|
31
|
+
<nav slot="nav" style="height: 100%;">
|
|
32
|
+
<ds-header-nav>
|
|
33
|
+
<ds-header-nav-item selected>Dashboard</ds-header-nav-item>
|
|
34
|
+
<ds-header-nav-item>Projects</ds-header-nav-item>
|
|
35
|
+
<ds-header-nav-item>Team</ds-header-nav-item>
|
|
36
|
+
</ds-header-nav>
|
|
37
|
+
</nav>
|
|
38
|
+
|
|
39
|
+
<div slot="actions" style="display: flex; gap: 8px;">
|
|
40
|
+
<ds-icon-button icon="search" variant="action"></ds-icon-button>
|
|
41
|
+
<ds-icon-button icon="notifications" variant="action"></ds-icon-button>
|
|
42
|
+
<ds-icon-button icon="account-circle" variant="action"></ds-icon-button>
|
|
43
|
+
</div>
|
|
44
|
+
</ds-header>
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
export const Default = Template.bind({});
|
|
48
|
+
|
|
49
|
+
export const NoMenu = {
|
|
50
|
+
render: () => html`
|
|
51
|
+
<ds-header>
|
|
52
|
+
<div slot="logo" style="display: flex; align-items: center; height: 100%;">
|
|
53
|
+
<svg width="55" height="32" viewBox="0 0 55 32" fill="none" xmlns="http://www.w3.org/2000/svg" style="color: var(--ds-color-text-header);">
|
|
54
|
+
<path d="M16.0539 12.04C15.2681 12.04 14.4999 12.2723 13.8465 12.7074C13.193 13.1425 12.6838 13.761 12.383 14.4846C12.0823 15.2082 12.0036 16.0044 12.1569 16.7726C12.3102 17.5407 12.6887 18.2463 13.2444 18.8001C13.8 19.354 14.508 19.7311 15.2788 19.8839C16.0495 20.0367 16.8484 19.9583 17.5745 19.6586C18.3005 19.3588 18.9211 18.8513 19.3577 18.2001C19.7943 17.5488 20.0273 16.7832 20.0273 16C20.0273 15.48 19.9246 14.965 19.7249 14.4845C19.5253 14.0041 19.2326 13.5675 18.8636 13.1998C18.4946 12.832 18.0566 12.5403 17.5745 12.3414C17.0924 12.1424 16.5757 12.04 16.0539 12.04ZM16.0539 18.656C15.5269 18.656 15.0116 18.5002 14.5734 18.2084C14.1351 17.9165 13.7935 17.5017 13.5918 17.0164C13.3901 16.5311 13.3374 15.9971 13.4402 15.4818C13.543 14.9666 13.7968 14.4934 14.1695 14.1219C14.5422 13.7505 15.0171 13.4975 15.534 13.395C16.051 13.2926 16.5868 13.3452 17.0738 13.5462C17.5607 13.7472 17.9769 14.0876 18.2698 14.5244C18.5626 14.9612 18.7189 15.4747 18.7189 16C18.7189 16.7044 18.4381 17.38 17.9383 17.8781C17.4386 18.3762 16.7607 18.656 16.0539 18.656ZM38.5339 1.46675e-05C34.3296 -0.00566465 30.2919 1.63815 27.2939 4.57601C24.296 1.63815 20.2583 -0.00566465 16.0539 1.46675e-05C7.18767 1.46675e-05 0 7.16353 0 16C0 24.8365 7.18767 32 16.0539 32C20.2583 32.0057 24.296 30.3619 27.2939 27.424C30.2919 30.3619 34.3296 32.0057 38.5339 32C47.4005 32 54.5879 24.8365 54.5879 16C54.5879 7.16353 47.4005 1.46675e-05 38.5339 1.46675e-05ZM38.5339 30.752C34.496 30.752 30.8325 29.1306 28.1592 26.5088C29.3205 25.1794 30.2535 23.668 30.9205 22.0355H29.5518C28.972 23.3187 28.2117 24.5129 27.2939 25.5824C26.3756 24.5131 25.6149 23.3188 25.0348 22.0355H23.6657C24.3328 23.668 25.2657 25.1794 26.427 26.5088C23.754 29.1306 20.0902 30.752 16.0523 30.752C7.89051 30.752 1.2506 24.1344 1.2506 16C1.2506 7.86561 7.89051 1.24801 16.0523 1.24801C20.0902 1.24801 23.754 2.86945 26.427 5.49121C25.2657 6.8206 24.3328 8.33204 23.6657 9.96449H25.0345C25.6147 8.68119 26.3755 7.48688 27.2939 6.41761C28.2122 7.48698 28.9728 8.68127 29.553 9.96449H30.9221C30.2551 8.33204 29.3221 6.8206 28.1608 5.49121C30.8342 2.86945 34.4977 1.24801 38.5355 1.24801C46.6974 1.24801 53.3373 7.86561 53.3373 16C53.3373 24.1344 46.6958 30.752 38.5339 30.752ZM48.1207 16.7398C48.9446 16.3763 49.5209 15.5878 49.5434 14.6797C49.5668 13.7197 49.0201 12.8922 48.1567 12.4842C47.6157 12.2282 47.04 12.1798 46.4511 12.1798H43.8654V19.8195H45.2101V17.0749H46.8425L48.4858 19.8202H50.0642L48.1207 16.7398ZM48.1878 14.5936C48.1878 14.9747 48.0292 15.3373 47.7242 15.5718C47.4269 15.8006 47.0534 15.8918 46.6839 15.9011C46.3374 15.911 45.3553 15.9011 45.2111 15.9011V13.3603H46.2707C46.7523 13.3603 47.2756 13.3414 47.694 13.6205C48.0231 13.8413 48.1878 14.2048 48.1878 14.5936ZM15.6343 10.4499C15.6343 10.5955 15.591 10.7378 15.5098 10.8588C15.4287 10.9799 15.3133 11.0742 15.1784 11.1299C15.0435 11.1856 14.895 11.2002 14.7517 11.1718C14.6085 11.1434 14.4769 11.0733 14.3736 10.9704C14.2703 10.8674 14.2 10.7363 14.1715 10.5935C14.143 10.4507 14.1576 10.3028 14.2135 10.1683C14.2694 10.0338 14.3641 9.91884 14.4855 9.83797C14.607 9.75709 14.7497 9.71393 14.8958 9.71393C15.0917 9.71393 15.2795 9.79147 15.418 9.9295C15.5565 10.0675 15.6343 10.2547 15.6343 10.4499ZM17.9502 10.4499C17.9502 10.5955 17.9069 10.7378 17.8258 10.8588C17.7446 10.9799 17.6293 11.0742 17.4944 11.1299C17.3594 11.1856 17.2109 11.2002 17.0677 11.1718C16.9244 11.1434 16.7928 11.0733 16.6896 10.9704C16.5863 10.8674 16.5159 10.7363 16.4875 10.5935C16.459 10.4507 16.4736 10.3028 16.5295 10.1683C16.5854 10.0338 16.68 9.91884 16.8015 9.83797C16.9229 9.75709 17.0657 9.71393 17.2117 9.71393C17.3088 9.71388 17.4048 9.73289 17.4944 9.76986C17.5841 9.80683 17.6655 9.86103 17.7341 9.92938C17.8027 9.99773 17.8572 10.0789 17.8943 10.1682C17.9314 10.2575 17.9502 10.3532 17.9502 10.4499ZM25.8488 16.7398C26.6726 16.3763 27.249 15.5878 27.2715 14.6797C27.2949 13.7197 26.7481 12.8922 25.8847 12.4842C25.3437 12.2282 24.768 12.1798 24.1792 12.1798H21.5948V19.8195H22.9404V17.0749H24.5728L26.2161 19.8202H27.7945L25.8488 16.7398ZM25.9159 14.5936C25.9159 14.9747 25.7573 15.3373 25.4522 15.5718C25.1549 15.8006 24.7815 15.8918 24.4119 15.9011C24.0655 15.911 23.0833 15.9011 22.9391 15.9011V13.3603H23.9987C24.4803 13.3603 25.0037 13.3414 25.422 13.6205C25.7528 13.8413 25.9172 14.2048 25.9172 14.5936H25.9159ZM34.8945 16.4867C34.7359 16.2335 34.5188 16.0219 34.2613 15.8694C34.4964 15.7261 34.6935 15.5285 34.8361 15.2934C35.0053 15.0102 35.0907 14.6822 35.0907 14.3194C35.0907 13.6522 34.8563 13.1219 34.3939 12.7434C33.938 12.3699 33.3135 12.1805 32.5372 12.1805H29.3809V19.8208H32.5779C33.3604 19.8208 33.9955 19.6288 34.4649 19.2483C34.9411 18.8643 35.1825 18.3018 35.1825 17.5805C35.1825 17.1552 35.0859 16.7872 34.8958 16.4867H34.8945ZM33.5303 18.3056C33.3106 18.4938 32.9899 18.5894 32.5766 18.5894H30.6858V16.5414H32.4954C32.9825 16.5414 33.3357 16.6333 33.5457 16.8147C33.7556 16.9962 33.8549 17.2387 33.8549 17.561C33.8558 17.8749 33.7499 18.1187 33.5315 18.3056H33.5303ZM33.4798 15.0128C33.291 15.1949 32.9838 15.2874 32.5667 15.2874H30.6858V13.3994H32.5359C32.9501 13.3994 33.2634 13.4819 33.467 13.6448C33.6706 13.8077 33.7627 14.024 33.7627 14.3168C33.764 14.6022 33.6715 14.8294 33.4811 15.0128H33.4798ZM38.313 13.4531V15.3533H41.457V16.6262H38.313V18.5366H42.0992V19.8198H37.0066V12.1802H42.1005V13.4531H38.313ZM8.04077 16L11.8738 19.8189H10.1429L6.72596 16.44V19.8192H5.38032V12.1795H6.72596V15.5773L10.1435 12.1795H11.8745L8.04077 16Z" fill="currentColor"/>
|
|
55
|
+
</svg>
|
|
56
|
+
</div>
|
|
57
|
+
<span slot="app-name">App Name</span>
|
|
58
|
+
<div slot="actions" style="display: flex; gap: 8px;">
|
|
59
|
+
<ds-icon-button icon="logout" variant="action"></ds-icon-button>
|
|
60
|
+
</div>
|
|
61
|
+
</ds-header>
|
|
62
|
+
`
|
|
63
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import './ds-header.js';
|
|
3
|
+
|
|
4
|
+
describe('ds-header', () => {
|
|
5
|
+
let container;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
container = document.createElement('div');
|
|
9
|
+
document.body.appendChild(container);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
container.remove();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('renders with default structure', async () => {
|
|
17
|
+
container.innerHTML = '<ds-header></ds-header>';
|
|
18
|
+
const element = container.querySelector('ds-header');
|
|
19
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
20
|
+
|
|
21
|
+
const header = element.shadowRoot.querySelector('.header');
|
|
22
|
+
expect(header).toBeTruthy();
|
|
23
|
+
expect(header.getAttribute('role')).toBe('banner');
|
|
24
|
+
|
|
25
|
+
const topBar = element.shadowRoot.querySelector('.top-bar');
|
|
26
|
+
expect(topBar).toBeTruthy();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders slots correctly', async () => {
|
|
30
|
+
container.innerHTML = `
|
|
31
|
+
<ds-header>
|
|
32
|
+
<button slot="menu-trigger">Menu</button>
|
|
33
|
+
<div slot="logo">Logo</div>
|
|
34
|
+
<span slot="app-name">App</span>
|
|
35
|
+
|
|
36
|
+
<div slot="actions">Actions</div>
|
|
37
|
+
</ds-header>
|
|
38
|
+
`;
|
|
39
|
+
const element = container.querySelector('ds-header');
|
|
40
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
41
|
+
|
|
42
|
+
const leftSection = element.shadowRoot.querySelector('.left-section');
|
|
43
|
+
|
|
44
|
+
const actionsSection = element.shadowRoot.querySelector('.actions-section');
|
|
45
|
+
|
|
46
|
+
expect(leftSection).toBeTruthy();
|
|
47
|
+
|
|
48
|
+
expect(actionsSection).toBeTruthy();
|
|
49
|
+
|
|
50
|
+
// Check slot distribution implicitly by section existence
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ds-header.js';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import axe from 'axe-core';
|
|
3
|
+
import './ds-header-nav.js';
|
|
4
|
+
import '../ds-header-nav-item/ds-header-nav-item.js';
|
|
5
|
+
|
|
6
|
+
describe('ds-header-nav a11y', () => {
|
|
7
|
+
let container;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
container = document.createElement('div');
|
|
11
|
+
document.body.appendChild(container);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
container.remove();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should pass axe accessibility checks with default state', async () => {
|
|
19
|
+
container.innerHTML = `
|
|
20
|
+
<ds-header-nav>
|
|
21
|
+
<ds-header-nav-item>Dashboard</ds-header-nav-item>
|
|
22
|
+
<ds-header-nav-item>Reports</ds-header-nav-item>
|
|
23
|
+
<ds-header-nav-item>Settings</ds-header-nav-item>
|
|
24
|
+
</ds-header-nav>
|
|
25
|
+
`;
|
|
26
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
27
|
+
|
|
28
|
+
const results = await axe.run(container, {
|
|
29
|
+
rules: { 'color-contrast': { enabled: false } }
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (results.violations.length > 0) {
|
|
33
|
+
console.log('Violations:', JSON.stringify(results.violations.map(v => ({ id: v.id, description: v.description, help: v.help })), null, 2));
|
|
34
|
+
}
|
|
35
|
+
expect(results.violations).toHaveLength(0);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should pass axe accessibility checks with selected item', async () => {
|
|
39
|
+
container.innerHTML = `
|
|
40
|
+
<ds-header-nav>
|
|
41
|
+
<ds-header-nav-item selected>Dashboard</ds-header-nav-item>
|
|
42
|
+
<ds-header-nav-item>Reports</ds-header-nav-item>
|
|
43
|
+
</ds-header-nav>
|
|
44
|
+
`;
|
|
45
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
46
|
+
|
|
47
|
+
const results = await axe.run(container, {
|
|
48
|
+
rules: { 'color-contrast': { enabled: false } }
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(results.violations).toHaveLength(0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should pass axe accessibility checks with links', async () => {
|
|
55
|
+
container.innerHTML = `
|
|
56
|
+
<ds-header-nav>
|
|
57
|
+
<ds-header-nav-item href="/dashboard">Dashboard</ds-header-nav-item>
|
|
58
|
+
<ds-header-nav-item href="/reports" selected>Reports</ds-header-nav-item>
|
|
59
|
+
</ds-header-nav>
|
|
60
|
+
`;
|
|
61
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
62
|
+
|
|
63
|
+
const results = await axe.run(container, {
|
|
64
|
+
rules: { 'color-contrast': { enabled: false } }
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(results.violations).toHaveLength(0);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import '../ds-header-nav-item/ds-header-nav-item.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Container for header navigation items.
|
|
6
|
+
* Manages selection state of children.
|
|
7
|
+
*
|
|
8
|
+
* @element ds-header-nav
|
|
9
|
+
*
|
|
10
|
+
* @slot - Default slot for ds-header-nav-item elements
|
|
11
|
+
*
|
|
12
|
+
* @fires ds-nav-change - Fired when selection changes
|
|
13
|
+
*/
|
|
14
|
+
export class DsHeaderNav extends LitElement {
|
|
15
|
+
static properties = {
|
|
16
|
+
value: { type: String, reflect: true }
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
static styles = css`
|
|
20
|
+
:host {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
height: 100%;
|
|
24
|
+
gap: var(--ds-space-sm);
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
super();
|
|
30
|
+
this.value = '';
|
|
31
|
+
this._selectedItem = null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
connectedCallback() {
|
|
35
|
+
super.connectedCallback();
|
|
36
|
+
// Since ds-header-nav-item now dispatches events (or we will ensure it does),
|
|
37
|
+
// we listen for selection. If ds-header-nav-item is a link <a>, it naturally clicks.
|
|
38
|
+
// We need to ensure we intercept clicks if we want to manage "selected" state
|
|
39
|
+
// visually for SPA behavior, or rely on URL matching.
|
|
40
|
+
// Given the requirement "only 1 element selected", we imply controlled state.
|
|
41
|
+
|
|
42
|
+
this.addEventListener('click', this._handleClick);
|
|
43
|
+
|
|
44
|
+
// Initial setup
|
|
45
|
+
this.updateComplete.then(() => {
|
|
46
|
+
this._syncSelection();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
disconnectedCallback() {
|
|
51
|
+
super.disconnectedCallback();
|
|
52
|
+
this.removeEventListener('click', this._handleClick);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
updated(changedProperties) {
|
|
56
|
+
if (changedProperties.has('value')) {
|
|
57
|
+
this._syncSelection();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_handleClick(e) {
|
|
62
|
+
// Find the clicked nav item
|
|
63
|
+
const item = e.target.closest('ds-header-nav-item');
|
|
64
|
+
if (!item) return;
|
|
65
|
+
|
|
66
|
+
// Update selection locally
|
|
67
|
+
this._selectItem(item);
|
|
68
|
+
|
|
69
|
+
// Dispatch change event
|
|
70
|
+
// Note: 'value' prop on item is not yet defined in our previous step,
|
|
71
|
+
// but we can use label or href as value fallback or just pass the item.
|
|
72
|
+
// Let's assume user might want to bind to a value later.
|
|
73
|
+
// For now we pass the item itself.
|
|
74
|
+
this.dispatchEvent(new CustomEvent('ds-nav-change', {
|
|
75
|
+
detail: { item, href: item.href },
|
|
76
|
+
bubbles: true,
|
|
77
|
+
composed: true
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_selectItem(selectedItem) {
|
|
82
|
+
const items = this._getAllItems();
|
|
83
|
+
items.forEach(item => {
|
|
84
|
+
const isSelected = item === selectedItem;
|
|
85
|
+
item.selected = isSelected;
|
|
86
|
+
if (isSelected) {
|
|
87
|
+
this.value = item.getAttribute('href') || ''; // Sync generic value
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
_syncSelection() {
|
|
93
|
+
// If value is provided, select matching item (by href presumably)
|
|
94
|
+
if (!this.value) return;
|
|
95
|
+
const items = this._getAllItems();
|
|
96
|
+
items.forEach(item => {
|
|
97
|
+
if (item.getAttribute('href') === this.value) {
|
|
98
|
+
item.selected = true;
|
|
99
|
+
} else {
|
|
100
|
+
item.selected = false;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_getAllItems() {
|
|
106
|
+
return Array.from(this.querySelectorAll('ds-header-nav-item'));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
render() {
|
|
110
|
+
return html`<slot></slot>`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
customElements.define('ds-header-nav', DsHeaderNav);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import './ds-header-nav.js';
|
|
3
|
+
import '../ds-header-nav-item/ds-header-nav-item.js';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Components/Header Nav',
|
|
7
|
+
component: 'ds-header-nav'
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const Group = () => html`
|
|
11
|
+
<ds-header-nav>
|
|
12
|
+
<ds-header-nav-item selected>Dashboard</ds-header-nav-item>
|
|
13
|
+
<ds-header-nav-item>Projects</ds-header-nav-item>
|
|
14
|
+
<ds-header-nav-item>Team</ds-header-nav-item>
|
|
15
|
+
<ds-header-nav-item icon="settings">Settings</ds-header-nav-item>
|
|
16
|
+
</ds-header-nav>
|
|
17
|
+
`;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import './ds-header-nav.js';
|
|
3
|
+
import '../ds-header-nav-item/ds-header-nav-item.js';
|
|
4
|
+
|
|
5
|
+
describe('ds-header-nav', () => {
|
|
6
|
+
let container;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
container = document.createElement('div');
|
|
10
|
+
document.body.appendChild(container);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
container.remove();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('renders slotted items', async () => {
|
|
18
|
+
container.innerHTML = `
|
|
19
|
+
<ds-header-nav>
|
|
20
|
+
<ds-header-nav-item>Item 1</ds-header-nav-item>
|
|
21
|
+
<ds-header-nav-item>Item 2</ds-header-nav-item>
|
|
22
|
+
</ds-header-nav>
|
|
23
|
+
`;
|
|
24
|
+
const element = container.querySelector('ds-header-nav');
|
|
25
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
26
|
+
|
|
27
|
+
const items = element.querySelectorAll('ds-header-nav-item');
|
|
28
|
+
expect(items.length).toBe(2);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('manages single selection via click', async () => {
|
|
32
|
+
container.innerHTML = `
|
|
33
|
+
<ds-header-nav>
|
|
34
|
+
<ds-header-nav-item id="item1">Item 1</ds-header-nav-item>
|
|
35
|
+
<ds-header-nav-item id="item2">Item 2</ds-header-nav-item>
|
|
36
|
+
</ds-header-nav>
|
|
37
|
+
`;
|
|
38
|
+
const element = container.querySelector('ds-header-nav');
|
|
39
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
40
|
+
|
|
41
|
+
const item1 = container.querySelector('#item1');
|
|
42
|
+
const item2 = container.querySelector('#item2');
|
|
43
|
+
|
|
44
|
+
// Click item 1 (simulate via dispatching click as items might not have href/button logic fully wired for programmatic click in jsdom context easily without full render)
|
|
45
|
+
// Actually, our component listens for 'click' on itself and finds target.
|
|
46
|
+
item1.click();
|
|
47
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
48
|
+
|
|
49
|
+
expect(item1.hasAttribute('selected')).toBe(true);
|
|
50
|
+
expect(item2.hasAttribute('selected')).toBe(false);
|
|
51
|
+
|
|
52
|
+
// Click item 2
|
|
53
|
+
item2.click();
|
|
54
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
55
|
+
|
|
56
|
+
expect(item1.hasAttribute('selected')).toBe(false);
|
|
57
|
+
expect(item2.hasAttribute('selected')).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('syncs selection via value prop', async () => {
|
|
61
|
+
container.innerHTML = `
|
|
62
|
+
<ds-header-nav value="/page2">
|
|
63
|
+
<ds-header-nav-item href="/page1">Item 1</ds-header-nav-item>
|
|
64
|
+
<ds-header-nav-item href="/page2">Item 2</ds-header-nav-item>
|
|
65
|
+
</ds-header-nav>
|
|
66
|
+
`;
|
|
67
|
+
const element = container.querySelector('ds-header-nav');
|
|
68
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
69
|
+
|
|
70
|
+
const items = element.querySelectorAll('ds-header-nav-item');
|
|
71
|
+
expect(items[0].hasAttribute('selected')).toBe(false);
|
|
72
|
+
expect(items[1].hasAttribute('selected')).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('dispatches ds-nav-change on selection', async () => {
|
|
76
|
+
const spy = vi.fn();
|
|
77
|
+
container.innerHTML = `
|
|
78
|
+
<ds-header-nav>
|
|
79
|
+
<ds-header-nav-item href="/test">Item 1</ds-header-nav-item>
|
|
80
|
+
</ds-header-nav>
|
|
81
|
+
`;
|
|
82
|
+
const element = container.querySelector('ds-header-nav');
|
|
83
|
+
element.addEventListener('ds-nav-change', spy);
|
|
84
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
85
|
+
|
|
86
|
+
const item = container.querySelector('ds-header-nav-item');
|
|
87
|
+
item.click();
|
|
88
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
89
|
+
|
|
90
|
+
expect(spy).toHaveBeenCalled();
|
|
91
|
+
expect(spy.mock.calls[0][0].detail.href).toBe('/test');
|
|
92
|
+
});
|
|
93
|
+
});
|