@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.
Files changed (230) hide show
  1. package/package.json +27 -0
  2. package/src/ds-accordion/ds-accordion-item.js +288 -0
  3. package/src/ds-accordion/ds-accordion-item.stories.js +82 -0
  4. package/src/ds-accordion/ds-accordion.a11y.test.js +92 -0
  5. package/src/ds-accordion/ds-accordion.js +68 -0
  6. package/src/ds-accordion/ds-accordion.stories.js +118 -0
  7. package/src/ds-accordion/ds-accordion.test.js +146 -0
  8. package/src/ds-accordion/index.js +2 -0
  9. package/src/ds-action-bar/ds-action-bar.js +116 -0
  10. package/src/ds-action-bar/ds-action-bar.stories.js +86 -0
  11. package/src/ds-action-bar/ds-action-bar.test.js +64 -0
  12. package/src/ds-action-bar/index.js +1 -0
  13. package/src/ds-alert/ds-alert.a11y.test.js +151 -0
  14. package/src/ds-alert/ds-alert.js +223 -0
  15. package/src/ds-alert/ds-alert.mdx +142 -0
  16. package/src/ds-alert/ds-alert.stories.js +166 -0
  17. package/src/ds-alert/ds-alert.test.js +256 -0
  18. package/src/ds-alert/index.js +1 -0
  19. package/src/ds-avatar/ds-avatar.a11y.test.js +45 -0
  20. package/src/ds-avatar/ds-avatar.js +216 -0
  21. package/src/ds-avatar/ds-avatar.stories.js +120 -0
  22. package/src/ds-avatar/ds-avatar.test.js +83 -0
  23. package/src/ds-avatar/index.js +1 -0
  24. package/src/ds-avatar-extended/ds-avatar-extended.a11y.test.js +29 -0
  25. package/src/ds-avatar-extended/ds-avatar-extended.js +108 -0
  26. package/src/ds-avatar-extended/ds-avatar-extended.stories.js +93 -0
  27. package/src/ds-avatar-extended/ds-avatar-extended.test.js +66 -0
  28. package/src/ds-avatar-extended/index.js +1 -0
  29. package/src/ds-banner/ds-banner.a11y.test.js +51 -0
  30. package/src/ds-banner/ds-banner.js +233 -0
  31. package/src/ds-banner/ds-banner.stories.js +185 -0
  32. package/src/ds-banner/ds-banner.test.js +116 -0
  33. package/src/ds-banner/index.js +1 -0
  34. package/src/ds-breadcrumb-item/ds-breadcrumb-item.js +135 -0
  35. package/src/ds-breadcrumb-item/ds-breadcrumb-item.stories.js +49 -0
  36. package/src/ds-breadcrumb-item/ds-breadcrumb-item.test.js +55 -0
  37. package/src/ds-breadcrumbs/ds-breadcrumbs.js +194 -0
  38. package/src/ds-breadcrumbs/ds-breadcrumbs.stories.js +54 -0
  39. package/src/ds-breadcrumbs/ds-breadcrumbs.test.js +33 -0
  40. package/src/ds-button/ds-button.a11y.test.js +49 -0
  41. package/src/ds-button/ds-button.js +205 -0
  42. package/src/ds-button/ds-button.mdx +141 -0
  43. package/src/ds-button/ds-button.stories.js +152 -0
  44. package/src/ds-button/ds-button.test.js +62 -0
  45. package/src/ds-button/index.js +1 -0
  46. package/src/ds-button-group/ds-button-group.js +82 -0
  47. package/src/ds-button-group/ds-button-group.mdx +39 -0
  48. package/src/ds-button-group/ds-button-group.stories.js +47 -0
  49. package/src/ds-button-group/ds-button-group.test.js +47 -0
  50. package/src/ds-button-group/index.js +1 -0
  51. package/src/ds-checkbox/ds-checkbox.a11y.test.js +79 -0
  52. package/src/ds-checkbox/ds-checkbox.js +271 -0
  53. package/src/ds-checkbox/ds-checkbox.stories.js +77 -0
  54. package/src/ds-checkbox/ds-checkbox.test.js +191 -0
  55. package/src/ds-checkbox/index.js +1 -0
  56. package/src/ds-checkbox-group/ds-checkbox-group.a11y.test.js +146 -0
  57. package/src/ds-checkbox-group/ds-checkbox-group.js +235 -0
  58. package/src/ds-checkbox-group/ds-checkbox-group.stories.js +210 -0
  59. package/src/ds-checkbox-group/ds-checkbox-group.test.js +150 -0
  60. package/src/ds-checkbox-group/index.js +1 -0
  61. package/src/ds-dialog/ds-dialog.js +466 -0
  62. package/src/ds-dialog/ds-dialog.stories.js +274 -0
  63. package/src/ds-dialog/ds-dialog.test.js +441 -0
  64. package/src/ds-dialog/index.js +1 -0
  65. package/src/ds-dropdown/ds-dropdown.a11y.test.js +80 -0
  66. package/src/ds-dropdown/ds-dropdown.js +891 -0
  67. package/src/ds-dropdown/ds-dropdown.stories.js +259 -0
  68. package/src/ds-dropdown/ds-dropdown.test.js +268 -0
  69. package/src/ds-dropdown/index.js +1 -0
  70. package/src/ds-dropdown-group/ds-dropdown-group.js +55 -0
  71. package/src/ds-dropdown-panel/ds-dropdown-panel.js +34 -0
  72. package/src/ds-file-uploaded/ds-file-uploaded.a11y.test.js +40 -0
  73. package/src/ds-file-uploaded/ds-file-uploaded.js +135 -0
  74. package/src/ds-file-uploaded/ds-file-uploaded.mdx +33 -0
  75. package/src/ds-file-uploaded/ds-file-uploaded.stories.js +81 -0
  76. package/src/ds-file-uploaded/ds-file-uploaded.test.js +85 -0
  77. package/src/ds-file-uploader/ds-file-uploader.a11y.test.js +61 -0
  78. package/src/ds-file-uploader/ds-file-uploader.js +442 -0
  79. package/src/ds-file-uploader/ds-file-uploader.mdx +44 -0
  80. package/src/ds-file-uploader/ds-file-uploader.stories.js +76 -0
  81. package/src/ds-file-uploader/ds-file-uploader.test.js +142 -0
  82. package/src/ds-header/ds-header.a11y.test.js +38 -0
  83. package/src/ds-header/ds-header.js +149 -0
  84. package/src/ds-header/ds-header.stories.js +63 -0
  85. package/src/ds-header/ds-header.test.js +52 -0
  86. package/src/ds-header/index.js +1 -0
  87. package/src/ds-header-nav/ds-header-nav.a11y.test.js +69 -0
  88. package/src/ds-header-nav/ds-header-nav.js +114 -0
  89. package/src/ds-header-nav/ds-header-nav.stories.js +17 -0
  90. package/src/ds-header-nav/ds-header-nav.test.js +93 -0
  91. package/src/ds-header-nav-item/ds-header-nav-item.a11y.test.js +71 -0
  92. package/src/ds-header-nav-item/ds-header-nav-item.js +124 -0
  93. package/src/ds-header-nav-item/ds-header-nav-item.stories.js +43 -0
  94. package/src/ds-header-nav-item/ds-header-nav-item.test.js +61 -0
  95. package/src/ds-icon/ds-icon.a11y.test.js +49 -0
  96. package/src/ds-icon/ds-icon.js +75 -0
  97. package/src/ds-icon/ds-icon.mdx +36 -0
  98. package/src/ds-icon/ds-icon.stories.js +88 -0
  99. package/src/ds-icon/ds-icon.test.js +97 -0
  100. package/src/ds-icon/index.js +1 -0
  101. package/src/ds-icon-button/ds-icon-button.a11y.test.js +55 -0
  102. package/src/ds-icon-button/ds-icon-button.js +224 -0
  103. package/src/ds-icon-button/ds-icon-button.mdx +131 -0
  104. package/src/ds-icon-button/ds-icon-button.stories.js +128 -0
  105. package/src/ds-icon-button/ds-icon-button.test.js +90 -0
  106. package/src/ds-icon-button/index.js +1 -0
  107. package/src/ds-input/ds-input.a11y.test.js +145 -0
  108. package/src/ds-input/ds-input.js +645 -0
  109. package/src/ds-input/ds-input.mdx +251 -0
  110. package/src/ds-input/ds-input.stories.js +298 -0
  111. package/src/ds-input/ds-input.test.js +792 -0
  112. package/src/ds-input/index.js +1 -0
  113. package/src/ds-link/ds-link.js +111 -0
  114. package/src/ds-link/ds-link.stories.js +56 -0
  115. package/src/ds-link/ds-link.test.js +74 -0
  116. package/src/ds-list-item/ds-list-item.a11y.test.js +39 -0
  117. package/src/ds-list-item/ds-list-item.js +292 -0
  118. package/src/ds-list-item/ds-list-item.stories.js +101 -0
  119. package/src/ds-list-item/ds-list-item.test.js +63 -0
  120. package/src/ds-menu/ds-menu.js +30 -0
  121. package/src/ds-menu/ds-menu.stories.js +120 -0
  122. package/src/ds-menu/ds-menu.test.js +123 -0
  123. package/src/ds-menu-group/ds-menu-group.js +101 -0
  124. package/src/ds-menu-group/ds-menu-group.stories.js +99 -0
  125. package/src/ds-nav-item/ds-nav-item.a11y.test.js +91 -0
  126. package/src/ds-nav-item/ds-nav-item.js +307 -0
  127. package/src/ds-nav-item/ds-nav-item.stories.js +99 -0
  128. package/src/ds-nav-item/ds-nav-item.test.js +169 -0
  129. package/src/ds-nav-item/index.js +1 -0
  130. package/src/ds-nav-vertical/ds-nav-vertical.a11y.test.js +69 -0
  131. package/src/ds-nav-vertical/ds-nav-vertical.js +173 -0
  132. package/src/ds-nav-vertical/ds-nav-vertical.stories.js +124 -0
  133. package/src/ds-nav-vertical/ds-nav-vertical.test.js +176 -0
  134. package/src/ds-nav-vertical/index.js +1 -0
  135. package/src/ds-pagination/ds-pagination.a11y.test.js +50 -0
  136. package/src/ds-pagination/ds-pagination.js +232 -0
  137. package/src/ds-pagination/ds-pagination.stories.js +63 -0
  138. package/src/ds-pagination/ds-pagination.test.js +141 -0
  139. package/src/ds-pagination/index.js +1 -0
  140. package/src/ds-progress-bar/ds-progress-bar.a11y.test.js +25 -0
  141. package/src/ds-progress-bar/ds-progress-bar.js +81 -0
  142. package/src/ds-progress-bar/ds-progress-bar.stories.js +69 -0
  143. package/src/ds-progress-bar/ds-progress-bar.test.js +60 -0
  144. package/src/ds-radio/ds-radio.a11y.test.js +69 -0
  145. package/src/ds-radio/ds-radio.js +240 -0
  146. package/src/ds-radio/ds-radio.stories.js +102 -0
  147. package/src/ds-radio/ds-radio.test.js +114 -0
  148. package/src/ds-radio/index.js +1 -0
  149. package/src/ds-radio-group/ds-radio-group.a11y.test.js +164 -0
  150. package/src/ds-radio-group/ds-radio-group.js +257 -0
  151. package/src/ds-radio-group/ds-radio-group.stories.js +247 -0
  152. package/src/ds-radio-group/ds-radio-group.test.js +194 -0
  153. package/src/ds-radio-group/index.js +1 -0
  154. package/src/ds-rich-list/ds-rich-list.js +246 -0
  155. package/src/ds-rich-list/ds-rich-list.stories.js +368 -0
  156. package/src/ds-rich-list/ds-rich-list.test.js +293 -0
  157. package/src/ds-rich-list-item/ds-rich-list-item.js +579 -0
  158. package/src/ds-rich-list-item/ds-rich-list-item.stories.js +197 -0
  159. package/src/ds-rich-list-item/ds-rich-list-item.test.js +434 -0
  160. package/src/ds-slider/ds-slider.js +399 -0
  161. package/src/ds-slider/ds-slider.stories.js +107 -0
  162. package/src/ds-slider/ds-slider.test.js +308 -0
  163. package/src/ds-spinner/ds-spinner.js +173 -0
  164. package/src/ds-spinner/ds-spinner.stories.js +52 -0
  165. package/src/ds-spinner/ds-spinner.test.js +50 -0
  166. package/src/ds-status-border/ds-status-border.js +88 -0
  167. package/src/ds-status-border/ds-status-border.stories.js +242 -0
  168. package/src/ds-status-border/ds-status-border.test.js +168 -0
  169. package/src/ds-stepper/ds-stepper.a11y.test.js +198 -0
  170. package/src/ds-stepper/ds-stepper.js +207 -0
  171. package/src/ds-stepper/ds-stepper.stories.js +530 -0
  172. package/src/ds-stepper/ds-stepper.test.js +311 -0
  173. package/src/ds-stepper-item/ds-stepper-item.js +485 -0
  174. package/src/ds-stepper-item/ds-stepper-item.stories.js +288 -0
  175. package/src/ds-switch/ds-switch.js +348 -0
  176. package/src/ds-switch/ds-switch.stories.js +145 -0
  177. package/src/ds-switch/ds-switch.test.js +226 -0
  178. package/src/ds-switch/index.js +1 -0
  179. package/src/ds-tab-item/ds-tab-item.js +341 -0
  180. package/src/ds-tab-item/ds-tab-item.stories.js +69 -0
  181. package/src/ds-tabs/ds-tab-panel.js +48 -0
  182. package/src/ds-tabs/ds-tabs.a11y.test.js +56 -0
  183. package/src/ds-tabs/ds-tabs.js +180 -0
  184. package/src/ds-tabs/ds-tabs.stories.js +152 -0
  185. package/src/ds-tabs/ds-tabs.test.js +306 -0
  186. package/src/ds-tabs/index.js +3 -0
  187. package/src/ds-tag-action/ds-tag-action.a11y.test.js +32 -0
  188. package/src/ds-tag-action/ds-tag-action.js +185 -0
  189. package/src/ds-tag-action/ds-tag-action.stories.js +55 -0
  190. package/src/ds-tag-action/ds-tag-action.test.js +44 -0
  191. package/src/ds-tag-removable/ds-tag-removable.a11y.test.js +24 -0
  192. package/src/ds-tag-removable/ds-tag-removable.js +146 -0
  193. package/src/ds-tag-removable/ds-tag-removable.stories.js +52 -0
  194. package/src/ds-tag-removable/ds-tag-removable.test.js +46 -0
  195. package/src/ds-tag-status/ds-tag-status.a11y.test.js +93 -0
  196. package/src/ds-tag-status/ds-tag-status.js +164 -0
  197. package/src/ds-tag-status/ds-tag-status.stories.js +200 -0
  198. package/src/ds-tag-status/ds-tag-status.test.js +140 -0
  199. package/src/ds-tag-status/index.js +1 -0
  200. package/src/ds-textarea/ds-textarea-clearable.test.js +89 -0
  201. package/src/ds-textarea/ds-textarea.a11y.test.js +66 -0
  202. package/src/ds-textarea/ds-textarea.js +505 -0
  203. package/src/ds-textarea/ds-textarea.stories.js +335 -0
  204. package/src/ds-textarea/ds-textarea.test.js +218 -0
  205. package/src/ds-textarea/index.js +1 -0
  206. package/src/ds-thumbnail/ds-thumbnail.js +207 -0
  207. package/src/ds-thumbnail/ds-thumbnail.stories.js +217 -0
  208. package/src/ds-thumbnail/ds-thumbnail.test.js +220 -0
  209. package/src/ds-toast/ds-toast-provider.js +110 -0
  210. package/src/ds-toast/ds-toast.a11y.test.js +34 -0
  211. package/src/ds-toast/ds-toast.js +243 -0
  212. package/src/ds-toast/ds-toast.stories.js +143 -0
  213. package/src/ds-toast/ds-toast.test.js +93 -0
  214. package/src/ds-toast/index.js +2 -0
  215. package/src/ds-tooltip/ds-tooltip.a11y.test.js +110 -0
  216. package/src/ds-tooltip/ds-tooltip.js +217 -0
  217. package/src/ds-tooltip/ds-tooltip.mdx +75 -0
  218. package/src/ds-tooltip/ds-tooltip.stories.js +72 -0
  219. package/src/ds-tooltip/ds-tooltip.test.js +191 -0
  220. package/src/ds-tooltip/index.js +1 -0
  221. package/src/ds-tooltip/positioner.js +117 -0
  222. package/src/index.js +50 -0
  223. package/src/mixins/field-label.mixin.js +113 -0
  224. package/src/mixins/field-message.mixin.js +66 -0
  225. package/src/token-provider/index.js +1 -0
  226. package/src/token-provider/token-provider.a11y.test.js +44 -0
  227. package/src/token-provider/token-provider.js +85 -0
  228. package/src/token-provider/token-provider.stories.js +105 -0
  229. package/src/token-provider/token-provider.test.js +134 -0
  230. 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
+ });