@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,173 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import '../ds-nav-item/ds-nav-item.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vertical navigation container component
|
|
6
|
+
*
|
|
7
|
+
* @element ds-nav-vertical
|
|
8
|
+
*
|
|
9
|
+
* @prop {string} value - Currently selected item value (optional, for programmatic control)
|
|
10
|
+
*
|
|
11
|
+
* @slot - Default slot for ds-nav-item elements
|
|
12
|
+
*
|
|
13
|
+
* @fires ds-nav-change - Fired when selection changes, detail includes { value, label, href, item }
|
|
14
|
+
*
|
|
15
|
+
* @csspart container - The main container element
|
|
16
|
+
*/
|
|
17
|
+
export class DsNavVertical extends LitElement {
|
|
18
|
+
static properties = {
|
|
19
|
+
value: { type: String, reflect: true }
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
static styles = css`
|
|
23
|
+
:host {
|
|
24
|
+
display: block;
|
|
25
|
+
box-sizing: border-box;
|
|
26
|
+
padding: var(--ds-space-lg) var(--ds-space-sm); /* 16px 8px */
|
|
27
|
+
background-color: var(--ds-color-bg-panel-default);
|
|
28
|
+
height: 100%;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
nav {
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
gap: var(--ds-space-sm); /* 8px */
|
|
35
|
+
height: 100%;
|
|
36
|
+
box-sizing: border-box;
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
constructor() {
|
|
41
|
+
super();
|
|
42
|
+
this.value = '';
|
|
43
|
+
this._selectedItem = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
connectedCallback() {
|
|
47
|
+
super.connectedCallback();
|
|
48
|
+
// Listen for selection events from nav items
|
|
49
|
+
this.addEventListener('ds-nav-select', this._handleNavSelect);
|
|
50
|
+
|
|
51
|
+
// Initial setup after render
|
|
52
|
+
this.updateComplete.then(() => {
|
|
53
|
+
this._updateLevels();
|
|
54
|
+
this._syncSelection();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
disconnectedCallback() {
|
|
59
|
+
super.disconnectedCallback();
|
|
60
|
+
this.removeEventListener('ds-nav-select', this._handleNavSelect);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
updated(changedProperties) {
|
|
64
|
+
if (changedProperties.has('value')) {
|
|
65
|
+
this._syncSelection();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Handle selection event from nav items
|
|
71
|
+
*/
|
|
72
|
+
_handleNavSelect = (e) => {
|
|
73
|
+
const { value, label, href, item } = e.detail;
|
|
74
|
+
|
|
75
|
+
// Prevent event from bubbling further (we'll dispatch our own)
|
|
76
|
+
e.stopPropagation();
|
|
77
|
+
|
|
78
|
+
// Deselect previous item
|
|
79
|
+
if (this._selectedItem && this._selectedItem !== item) {
|
|
80
|
+
this._selectedItem.selected = false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Select new item
|
|
84
|
+
item.selected = true;
|
|
85
|
+
this._selectedItem = item;
|
|
86
|
+
this.value = value;
|
|
87
|
+
|
|
88
|
+
// Update parent bold states
|
|
89
|
+
this._updateParentStates(item);
|
|
90
|
+
|
|
91
|
+
// Dispatch change event
|
|
92
|
+
this.dispatchEvent(new CustomEvent('ds-nav-change', {
|
|
93
|
+
detail: { value, label, href, item },
|
|
94
|
+
bubbles: true,
|
|
95
|
+
composed: true
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Sync selection state based on value prop
|
|
101
|
+
*/
|
|
102
|
+
_syncSelection() {
|
|
103
|
+
if (!this.value) return;
|
|
104
|
+
|
|
105
|
+
const items = this._getAllItems();
|
|
106
|
+
items.forEach(item => {
|
|
107
|
+
if (item.value === this.value) {
|
|
108
|
+
item.selected = true;
|
|
109
|
+
this._selectedItem = item;
|
|
110
|
+
this._updateParentStates(item);
|
|
111
|
+
} else {
|
|
112
|
+
item.selected = false;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Update level property on all nested items
|
|
119
|
+
*/
|
|
120
|
+
_updateLevels() {
|
|
121
|
+
const processItems = (parent, level) => {
|
|
122
|
+
const children = parent.querySelectorAll(':scope > ds-nav-item');
|
|
123
|
+
children.forEach(item => {
|
|
124
|
+
item.level = level;
|
|
125
|
+
// Process children of this item
|
|
126
|
+
processItems(item, level + 1);
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Start with direct children at level 0
|
|
131
|
+
processItems(this, 0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Propagate child-selected state to parent items
|
|
136
|
+
*/
|
|
137
|
+
_updateParentStates(selectedItem) {
|
|
138
|
+
const allItems = this._getAllItems();
|
|
139
|
+
// Clear previous state
|
|
140
|
+
allItems.forEach(item => { item.childSelected = false; });
|
|
141
|
+
|
|
142
|
+
if (!selectedItem) return;
|
|
143
|
+
|
|
144
|
+
// Traverse up
|
|
145
|
+
let parent = selectedItem.parentElement;
|
|
146
|
+
while (parent && parent.tagName === 'DS-NAV-ITEM') {
|
|
147
|
+
parent.childSelected = true;
|
|
148
|
+
parent = parent.parentElement;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get all ds-nav-item descendants
|
|
154
|
+
*/
|
|
155
|
+
_getAllItems() {
|
|
156
|
+
return Array.from(this.querySelectorAll('ds-nav-item'));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_handleSlotChange() {
|
|
160
|
+
this._updateLevels();
|
|
161
|
+
this._syncSelection();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
render() {
|
|
165
|
+
return html`
|
|
166
|
+
<nav part="container" role="navigation" aria-label="Main navigation">
|
|
167
|
+
<slot @slotchange="${this._handleSlotChange}"></slot>
|
|
168
|
+
</nav>
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
customElements.define('ds-nav-vertical', DsNavVertical);
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import './ds-nav-vertical.js';
|
|
3
|
+
import '../ds-nav-item/ds-nav-item.js';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Components/Nav Vertical',
|
|
7
|
+
component: 'ds-nav-vertical',
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
argTypes: {
|
|
10
|
+
value: {
|
|
11
|
+
control: 'text',
|
|
12
|
+
description: 'Currently selected item value'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Basic navigation with items
|
|
19
|
+
*/
|
|
20
|
+
export const Default = {
|
|
21
|
+
render: () => html`
|
|
22
|
+
<ds-nav-vertical style="width: 280px;">
|
|
23
|
+
<ds-nav-item value="dashboard" label="Dashboard" icon="apps"></ds-nav-item>
|
|
24
|
+
<ds-nav-item value="orders" label="Orders" icon="local-shipping"></ds-nav-item>
|
|
25
|
+
<ds-nav-item value="customers" label="Customers" icon="groups"></ds-nav-item>
|
|
26
|
+
<ds-nav-item value="settings" label="Settings" icon="settings"></ds-nav-item>
|
|
27
|
+
</ds-nav-vertical>
|
|
28
|
+
`
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Navigation with pre-selected item
|
|
33
|
+
*/
|
|
34
|
+
export const WithSelectedItem = {
|
|
35
|
+
render: () => html`
|
|
36
|
+
<ds-nav-vertical value="dashboard" style="width: 280px;">
|
|
37
|
+
<ds-nav-item value="dashboard" label="Dashboard" icon="apps"></ds-nav-item>
|
|
38
|
+
<ds-nav-item value="orders" label="Orders" icon="local-shipping"></ds-nav-item>
|
|
39
|
+
<ds-nav-item value="customers" label="Customers" icon="groups"></ds-nav-item>
|
|
40
|
+
<ds-nav-item value="settings" label="Settings" icon="settings"></ds-nav-item>
|
|
41
|
+
</ds-nav-vertical>
|
|
42
|
+
`
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Navigation with nested/collapsible items
|
|
47
|
+
*/
|
|
48
|
+
export const WithNestedItems = {
|
|
49
|
+
render: () => html`
|
|
50
|
+
<ds-nav-vertical value="all-products" style="width: 280px;">
|
|
51
|
+
<ds-nav-item value="dashboard" label="Dashboard" icon="apps"></ds-nav-item>
|
|
52
|
+
<ds-nav-item value="products" label="Products" icon="folder" expanded>
|
|
53
|
+
<ds-nav-item value="all-products" label="All Products"></ds-nav-item>
|
|
54
|
+
<ds-nav-item value="categories" label="Categories"></ds-nav-item>
|
|
55
|
+
<ds-nav-item value="inventory" label="Inventory"></ds-nav-item>
|
|
56
|
+
</ds-nav-item>
|
|
57
|
+
<ds-nav-item value="orders" label="Orders" icon="local-shipping"></ds-nav-item>
|
|
58
|
+
</ds-nav-vertical>
|
|
59
|
+
`
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Navigation with multiple levels of nesting
|
|
64
|
+
*/
|
|
65
|
+
export const DeepNesting = {
|
|
66
|
+
render: () => html`
|
|
67
|
+
<ds-nav-vertical style="width: 280px;">
|
|
68
|
+
<ds-nav-item value="home" label="Home" icon="home"></ds-nav-item>
|
|
69
|
+
<ds-nav-item value="level-1" label="Level 1" icon="folder" expanded>
|
|
70
|
+
<ds-nav-item value="level-2a" label="Level 2A" expanded>
|
|
71
|
+
<ds-nav-item value="level-3" label="Level 3"></ds-nav-item>
|
|
72
|
+
</ds-nav-item>
|
|
73
|
+
<ds-nav-item value="level-2b" label="Level 2B"></ds-nav-item>
|
|
74
|
+
</ds-nav-item>
|
|
75
|
+
</ds-nav-vertical>
|
|
76
|
+
`
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Full nav vertical example (replaces Full Sidebar)
|
|
81
|
+
*/
|
|
82
|
+
export const FullNavVertical = {
|
|
83
|
+
render: () => html`
|
|
84
|
+
<div style="width: 280px; height: 600px; border-right: 1px solid var(--ds-color-border-default);">
|
|
85
|
+
<ds-nav-vertical value="dashboard">
|
|
86
|
+
<ds-nav-item value="dashboard" label="Dashboard" icon="apps"></ds-nav-item>
|
|
87
|
+
<ds-nav-item value="products" label="Products" icon="folder" expanded>
|
|
88
|
+
<ds-nav-item value="all-products" label="All Products"></ds-nav-item>
|
|
89
|
+
<ds-nav-item value="categories" label="Categories"></ds-nav-item>
|
|
90
|
+
<ds-nav-item value="inventory" label="Inventory"></ds-nav-item>
|
|
91
|
+
</ds-nav-item>
|
|
92
|
+
<ds-nav-item value="orders" label="Orders" icon="local-shipping"></ds-nav-item>
|
|
93
|
+
<ds-nav-item value="customers" label="Customers" icon="groups"></ds-nav-item>
|
|
94
|
+
<ds-nav-item value="analytics" label="Analytics" icon="bar-chart" expanded>
|
|
95
|
+
<ds-nav-item value="overview" label="Overview"></ds-nav-item>
|
|
96
|
+
<ds-nav-item value="reports" label="Reports"></ds-nav-item>
|
|
97
|
+
</ds-nav-item>
|
|
98
|
+
<ds-nav-item value="settings" label="Settings" icon="settings"></ds-nav-item>
|
|
99
|
+
</ds-nav-vertical>
|
|
100
|
+
</div>
|
|
101
|
+
`
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Navigation with event handling
|
|
106
|
+
*/
|
|
107
|
+
export const WithEventHandling = {
|
|
108
|
+
render: () => html`
|
|
109
|
+
<div>
|
|
110
|
+
<ds-nav-vertical
|
|
111
|
+
style="width: 280px;"
|
|
112
|
+
@ds-nav-change="${(e) => {
|
|
113
|
+
document.getElementById('nav-output').textContent =
|
|
114
|
+
'Selected: ' + e.detail.value + ' (' + e.detail.label + ')';
|
|
115
|
+
}}"
|
|
116
|
+
>
|
|
117
|
+
<ds-nav-item value="home" label="Home" icon="home"></ds-nav-item>
|
|
118
|
+
<ds-nav-item value="about" label="About" icon="info"></ds-nav-item>
|
|
119
|
+
<ds-nav-item value="contact" label="Contact" icon="groups"></ds-nav-item>
|
|
120
|
+
</ds-nav-vertical>
|
|
121
|
+
<p id="nav-output" style="margin-top: 16px; color: var(--ds-color-text-default);">Click an item to see the event</p>
|
|
122
|
+
</div>
|
|
123
|
+
`
|
|
124
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import './ds-nav-vertical.js';
|
|
3
|
+
import '../ds-nav-item/ds-nav-item.js';
|
|
4
|
+
|
|
5
|
+
describe('ds-nav-vertical', () => {
|
|
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 with default values', async () => {
|
|
18
|
+
container.innerHTML = '<ds-nav-vertical></ds-nav-vertical>';
|
|
19
|
+
const element = container.querySelector('ds-nav-vertical');
|
|
20
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
21
|
+
|
|
22
|
+
expect(element.value).toBe('');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders slotted nav items', async () => {
|
|
26
|
+
container.innerHTML = `
|
|
27
|
+
<ds-nav-vertical>
|
|
28
|
+
<ds-nav-item value="home" label="Home"></ds-nav-item>
|
|
29
|
+
<ds-nav-item value="about" label="About"></ds-nav-item>
|
|
30
|
+
</ds-nav-vertical>
|
|
31
|
+
`;
|
|
32
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
33
|
+
|
|
34
|
+
const items = container.querySelectorAll('ds-nav-item');
|
|
35
|
+
expect(items.length).toBe(2);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('selects item on click and updates value', async () => {
|
|
39
|
+
container.innerHTML = `
|
|
40
|
+
<ds-nav-vertical>
|
|
41
|
+
<ds-nav-item value="home" label="Home" icon="home"></ds-nav-item>
|
|
42
|
+
<ds-nav-item value="settings" label="Settings" icon="settings"></ds-nav-item>
|
|
43
|
+
</ds-nav-vertical>
|
|
44
|
+
`;
|
|
45
|
+
const nav = container.querySelector('ds-nav-vertical');
|
|
46
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
47
|
+
|
|
48
|
+
const homeItem = container.querySelector('ds-nav-item[value="home"]');
|
|
49
|
+
const button = homeItem.shadowRoot.querySelector('.nav-item');
|
|
50
|
+
button.click();
|
|
51
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
52
|
+
|
|
53
|
+
expect(homeItem.selected).toBe(true);
|
|
54
|
+
expect(nav.value).toBe('home');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('deselects previous item when new item is selected', async () => {
|
|
58
|
+
container.innerHTML = `
|
|
59
|
+
<ds-nav-vertical>
|
|
60
|
+
<ds-nav-item value="home" label="Home"></ds-nav-item>
|
|
61
|
+
<ds-nav-item value="settings" label="Settings"></ds-nav-item>
|
|
62
|
+
</ds-nav-vertical>
|
|
63
|
+
`;
|
|
64
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
65
|
+
|
|
66
|
+
const homeItem = container.querySelector('ds-nav-item[value="home"]');
|
|
67
|
+
const settingsItem = container.querySelector('ds-nav-item[value="settings"]');
|
|
68
|
+
|
|
69
|
+
// Click home
|
|
70
|
+
homeItem.shadowRoot.querySelector('.nav-item').click();
|
|
71
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
72
|
+
expect(homeItem.selected).toBe(true);
|
|
73
|
+
|
|
74
|
+
// Click settings
|
|
75
|
+
settingsItem.shadowRoot.querySelector('.nav-item').click();
|
|
76
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
77
|
+
|
|
78
|
+
expect(settingsItem.selected).toBe(true);
|
|
79
|
+
expect(homeItem.selected).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('dispatches ds-nav-change event on selection', async () => {
|
|
83
|
+
container.innerHTML = `
|
|
84
|
+
<ds-nav-vertical>
|
|
85
|
+
<ds-nav-item value="home" label="Home"></ds-nav-item>
|
|
86
|
+
</ds-nav-vertical>
|
|
87
|
+
`;
|
|
88
|
+
const nav = container.querySelector('ds-nav-vertical');
|
|
89
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
90
|
+
|
|
91
|
+
const changeSpy = vi.fn();
|
|
92
|
+
nav.addEventListener('ds-nav-change', changeSpy);
|
|
93
|
+
|
|
94
|
+
const homeItem = container.querySelector('ds-nav-item[value="home"]');
|
|
95
|
+
homeItem.shadowRoot.querySelector('.nav-item').click();
|
|
96
|
+
|
|
97
|
+
expect(changeSpy).toHaveBeenCalledTimes(1);
|
|
98
|
+
expect(changeSpy.mock.calls[0][0].detail.value).toBe('home');
|
|
99
|
+
expect(changeSpy.mock.calls[0][0].detail.label).toBe('Home');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('sets initial selection based on value prop', async () => {
|
|
103
|
+
container.innerHTML = `
|
|
104
|
+
<ds-nav-vertical value="settings">
|
|
105
|
+
<ds-nav-item value="home" label="Home"></ds-nav-item>
|
|
106
|
+
<ds-nav-item value="settings" label="Settings"></ds-nav-item>
|
|
107
|
+
</ds-nav-vertical>
|
|
108
|
+
`;
|
|
109
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
110
|
+
|
|
111
|
+
const settingsItem = container.querySelector('ds-nav-item[value="settings"]');
|
|
112
|
+
expect(settingsItem.selected).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('propagates level to nested items', async () => {
|
|
116
|
+
container.innerHTML = `
|
|
117
|
+
<ds-nav-vertical>
|
|
118
|
+
<ds-nav-item value="parent" label="Parent">
|
|
119
|
+
<ds-nav-item value="child" label="Child"></ds-nav-item>
|
|
120
|
+
</ds-nav-item>
|
|
121
|
+
</ds-nav-vertical>
|
|
122
|
+
`;
|
|
123
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
124
|
+
|
|
125
|
+
const parent = container.querySelector('ds-nav-item[value="parent"]');
|
|
126
|
+
const child = container.querySelector('ds-nav-item[value="child"]');
|
|
127
|
+
|
|
128
|
+
expect(parent.level).toBe(0);
|
|
129
|
+
expect(child.level).toBe(1);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('propagates child-selected state to parent items', async () => {
|
|
133
|
+
container.innerHTML = `
|
|
134
|
+
<ds-nav-vertical>
|
|
135
|
+
<ds-nav-item value="parent" label="Parent">
|
|
136
|
+
<ds-nav-item value="child" label="Child">
|
|
137
|
+
<ds-nav-item value="grandchild" label="Grandchild"></ds-nav-item>
|
|
138
|
+
</ds-nav-item>
|
|
139
|
+
</ds-nav-item>
|
|
140
|
+
</ds-nav-vertical>
|
|
141
|
+
`;
|
|
142
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
143
|
+
|
|
144
|
+
const parent = container.querySelector('ds-nav-item[value="parent"]');
|
|
145
|
+
const child = container.querySelector('ds-nav-item[value="child"]');
|
|
146
|
+
const grandchild = container.querySelector('ds-nav-item[value="grandchild"]');
|
|
147
|
+
|
|
148
|
+
// Select grandchild
|
|
149
|
+
grandchild.shadowRoot.querySelector('.nav-item').click();
|
|
150
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
151
|
+
|
|
152
|
+
expect(grandchild.selected).toBe(true);
|
|
153
|
+
expect(child.childSelected).toBe(true);
|
|
154
|
+
expect(parent.childSelected).toBe(true);
|
|
155
|
+
|
|
156
|
+
// Click child (parent item): should only toggle, not change selection
|
|
157
|
+
const childButton = child.shadowRoot.querySelector('.nav-item');
|
|
158
|
+
const initialExpanded = child.expanded;
|
|
159
|
+
childButton.click();
|
|
160
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
161
|
+
|
|
162
|
+
expect(child.expanded).toBe(!initialExpanded);
|
|
163
|
+
expect(child.selected).toBe(false); // Parent items with children aren't selectable via click
|
|
164
|
+
expect(grandchild.selected).toBe(true); // Grandchild remains selected
|
|
165
|
+
expect(child.childSelected).toBe(true); // Still true as descendant is selected
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('has navigation role for accessibility', async () => {
|
|
169
|
+
container.innerHTML = '<ds-nav-vertical></ds-nav-vertical>';
|
|
170
|
+
const nav = container.querySelector('ds-nav-vertical');
|
|
171
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
172
|
+
|
|
173
|
+
const navElement = nav.shadowRoot.querySelector('nav');
|
|
174
|
+
expect(navElement.getAttribute('role')).toBe('navigation');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DsNavVertical } from './ds-nav-vertical.js';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import axe from 'axe-core';
|
|
3
|
+
import './ds-pagination.js';
|
|
4
|
+
|
|
5
|
+
describe('ds-pagination 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 for default variant', async () => {
|
|
18
|
+
container.innerHTML = '<ds-pagination total="5" current="3"></ds-pagination>';
|
|
19
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
20
|
+
|
|
21
|
+
const options = {
|
|
22
|
+
rules: {
|
|
23
|
+
'color-contrast': { enabled: false }
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const results = await axe.run(container, options);
|
|
28
|
+
if (results.violations.length > 0) {
|
|
29
|
+
console.log('Violations for default variant:', JSON.stringify(results.violations, null, 2));
|
|
30
|
+
}
|
|
31
|
+
expect(results.violations).toHaveLength(0);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should pass axe accessibility checks for dot variant', async () => {
|
|
35
|
+
container.innerHTML = '<ds-pagination variant="dot" total="5" current="3"></ds-pagination>';
|
|
36
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
37
|
+
|
|
38
|
+
const options = {
|
|
39
|
+
rules: {
|
|
40
|
+
'color-contrast': { enabled: false }
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const results = await axe.run(container, options);
|
|
45
|
+
if (results.violations.length > 0) {
|
|
46
|
+
console.log('Violations for dot variant:', JSON.stringify(results.violations, null, 2));
|
|
47
|
+
}
|
|
48
|
+
expect(results.violations).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
});
|