@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,232 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import '../ds-icon-button/ds-icon-button.js';
|
|
3
|
+
import '../ds-input/ds-input.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pagination component for navigating through multiple pages of content.
|
|
7
|
+
*
|
|
8
|
+
* @element ds-pagination
|
|
9
|
+
*
|
|
10
|
+
* @prop {number} total - Total number of pages (required).
|
|
11
|
+
* @prop {number} current - Current active page (default: 1).
|
|
12
|
+
* @prop {string} variant - Pagination style: 'default' | 'dot' (default: 'default').
|
|
13
|
+
*
|
|
14
|
+
* @fires ds-change - Fired when the page changes. Detail: { page: number }
|
|
15
|
+
*/
|
|
16
|
+
export class DsPagination extends LitElement {
|
|
17
|
+
static properties = {
|
|
18
|
+
total: { type: Number },
|
|
19
|
+
current: { type: Number },
|
|
20
|
+
variant: { type: String }
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
static styles = css`
|
|
24
|
+
:host {
|
|
25
|
+
display: inline-block;
|
|
26
|
+
vertical-align: middle;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Reset & Base Layout */
|
|
30
|
+
.ds-pagination {
|
|
31
|
+
display: block;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.ds-pagination__list {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
list-style: none;
|
|
38
|
+
margin: 0;
|
|
39
|
+
padding: 0;
|
|
40
|
+
gap: var(--ds-space-xs, 4px); /* Base 4px gap between most items */
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ds-pagination__item {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Input Group Specifics (Default Variant) */
|
|
49
|
+
|
|
50
|
+
/* The item containing the input group needs extra spacing */
|
|
51
|
+
.ds-pagination__item--input {
|
|
52
|
+
margin: 0 var(--ds-space-xs, 4px); /* Adds 4px extra on each side. Total gap = 4px(gap) + 4px(margin) = 8px */
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.ds-pagination__input-group {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: var(--ds-space-sm, 8px); /* Space between Input and Total Text */
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.ds-pagination__text {
|
|
62
|
+
font: var(--ds-typo-content-body-regular);
|
|
63
|
+
color: var(--ds-color-text-default);
|
|
64
|
+
white-space: nowrap;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Screen reader only - for live region */
|
|
68
|
+
.sr-only {
|
|
69
|
+
position: absolute;
|
|
70
|
+
width: 1px;
|
|
71
|
+
height: 1px;
|
|
72
|
+
padding: 0;
|
|
73
|
+
margin: -1px;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
clip: rect(0, 0, 0, 0);
|
|
76
|
+
white-space: nowrap;
|
|
77
|
+
border: 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Dot Variant Specifics */
|
|
81
|
+
.variant-dot .ds-pagination__list {
|
|
82
|
+
gap: var(--ds-space-xs, 4px);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.variant-dot ds-icon-button::part(button) {
|
|
86
|
+
color: var(--ds-color-icon-secondary);
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
constructor() {
|
|
91
|
+
super();
|
|
92
|
+
this.total = 1;
|
|
93
|
+
this.current = 1;
|
|
94
|
+
this.variant = 'default';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_dispatchChange(newPage) {
|
|
98
|
+
if (newPage >= 1 && newPage <= this.total && newPage !== this.current) {
|
|
99
|
+
this.current = newPage;
|
|
100
|
+
this.dispatchEvent(new CustomEvent('ds-change', {
|
|
101
|
+
detail: { page: this.current },
|
|
102
|
+
bubbles: true,
|
|
103
|
+
composed: true
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_handleFirst() { this._dispatchChange(1); }
|
|
109
|
+
_handlePrev() { this._dispatchChange(this.current - 1); }
|
|
110
|
+
_handleNext() { this._dispatchChange(this.current + 1); }
|
|
111
|
+
_handleLast() { this._dispatchChange(this.total); }
|
|
112
|
+
|
|
113
|
+
_handleInputFocus(e) {
|
|
114
|
+
// Use new public API instead of shadow DOM access
|
|
115
|
+
e.target.selectAll();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_handleInputChange(e) {
|
|
119
|
+
// ds-input now handles clamping via min/max/clamp props
|
|
120
|
+
// We just need to dispatch the change if value differs
|
|
121
|
+
const val = parseInt(e.target.value, 10);
|
|
122
|
+
|
|
123
|
+
if (!isNaN(val) && val !== this.current) {
|
|
124
|
+
this._dispatchChange(val);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
renderDefault() {
|
|
129
|
+
return html`
|
|
130
|
+
<nav class="ds-pagination" aria-label="Pagination">
|
|
131
|
+
<ul class="ds-pagination__list">
|
|
132
|
+
<li class="ds-pagination__item">
|
|
133
|
+
<ds-icon-button
|
|
134
|
+
icon="first-page"
|
|
135
|
+
variant="action"
|
|
136
|
+
size="m"
|
|
137
|
+
?disabled=${this.current === 1}
|
|
138
|
+
aria-label="First page"
|
|
139
|
+
@click=${this._handleFirst}
|
|
140
|
+
></ds-icon-button>
|
|
141
|
+
</li>
|
|
142
|
+
<li class="ds-pagination__item">
|
|
143
|
+
<ds-icon-button
|
|
144
|
+
icon="chevron-left"
|
|
145
|
+
variant="action"
|
|
146
|
+
size="m"
|
|
147
|
+
?disabled=${this.current === 1}
|
|
148
|
+
aria-label="Previous page"
|
|
149
|
+
@click=${this._handlePrev}
|
|
150
|
+
></ds-icon-button>
|
|
151
|
+
</li>
|
|
152
|
+
|
|
153
|
+
<li class="ds-pagination__item ds-pagination__item--input">
|
|
154
|
+
<div class="ds-pagination__input-group">
|
|
155
|
+
<ds-input
|
|
156
|
+
class="page-input"
|
|
157
|
+
type="number"
|
|
158
|
+
width="60px"
|
|
159
|
+
text-align="center"
|
|
160
|
+
hide-steppers
|
|
161
|
+
autocomplete="off"
|
|
162
|
+
min="1"
|
|
163
|
+
.max=${this.total}
|
|
164
|
+
clamp
|
|
165
|
+
.value=${this.current.toString()}
|
|
166
|
+
aria-label="Current page"
|
|
167
|
+
@change=${this._handleInputChange}
|
|
168
|
+
@focus=${this._handleInputFocus}
|
|
169
|
+
></ds-input>
|
|
170
|
+
<span class="ds-pagination__text">/ ${this.total}</span>
|
|
171
|
+
</div>
|
|
172
|
+
</li>
|
|
173
|
+
|
|
174
|
+
<li class="ds-pagination__item">
|
|
175
|
+
<ds-icon-button
|
|
176
|
+
icon="chevron-right"
|
|
177
|
+
variant="action"
|
|
178
|
+
size="m"
|
|
179
|
+
?disabled=${this.current === this.total}
|
|
180
|
+
aria-label="Next page"
|
|
181
|
+
@click=${this._handleNext}
|
|
182
|
+
></ds-icon-button>
|
|
183
|
+
</li>
|
|
184
|
+
<li class="ds-pagination__item">
|
|
185
|
+
<ds-icon-button
|
|
186
|
+
icon="last-page"
|
|
187
|
+
variant="action"
|
|
188
|
+
size="m"
|
|
189
|
+
?disabled=${this.current === this.total}
|
|
190
|
+
aria-label="Last page"
|
|
191
|
+
@click=${this._handleLast}
|
|
192
|
+
></ds-icon-button>
|
|
193
|
+
</li>
|
|
194
|
+
</ul>
|
|
195
|
+
|
|
196
|
+
<!-- Live region for accessibility -->
|
|
197
|
+
<span class="sr-only" role="status" aria-live="polite">
|
|
198
|
+
Page ${this.current} of ${this.total}
|
|
199
|
+
</span>
|
|
200
|
+
</nav>
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
renderDot() {
|
|
205
|
+
const pages = Array.from({ length: this.total }, (_, i) => i + 1);
|
|
206
|
+
|
|
207
|
+
return html`
|
|
208
|
+
<nav class="ds-pagination variant-dot" aria-label="Pagination">
|
|
209
|
+
<ul class="ds-pagination__list">
|
|
210
|
+
${pages.map(page => html`
|
|
211
|
+
<li class="ds-pagination__item">
|
|
212
|
+
<ds-icon-button
|
|
213
|
+
icon=${page === this.current ? 'circle-filled' : 'circle'}
|
|
214
|
+
variant="action"
|
|
215
|
+
size="m"
|
|
216
|
+
aria-label="Page ${page}"
|
|
217
|
+
aria-current=${page === this.current ? 'page' : 'false'}
|
|
218
|
+
@click=${() => this._dispatchChange(page)}
|
|
219
|
+
></ds-icon-button>
|
|
220
|
+
</li>
|
|
221
|
+
`)}
|
|
222
|
+
</ul>
|
|
223
|
+
</nav>
|
|
224
|
+
`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
render() {
|
|
228
|
+
return this.variant === 'dot' ? this.renderDot() : this.renderDefault();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
customElements.define('ds-pagination', DsPagination);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import './ds-pagination.js';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Components/Pagination',
|
|
6
|
+
component: 'ds-pagination',
|
|
7
|
+
parameters: {
|
|
8
|
+
actions: {
|
|
9
|
+
handles: ['ds-change'],
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
argTypes: {
|
|
13
|
+
total: { control: 'number' },
|
|
14
|
+
current: { control: 'number' },
|
|
15
|
+
variant: {
|
|
16
|
+
control: { type: 'select' },
|
|
17
|
+
options: ['default', 'dot'],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const Template = (args) => html`
|
|
23
|
+
<ds-pagination
|
|
24
|
+
.total=${args.total}
|
|
25
|
+
.current=${args.current}
|
|
26
|
+
.variant=${args.variant}
|
|
27
|
+
></ds-pagination>
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
export const Default = Template.bind({});
|
|
31
|
+
Default.args = {
|
|
32
|
+
total: 10,
|
|
33
|
+
current: 1,
|
|
34
|
+
variant: 'default',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const MiddlePage = Template.bind({});
|
|
38
|
+
MiddlePage.args = {
|
|
39
|
+
total: 10,
|
|
40
|
+
current: 5,
|
|
41
|
+
variant: 'default',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const LastPage = Template.bind({});
|
|
45
|
+
LastPage.args = {
|
|
46
|
+
total: 10,
|
|
47
|
+
current: 10,
|
|
48
|
+
variant: 'default',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const DotVariant = Template.bind({});
|
|
52
|
+
DotVariant.args = {
|
|
53
|
+
total: 5,
|
|
54
|
+
current: 1,
|
|
55
|
+
variant: 'dot',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const DotVariantMax = Template.bind({});
|
|
59
|
+
DotVariantMax.args = {
|
|
60
|
+
total: 6,
|
|
61
|
+
current: 3,
|
|
62
|
+
variant: 'dot',
|
|
63
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import './ds-pagination.js';
|
|
3
|
+
|
|
4
|
+
describe('ds-pagination', () => {
|
|
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('should render default variant with correct initial state', async () => {
|
|
17
|
+
container.innerHTML = '<ds-pagination total="10"></ds-pagination>';
|
|
18
|
+
const el = container.querySelector('ds-pagination');
|
|
19
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
20
|
+
|
|
21
|
+
const nav = el.shadowRoot.querySelector('nav.ds-pagination');
|
|
22
|
+
expect(nav).to.exist;
|
|
23
|
+
|
|
24
|
+
const list = el.shadowRoot.querySelector('ul.ds-pagination__list');
|
|
25
|
+
expect(list).to.exist;
|
|
26
|
+
|
|
27
|
+
const buttons = el.shadowRoot.querySelectorAll('ds-icon-button');
|
|
28
|
+
expect(buttons.length).toBe(4); // First, Prev, Next, Last
|
|
29
|
+
|
|
30
|
+
expect(buttons[0].hasAttribute('disabled')).toBe(true);
|
|
31
|
+
expect(buttons[1].hasAttribute('disabled')).toBe(true);
|
|
32
|
+
expect(buttons[2].hasAttribute('disabled')).toBe(false);
|
|
33
|
+
expect(buttons[3].hasAttribute('disabled')).toBe(false);
|
|
34
|
+
|
|
35
|
+
const input = el.shadowRoot.querySelector('ds-input');
|
|
36
|
+
expect(input.getAttribute('type')).toBe('number');
|
|
37
|
+
expect(input.hasAttribute('hide-steppers')).toBe(true);
|
|
38
|
+
expect(input.value).toBe('1');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should handle navigation via buttons', async () => {
|
|
42
|
+
container.innerHTML = '<ds-pagination total="10" current="5"></ds-pagination>';
|
|
43
|
+
const el = container.querySelector('ds-pagination');
|
|
44
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
45
|
+
|
|
46
|
+
const buttons = el.shadowRoot.querySelectorAll('ds-icon-button');
|
|
47
|
+
|
|
48
|
+
// Next button (index 2)
|
|
49
|
+
buttons[2].click();
|
|
50
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
51
|
+
expect(el.current).toBe(6);
|
|
52
|
+
|
|
53
|
+
// Prev button (index 1)
|
|
54
|
+
buttons[1].click();
|
|
55
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
56
|
+
expect(el.current).toBe(5);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should handle input change (valid number)', async () => {
|
|
60
|
+
container.innerHTML = '<ds-pagination total="10"></ds-pagination>';
|
|
61
|
+
const el = container.querySelector('ds-pagination');
|
|
62
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
63
|
+
|
|
64
|
+
const input = el.shadowRoot.querySelector('ds-input');
|
|
65
|
+
input.value = '5';
|
|
66
|
+
input.dispatchEvent(new CustomEvent('change', { detail: { value: '5' } }));
|
|
67
|
+
|
|
68
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
69
|
+
expect(el.current).toBe(5);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should clamp input values via ds-input clamp prop', async () => {
|
|
73
|
+
container.innerHTML = '<ds-pagination total="10" current="5"></ds-pagination>';
|
|
74
|
+
const el = container.querySelector('ds-pagination');
|
|
75
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
76
|
+
|
|
77
|
+
const input = el.shadowRoot.querySelector('ds-input');
|
|
78
|
+
const nativeInput = input.shadowRoot.querySelector('input');
|
|
79
|
+
|
|
80
|
+
// Assert autocomplete off
|
|
81
|
+
expect(input.getAttribute('autocomplete')).toBe('off');
|
|
82
|
+
|
|
83
|
+
// Assert width and clamp props are set
|
|
84
|
+
expect(input.getAttribute('width')).toBe('60px');
|
|
85
|
+
expect(input.hasAttribute('clamp')).toBe(true);
|
|
86
|
+
expect(input.getAttribute('min')).toBe('1');
|
|
87
|
+
|
|
88
|
+
// Test max clamp (ds-input clamps on blur)
|
|
89
|
+
nativeInput.value = '15';
|
|
90
|
+
input.value = '15';
|
|
91
|
+
nativeInput.dispatchEvent(new Event('blur'));
|
|
92
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
93
|
+
|
|
94
|
+
// ds-input should have clamped the value to max (10)
|
|
95
|
+
expect(input.value).toBe('10');
|
|
96
|
+
|
|
97
|
+
// Test min clamp
|
|
98
|
+
nativeInput.value = '0';
|
|
99
|
+
input.value = '0';
|
|
100
|
+
nativeInput.dispatchEvent(new Event('blur'));
|
|
101
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
102
|
+
|
|
103
|
+
expect(input.value).toBe('1');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should clamp on Enter key via ds-input', async () => {
|
|
107
|
+
container.innerHTML = '<ds-pagination total="10"></ds-pagination>';
|
|
108
|
+
const el = container.querySelector('ds-pagination');
|
|
109
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
110
|
+
|
|
111
|
+
const input = el.shadowRoot.querySelector('ds-input');
|
|
112
|
+
const nativeInput = input.shadowRoot.querySelector('input');
|
|
113
|
+
|
|
114
|
+
// Type an out-of-range value
|
|
115
|
+
nativeInput.value = '99';
|
|
116
|
+
input.value = '99';
|
|
117
|
+
|
|
118
|
+
// Press Enter - ds-input should clamp
|
|
119
|
+
nativeInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
120
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
121
|
+
|
|
122
|
+
// Input should be clamped to max
|
|
123
|
+
expect(input.value).toBe('10');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should render dot variant', async () => {
|
|
127
|
+
container.innerHTML = '<ds-pagination variant="dot" total="5"></ds-pagination>';
|
|
128
|
+
const el = container.querySelector('ds-pagination');
|
|
129
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
130
|
+
|
|
131
|
+
const nav = el.shadowRoot.querySelector('nav.variant-dot');
|
|
132
|
+
expect(nav).to.exist;
|
|
133
|
+
|
|
134
|
+
const dots = el.shadowRoot.querySelectorAll('ds-icon-button');
|
|
135
|
+
expect(dots.length).toBe(5);
|
|
136
|
+
|
|
137
|
+
// Default current is 1 (index 0)
|
|
138
|
+
expect(dots[0].getAttribute('icon')).toBe('circle-filled');
|
|
139
|
+
expect(dots[1].getAttribute('icon')).toBe('circle');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DsPagination } from './ds-pagination.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import axe from 'axe-core';
|
|
3
|
+
import './ds-progress-bar.js';
|
|
4
|
+
|
|
5
|
+
describe('ds-progress-bar 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('passes accessibility checks', async () => {
|
|
18
|
+
container.innerHTML = '<ds-progress-bar value="50" label="Loading content"></ds-progress-bar>';
|
|
19
|
+
const el = container.querySelector('ds-progress-bar');
|
|
20
|
+
await new Promise(r => setTimeout(r, 50));
|
|
21
|
+
|
|
22
|
+
const results = await axe.run(el);
|
|
23
|
+
expect(results.violations.length).toBe(0);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @element ds-progress-bar
|
|
5
|
+
* @summary A linear progress bar component.
|
|
6
|
+
*
|
|
7
|
+
* @prop {number} value - Current progress value. Default: 0.
|
|
8
|
+
* @prop {number} max - Maximum progress value. Default: 100.
|
|
9
|
+
* @prop {string} status - 'default' | 'error' | 'finished'. Default: 'default'.
|
|
10
|
+
* @prop {string} label - Accessible label.
|
|
11
|
+
*/
|
|
12
|
+
export class DsProgressBar extends LitElement {
|
|
13
|
+
static properties = {
|
|
14
|
+
value: { type: Number },
|
|
15
|
+
max: { type: Number },
|
|
16
|
+
status: { type: String, reflect: true },
|
|
17
|
+
label: { type: String },
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
static styles = css`
|
|
21
|
+
:host {
|
|
22
|
+
display: block;
|
|
23
|
+
width: 100%;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.progress-track {
|
|
27
|
+
background-color: var(--ds-color-border-default);
|
|
28
|
+
height: 4px;
|
|
29
|
+
border-radius: var(--ds-radius-container, 0px); /* Sharp / Container radius */
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
width: 100%;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.progress-indicator {
|
|
35
|
+
height: 100%;
|
|
36
|
+
background-color: var(--ds-color-border-brand);
|
|
37
|
+
transition: width 0.2s ease-in-out;
|
|
38
|
+
width: 0%;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* ERROR STATUS */
|
|
42
|
+
:host([status="error"]) .progress-indicator {
|
|
43
|
+
background-color: var(--ds-color-border-error);
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
constructor() {
|
|
48
|
+
super();
|
|
49
|
+
this.value = 0;
|
|
50
|
+
this.max = 100;
|
|
51
|
+
this.status = 'default';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
render() {
|
|
55
|
+
const max = (this.max && this.max > 0) ? this.max : 100;
|
|
56
|
+
const value = this.value || 0;
|
|
57
|
+
|
|
58
|
+
let percentage = Math.min(100, Math.max(0, (value / max) * 100));
|
|
59
|
+
|
|
60
|
+
if (this.status === 'finished') {
|
|
61
|
+
percentage = 100;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return html`
|
|
65
|
+
<div
|
|
66
|
+
role="progressbar"
|
|
67
|
+
class="progress-track"
|
|
68
|
+
aria-valuenow="${this.status === 'finished' ? max : value}"
|
|
69
|
+
aria-valuemin="0"
|
|
70
|
+
aria-valuemax="${max}"
|
|
71
|
+
aria-label="${this.label || ''}"
|
|
72
|
+
>
|
|
73
|
+
<div class="progress-indicator" style="width: ${percentage}%;"></div>
|
|
74
|
+
</div>
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!customElements.get('ds-progress-bar')) {
|
|
80
|
+
customElements.define('ds-progress-bar', DsProgressBar);
|
|
81
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
3
|
+
import './ds-progress-bar.js';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Components/Progress Bar',
|
|
7
|
+
component: 'ds-progress-bar',
|
|
8
|
+
argTypes: {
|
|
9
|
+
value: { control: 'number' },
|
|
10
|
+
max: { control: 'number' },
|
|
11
|
+
status: {
|
|
12
|
+
control: 'select',
|
|
13
|
+
options: ['default', 'error', 'finished']
|
|
14
|
+
},
|
|
15
|
+
label: { control: 'text' }
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const Template = (args) => html`
|
|
20
|
+
<div style="width: 300px;">
|
|
21
|
+
<ds-progress-bar
|
|
22
|
+
.value=${args.value}
|
|
23
|
+
.max=${args.max}
|
|
24
|
+
.status=${ifDefined(args.status)}
|
|
25
|
+
label=${ifDefined(args.label)}
|
|
26
|
+
></ds-progress-bar>
|
|
27
|
+
</div>
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
export const Default = Template.bind({});
|
|
31
|
+
Default.args = {
|
|
32
|
+
value: 50,
|
|
33
|
+
max: 100,
|
|
34
|
+
status: 'default',
|
|
35
|
+
label: 'Loading...',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Full = Template.bind({});
|
|
39
|
+
Full.args = {
|
|
40
|
+
value: 100,
|
|
41
|
+
max: 100,
|
|
42
|
+
status: 'default',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Empty = Template.bind({});
|
|
46
|
+
Empty.args = {
|
|
47
|
+
value: 0,
|
|
48
|
+
max: 100,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const ErrorState = Template.bind({});
|
|
52
|
+
ErrorState.args = {
|
|
53
|
+
value: 75,
|
|
54
|
+
max: 100,
|
|
55
|
+
status: 'error',
|
|
56
|
+
label: 'Upload Failed',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const LargeScale = Template.bind({});
|
|
60
|
+
LargeScale.args = {
|
|
61
|
+
value: 450,
|
|
62
|
+
max: 1000,
|
|
63
|
+
status: 'default',
|
|
64
|
+
label: 'Processing Items',
|
|
65
|
+
};
|
|
66
|
+
LargeScale.argTypes = {
|
|
67
|
+
value: { control: { type: 'range', min: 0, max: 1000, step: 10 } },
|
|
68
|
+
max: { control: { type: 'number' } }
|
|
69
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import './ds-progress-bar.js';
|
|
3
|
+
|
|
4
|
+
describe('ds-progress-bar', () => {
|
|
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 values', async () => {
|
|
17
|
+
container.innerHTML = '<ds-progress-bar></ds-progress-bar>';
|
|
18
|
+
const el = container.querySelector('ds-progress-bar');
|
|
19
|
+
await new Promise(r => setTimeout(r, 50));
|
|
20
|
+
|
|
21
|
+
const track = el.shadowRoot.querySelector('.progress-track');
|
|
22
|
+
expect(track).toBeTruthy();
|
|
23
|
+
expect(track.getAttribute('role')).toBe('progressbar');
|
|
24
|
+
expect(track.getAttribute('aria-valuenow')).toBe('0');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('reflects value updates in width', async () => {
|
|
28
|
+
container.innerHTML = '<ds-progress-bar value="50" max="100"></ds-progress-bar>';
|
|
29
|
+
const el = container.querySelector('ds-progress-bar');
|
|
30
|
+
await new Promise(r => setTimeout(r, 50));
|
|
31
|
+
|
|
32
|
+
const indicator = el.shadowRoot.querySelector('.progress-indicator');
|
|
33
|
+
expect(indicator.style.width).toBe('50%');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('calculates percentage correctly with custom max', async () => {
|
|
37
|
+
container.innerHTML = '<ds-progress-bar value="50" max="200"></ds-progress-bar>';
|
|
38
|
+
const el = container.querySelector('ds-progress-bar');
|
|
39
|
+
await new Promise(r => setTimeout(r, 50));
|
|
40
|
+
|
|
41
|
+
const indicator = el.shadowRoot.querySelector('.progress-indicator');
|
|
42
|
+
// 50 / 200 = 25%
|
|
43
|
+
expect(indicator.style.width).toBe('25%');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('applies error styling', async () => {
|
|
47
|
+
container.innerHTML = '<ds-progress-bar status="error"></ds-progress-bar>';
|
|
48
|
+
const el = container.querySelector('ds-progress-bar');
|
|
49
|
+
await new Promise(r => setTimeout(r, 50));
|
|
50
|
+
|
|
51
|
+
// We check if the attribute is reflected (Lit reflect: true)
|
|
52
|
+
expect(el.getAttribute('status')).toBe('error');
|
|
53
|
+
|
|
54
|
+
// We can also check computed style if needed, but checking the attribute reflection is usually enough for the test if the CSS relies on :host([status="error"])
|
|
55
|
+
const indicator = el.shadowRoot.querySelector('.progress-indicator');
|
|
56
|
+
const style = getComputedStyle(indicator);
|
|
57
|
+
// Since we are not in a full browser environment with variables loaded, checking the exact color might be tricky unless mocked.
|
|
58
|
+
// Verification of logic is sufficient here.
|
|
59
|
+
});
|
|
60
|
+
});
|