@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,83 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import './ds-avatar.js';
|
|
3
|
+
|
|
4
|
+
describe('ds-avatar', () => {
|
|
5
|
+
let container;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
container = document.createElement('div');
|
|
9
|
+
document.body.appendChild(container);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
container.remove();
|
|
14
|
+
vi.restoreAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should render default icon variant', async () => {
|
|
18
|
+
container.innerHTML = '<ds-avatar></ds-avatar>';
|
|
19
|
+
const el = container.querySelector('ds-avatar');
|
|
20
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
21
|
+
|
|
22
|
+
const icon = el.shadowRoot.querySelector('ds-icon');
|
|
23
|
+
expect(icon).toBeTruthy();
|
|
24
|
+
expect(icon.getAttribute('name')).toBe('account_circle');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should render image when src is provided', async () => {
|
|
28
|
+
container.innerHTML = '<ds-avatar src="image.jpg" alt="User Image"></ds-avatar>';
|
|
29
|
+
const el = container.querySelector('ds-avatar');
|
|
30
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
31
|
+
|
|
32
|
+
const img = el.shadowRoot.querySelector('img');
|
|
33
|
+
expect(img).toBeTruthy();
|
|
34
|
+
expect(img.getAttribute('src')).toBe('image.jpg');
|
|
35
|
+
expect(img.getAttribute('alt')).toBe('User Image');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should render initials when src is missing but initials are provided', async () => {
|
|
39
|
+
container.innerHTML = '<ds-avatar initials="MS"></ds-avatar>';
|
|
40
|
+
const el = container.querySelector('ds-avatar');
|
|
41
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
42
|
+
|
|
43
|
+
const initials = el.shadowRoot.querySelector('.avatar__initials');
|
|
44
|
+
expect(initials).toBeTruthy();
|
|
45
|
+
expect(initials.textContent).toBe('MS');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should render custom icon when no src/initials', async () => {
|
|
49
|
+
container.innerHTML = '<ds-avatar icon="star"></ds-avatar>';
|
|
50
|
+
const el = container.querySelector('ds-avatar');
|
|
51
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
52
|
+
|
|
53
|
+
const icon = el.shadowRoot.querySelector('ds-icon');
|
|
54
|
+
expect(icon.getAttribute('name')).toBe('star');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should apply size attribute', async () => {
|
|
58
|
+
container.innerHTML = '<ds-avatar size="sm"></ds-avatar>';
|
|
59
|
+
const el = container.querySelector('ds-avatar');
|
|
60
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
61
|
+
|
|
62
|
+
expect(el.size).toBe('sm');
|
|
63
|
+
expect(el.getAttribute('size')).toBe('sm');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should handle clickable state and emit click event', async () => {
|
|
67
|
+
const clickSpy = vi.fn();
|
|
68
|
+
container.innerHTML = '<ds-avatar clickable></ds-avatar>';
|
|
69
|
+
const el = container.querySelector('ds-avatar');
|
|
70
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
71
|
+
|
|
72
|
+
el.addEventListener('click', clickSpy);
|
|
73
|
+
|
|
74
|
+
// Setup internal click if needed, but since it's on host, we can simulate click on host
|
|
75
|
+
el.click();
|
|
76
|
+
expect(clickSpy).toHaveBeenCalled();
|
|
77
|
+
|
|
78
|
+
// Keyboard interaction
|
|
79
|
+
const event = new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, composed: true });
|
|
80
|
+
el.dispatchEvent(event);
|
|
81
|
+
expect(clickSpy).toHaveBeenCalledTimes(2);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DsAvatar } from './ds-avatar.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import axe from 'axe-core';
|
|
3
|
+
import './ds-avatar-extended.js';
|
|
4
|
+
|
|
5
|
+
describe('ds-avatar-extended 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 be accessible', async () => {
|
|
18
|
+
container.innerHTML = `
|
|
19
|
+
<ds-avatar-extended
|
|
20
|
+
name="Miguel Silva"
|
|
21
|
+
additional-text="Developer"
|
|
22
|
+
initials="MS">
|
|
23
|
+
</ds-avatar-extended>`;
|
|
24
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
25
|
+
|
|
26
|
+
const results = await axe.run(container);
|
|
27
|
+
expect(results.violations).toHaveLength(0);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import '../ds-avatar/ds-avatar.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extended Avatar component with name and additional text.
|
|
6
|
+
*
|
|
7
|
+
* @element ds-avatar-extended
|
|
8
|
+
*
|
|
9
|
+
* @prop {string} size - Avatar size: 'sm' | 'md' | 'lg' (default: 'md')
|
|
10
|
+
* @prop {string} src - Avatar image source
|
|
11
|
+
* @prop {string} alt - Avatar alt text
|
|
12
|
+
* @prop {string} initials - Avatar initials (overrides auto-generated)
|
|
13
|
+
* @prop {string} icon - Avatar icon
|
|
14
|
+
* @prop {string} backgroundColor - Avatar custom background color
|
|
15
|
+
* @prop {boolean} clickable - Whether the avatar is clickable
|
|
16
|
+
*
|
|
17
|
+
* @prop {string} name - Primary text (Body Bold - updated to Caption Bold)
|
|
18
|
+
* @prop {string} additionalText - Body Regular text (updated to Caption Regular)
|
|
19
|
+
* @prop {string} textPosition - 'left' | 'right' (default: 'right')
|
|
20
|
+
*/
|
|
21
|
+
export class DsAvatarExtended extends LitElement {
|
|
22
|
+
static properties = {
|
|
23
|
+
// Avatar Props (Pass-through)
|
|
24
|
+
size: { type: String },
|
|
25
|
+
src: { type: String },
|
|
26
|
+
alt: { type: String },
|
|
27
|
+
initials: { type: String },
|
|
28
|
+
icon: { type: String },
|
|
29
|
+
backgroundColor: { type: String, attribute: 'background-color' },
|
|
30
|
+
clickable: { type: Boolean },
|
|
31
|
+
|
|
32
|
+
// Extended Props
|
|
33
|
+
name: { type: String },
|
|
34
|
+
additionalText: { type: String, attribute: 'additional-text' },
|
|
35
|
+
textPosition: { type: String, attribute: 'text-position' }
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
static styles = css`
|
|
39
|
+
:host {
|
|
40
|
+
display: inline-flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: var(--ds-space-sm);
|
|
43
|
+
color: var(--ds-color-text-default);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
:host([text-position="left"]) {
|
|
47
|
+
flex-direction: row-reverse;
|
|
48
|
+
text-align: right;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.info {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.name {
|
|
58
|
+
font: var(--ds-typo-content-caption-bold);
|
|
59
|
+
color: var(--ds-color-text-default);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.additional-text {
|
|
63
|
+
font: var(--ds-typo-content-caption-regular);
|
|
64
|
+
color: var(--ds-color-text-default);
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
constructor() {
|
|
69
|
+
super();
|
|
70
|
+
this.size = 'md';
|
|
71
|
+
this.textPosition = 'right';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_getInitials(name) {
|
|
75
|
+
if (!name) return '';
|
|
76
|
+
const parts = name.trim().split(/\s+/);
|
|
77
|
+
if (parts.length === 1) {
|
|
78
|
+
return parts[0].substring(0, 2).toUpperCase();
|
|
79
|
+
}
|
|
80
|
+
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
render() {
|
|
84
|
+
// Determine initials: explicit prop > auto-generated from name
|
|
85
|
+
const displayInitials = this.initials || this._getInitials(this.name);
|
|
86
|
+
|
|
87
|
+
return html`
|
|
88
|
+
<ds-avatar
|
|
89
|
+
.size=${this.size}
|
|
90
|
+
.src=${this.src}
|
|
91
|
+
.alt=${this.alt}
|
|
92
|
+
.initials=${displayInitials}
|
|
93
|
+
.icon=${this.icon}
|
|
94
|
+
.backgroundColor=${this.backgroundColor}
|
|
95
|
+
.clickable=${this.clickable}
|
|
96
|
+
></ds-avatar>
|
|
97
|
+
|
|
98
|
+
<div class="info">
|
|
99
|
+
<span class="name">${this.name}</span>
|
|
100
|
+
${this.additionalText
|
|
101
|
+
? html`<span class="additional-text">${this.additionalText}</span>`
|
|
102
|
+
: ''}
|
|
103
|
+
</div>
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
customElements.define('ds-avatar-extended', DsAvatarExtended);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import './ds-avatar-extended.js';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Components/Avatar Extended',
|
|
5
|
+
component: 'ds-avatar-extended',
|
|
6
|
+
argTypes: {
|
|
7
|
+
size: {
|
|
8
|
+
control: 'select',
|
|
9
|
+
options: ['sm', 'md', 'lg'],
|
|
10
|
+
},
|
|
11
|
+
name: { control: 'text' },
|
|
12
|
+
additionalText: { control: 'text' },
|
|
13
|
+
textPosition: {
|
|
14
|
+
control: 'select',
|
|
15
|
+
options: ['left', 'right']
|
|
16
|
+
},
|
|
17
|
+
src: { control: 'text' },
|
|
18
|
+
initials: { control: 'text' },
|
|
19
|
+
icon: { control: 'text' },
|
|
20
|
+
clickable: { control: 'boolean' }
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const createExtended = ({ size, name, additionalText, textPosition, src, initials, icon, clickable }) => {
|
|
25
|
+
const el = document.createElement('ds-avatar-extended');
|
|
26
|
+
|
|
27
|
+
if (size) el.setAttribute('size', size);
|
|
28
|
+
if (name) el.setAttribute('name', name);
|
|
29
|
+
if (additionalText) el.setAttribute('additional-text', additionalText);
|
|
30
|
+
if (textPosition) el.setAttribute('text-position', textPosition);
|
|
31
|
+
|
|
32
|
+
if (src) el.setAttribute('src', src);
|
|
33
|
+
if (initials) el.setAttribute('initials', initials);
|
|
34
|
+
if (icon) el.setAttribute('icon', icon);
|
|
35
|
+
if (clickable) el.setAttribute('clickable', '');
|
|
36
|
+
|
|
37
|
+
return el;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const Default = {
|
|
41
|
+
args: {
|
|
42
|
+
name: 'Miguel Silva',
|
|
43
|
+
additionalText: 'Software Engineer',
|
|
44
|
+
src: '/avatar-default.png',
|
|
45
|
+
size: 'md'
|
|
46
|
+
},
|
|
47
|
+
render: createExtended
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const AutoInitials = {
|
|
51
|
+
args: {
|
|
52
|
+
name: 'John Doe',
|
|
53
|
+
additionalText: 'Product Manager',
|
|
54
|
+
size: 'md'
|
|
55
|
+
},
|
|
56
|
+
render: createExtended
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const TextLeft = {
|
|
60
|
+
args: {
|
|
61
|
+
name: 'Jane Smith',
|
|
62
|
+
additionalText: 'Designer',
|
|
63
|
+
src: '/avatar-default.png',
|
|
64
|
+
textPosition: 'left'
|
|
65
|
+
},
|
|
66
|
+
render: createExtended
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const NameOnly = {
|
|
70
|
+
args: {
|
|
71
|
+
name: 'Guest User',
|
|
72
|
+
icon: 'account_circle',
|
|
73
|
+
},
|
|
74
|
+
render: createExtended
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const Sizes = {
|
|
78
|
+
render: () => {
|
|
79
|
+
const container = document.createElement('div');
|
|
80
|
+
container.style.cssText = 'display: flex; flex-direction: column; gap: 20px;';
|
|
81
|
+
|
|
82
|
+
['sm', 'md', 'lg'].forEach(size => {
|
|
83
|
+
container.appendChild(createExtended({
|
|
84
|
+
size,
|
|
85
|
+
name: `Size ${size}`,
|
|
86
|
+
additionalText: 'Description',
|
|
87
|
+
// Auto initials will work here
|
|
88
|
+
}));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return container;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import './ds-avatar-extended.js';
|
|
3
|
+
|
|
4
|
+
describe('ds-avatar-extended', () => {
|
|
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 avatar and text', async () => {
|
|
17
|
+
container.innerHTML = `
|
|
18
|
+
<ds-avatar-extended
|
|
19
|
+
name="Miguel Silva"
|
|
20
|
+
additional-text="Developer"
|
|
21
|
+
initials="MS">
|
|
22
|
+
</ds-avatar-extended>`;
|
|
23
|
+
const el = container.querySelector('ds-avatar-extended');
|
|
24
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
25
|
+
|
|
26
|
+
const avatar = el.shadowRoot.querySelector('ds-avatar');
|
|
27
|
+
expect(avatar).toBeTruthy();
|
|
28
|
+
expect(avatar.initials).toBe('MS');
|
|
29
|
+
|
|
30
|
+
const name = el.shadowRoot.querySelector('.name');
|
|
31
|
+
expect(name.textContent).toBe('Miguel Silva');
|
|
32
|
+
|
|
33
|
+
const additional = el.shadowRoot.querySelector('.additional-text');
|
|
34
|
+
expect(additional.textContent).toBe('Developer');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should auto-generate initials from name', async () => {
|
|
38
|
+
container.innerHTML = '<ds-avatar-extended name="Jane Doe"></ds-avatar-extended>';
|
|
39
|
+
const el = container.querySelector('ds-avatar-extended');
|
|
40
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
41
|
+
|
|
42
|
+
const avatar = el.shadowRoot.querySelector('ds-avatar');
|
|
43
|
+
expect(avatar.initials).toBe('JD');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should handle text position left', async () => {
|
|
47
|
+
container.innerHTML = `
|
|
48
|
+
<ds-avatar-extended
|
|
49
|
+
name="Miguel Silva"
|
|
50
|
+
text-position="left">
|
|
51
|
+
</ds-avatar-extended>`;
|
|
52
|
+
const el = container.querySelector('ds-avatar-extended');
|
|
53
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
54
|
+
|
|
55
|
+
expect(el.getAttribute('text-position')).toBe('left');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should pass clickable prop to avatar', async () => {
|
|
59
|
+
container.innerHTML = '<ds-avatar-extended clickable></ds-avatar-extended>';
|
|
60
|
+
const el = container.querySelector('ds-avatar-extended');
|
|
61
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
62
|
+
|
|
63
|
+
const avatar = el.shadowRoot.querySelector('ds-avatar');
|
|
64
|
+
expect(avatar.clickable).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DsAvatarExtended } from './ds-avatar-extended.js';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import axe from 'axe-core';
|
|
3
|
+
import './ds-banner.js';
|
|
4
|
+
|
|
5
|
+
describe('ds-banner 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 with default settings', async () => {
|
|
18
|
+
container.innerHTML = '<ds-banner message="Accessible banner message"></ds-banner>';
|
|
19
|
+
const element = container.querySelector('ds-banner');
|
|
20
|
+
await element.updateComplete;
|
|
21
|
+
|
|
22
|
+
const results = await axe.run(element);
|
|
23
|
+
expect(results.violations.length).toBe(0);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should pass axe checks with different statuses', async () => {
|
|
27
|
+
const statuses = ['error', 'warning', 'success', 'info'];
|
|
28
|
+
|
|
29
|
+
for (const status of statuses) {
|
|
30
|
+
container.innerHTML = `<ds-banner status="${status}" message="Message for ${status}"></ds-banner>`;
|
|
31
|
+
const element = container.querySelector('ds-banner');
|
|
32
|
+
await element.updateComplete;
|
|
33
|
+
|
|
34
|
+
const results = await axe.run(element);
|
|
35
|
+
expect(results.violations.length).toBe(0);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should have accessible dismiss button', async () => {
|
|
40
|
+
container.innerHTML = '<ds-banner dismissible message="Dismissible banner"></ds-banner>';
|
|
41
|
+
const element = container.querySelector('ds-banner');
|
|
42
|
+
await element.updateComplete;
|
|
43
|
+
|
|
44
|
+
const dismissButton = element.shadowRoot.querySelector('ds-icon-button');
|
|
45
|
+
expect(dismissButton).toBeTruthy();
|
|
46
|
+
expect(dismissButton.getAttribute('aria-label')).toBe('Fechar banner');
|
|
47
|
+
|
|
48
|
+
const results = await axe.run(element);
|
|
49
|
+
expect(results.violations.length).toBe(0);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import '../ds-icon/ds-icon.js';
|
|
3
|
+
import '../ds-icon-button/ds-icon-button.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Banner component for system-wide feedback
|
|
7
|
+
*
|
|
8
|
+
* @element ds-banner
|
|
9
|
+
*
|
|
10
|
+
* @prop {string} status - Status type: 'error' | 'warning' | 'success' | 'info' (default: 'info')
|
|
11
|
+
* @prop {string} message - Message text
|
|
12
|
+
* @prop {boolean} dismissible - Show/hide close button (default: false)
|
|
13
|
+
* @prop {boolean} fixed - Whether the banner is fixed at the top of the viewport
|
|
14
|
+
* @prop {boolean} hideIcon - Hide the status icon
|
|
15
|
+
*
|
|
16
|
+
* @slot - Default slot for message content (if message prop is not used)
|
|
17
|
+
* @slot actions - Optional slot for action buttons
|
|
18
|
+
*
|
|
19
|
+
* @fires ds-dismiss - Dispatched when user clicks the close button
|
|
20
|
+
*
|
|
21
|
+
* @csspart banner - The main banner container
|
|
22
|
+
* @csspart content-container - The centered container limiting width
|
|
23
|
+
* @csspart icon - The status icon element
|
|
24
|
+
* @csspart message - The message text container
|
|
25
|
+
* @csspart actions - The actions slot container
|
|
26
|
+
* @csspart close-button - The close button
|
|
27
|
+
*/
|
|
28
|
+
export class DsBanner extends LitElement {
|
|
29
|
+
static properties = {
|
|
30
|
+
status: { type: String, reflect: true },
|
|
31
|
+
title: { type: String },
|
|
32
|
+
message: { type: String },
|
|
33
|
+
dismissible: { type: Boolean, reflect: true },
|
|
34
|
+
fixed: { type: Boolean, reflect: true },
|
|
35
|
+
hideIcon: { type: Boolean, attribute: 'hide-icon', reflect: true },
|
|
36
|
+
_hasActions: { type: Boolean, state: true }
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
static styles = css`
|
|
40
|
+
:host {
|
|
41
|
+
display: block;
|
|
42
|
+
width: 100%;
|
|
43
|
+
box-sizing: border-box;
|
|
44
|
+
z-index: var(--ds-zindex-message, 70);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Fixed positioning variant */
|
|
48
|
+
:host([fixed]) {
|
|
49
|
+
position: fixed;
|
|
50
|
+
top: 0;
|
|
51
|
+
left: 0;
|
|
52
|
+
right: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.banner {
|
|
56
|
+
display: flex;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
width: 100%;
|
|
59
|
+
min-height: 40px; /* Ensure 40px minimum height */
|
|
60
|
+
padding: 10px 8px; /* Matching ds-alert padding */
|
|
61
|
+
box-sizing: border-box;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Inner container to center content and constrain max-width of message */
|
|
65
|
+
.content-container {
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: flex-start; /* Align valid for multi-line */
|
|
68
|
+
width: 100%;
|
|
69
|
+
max-width: 100%; /* Allow taking full width if needed, but message will constrain */
|
|
70
|
+
gap: var(--ds-space-sm); /* 8px gap elements */
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Side containers matching ds-alert strategy for alignment */
|
|
74
|
+
.icon-container,
|
|
75
|
+
.dismiss-container {
|
|
76
|
+
flex-shrink: 0;
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
height: 20px; /* text line-height matching */
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.icon {
|
|
84
|
+
color: var(--ds-color-icon-default);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Message Area */
|
|
88
|
+
.message-area {
|
|
89
|
+
flex: 1;
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-direction: column;
|
|
92
|
+
min-width: 0;
|
|
93
|
+
color: var(--ds-color-text-default);
|
|
94
|
+
font: var(--ds-typo-content-body-regular);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Title styling */
|
|
98
|
+
.title {
|
|
99
|
+
font: var(--ds-typo-content-body-bold);
|
|
100
|
+
margin-bottom: var(--ds-space-xs); /* 4px gap to message */
|
|
101
|
+
max-width: 800px;
|
|
102
|
+
width: 100%;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Constrain the message text specifically to 800px as requested */
|
|
106
|
+
.message-text {
|
|
107
|
+
max-width: 800px;
|
|
108
|
+
width: 100%;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Actions slot wrapper */
|
|
112
|
+
.actions {
|
|
113
|
+
display: flex;
|
|
114
|
+
gap: var(--ds-space-sm);
|
|
115
|
+
flex-wrap: wrap;
|
|
116
|
+
margin-top: var(--ds-space-sm);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.actions[hidden] {
|
|
120
|
+
display: none;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Dismiss button container needs to be pushed to the right if we want it fully responsive,
|
|
124
|
+
but in this flex layout, flex:1 on message area handles it. */
|
|
125
|
+
.dismiss-container {
|
|
126
|
+
margin-left: auto; /* Push to the far right if content doesn't fill */
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Status Styles - Background Colors */
|
|
130
|
+
:host([status="error"]) .banner {
|
|
131
|
+
background-color: var(--ds-color-bg-error-subtle);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
:host([status="warning"]) .banner {
|
|
135
|
+
background-color: var(--ds-color-bg-warning-subtle);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
:host([status="success"]) .banner {
|
|
139
|
+
background-color: var(--ds-color-bg-success-subtle);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
:host([status="info"]) .banner,
|
|
143
|
+
:host(:not([status])) .banner {
|
|
144
|
+
background-color: var(--ds-color-bg-info-subtle);
|
|
145
|
+
}
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
constructor() {
|
|
149
|
+
super();
|
|
150
|
+
this.status = 'info';
|
|
151
|
+
this.title = '';
|
|
152
|
+
this.message = '';
|
|
153
|
+
this.dismissible = false;
|
|
154
|
+
this.fixed = false;
|
|
155
|
+
this.hideIcon = false;
|
|
156
|
+
this._hasActions = false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Returns the icon name based on the status
|
|
161
|
+
*/
|
|
162
|
+
_getIconName() {
|
|
163
|
+
const iconMap = {
|
|
164
|
+
error: 'cancel',
|
|
165
|
+
warning: 'warning',
|
|
166
|
+
success: 'check-circle',
|
|
167
|
+
info: 'info'
|
|
168
|
+
};
|
|
169
|
+
return iconMap[this.status] || iconMap.info;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_handleDismiss() {
|
|
173
|
+
this.dispatchEvent(new CustomEvent('ds-dismiss', {
|
|
174
|
+
bubbles: true,
|
|
175
|
+
composed: true
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_handleSlotChange(e) {
|
|
180
|
+
const assignedNodes = e.target.assignedNodes({ flatten: true });
|
|
181
|
+
this._hasActions = assignedNodes.length > 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
render() {
|
|
185
|
+
return html`
|
|
186
|
+
<div class="banner" part="banner" role="status">
|
|
187
|
+
<div class="content-container" part="content-container">
|
|
188
|
+
|
|
189
|
+
${!this.hideIcon ? html`
|
|
190
|
+
<div class="icon-container" part="icon-container">
|
|
191
|
+
<ds-icon
|
|
192
|
+
class="icon"
|
|
193
|
+
name="${this._getIconName()}"
|
|
194
|
+
size="sm"
|
|
195
|
+
part="icon"
|
|
196
|
+
></ds-icon>
|
|
197
|
+
</div>
|
|
198
|
+
` : ''}
|
|
199
|
+
|
|
200
|
+
<div class="message-area" part="message-area">
|
|
201
|
+
${this.title ? html`<div class="title" part="title">${this.title}</div>` : ''}
|
|
202
|
+
|
|
203
|
+
<div class="message-text" part="message">
|
|
204
|
+
${this.message}
|
|
205
|
+
<slot></slot>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div class="actions" ?hidden="${!this._hasActions}" part="actions">
|
|
209
|
+
<slot name="actions" @slotchange="${this._handleSlotChange}"></slot>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
${this.dismissible ? html`
|
|
214
|
+
<div class="dismiss-container" part="dismiss-container">
|
|
215
|
+
<ds-icon-button
|
|
216
|
+
class="close-button"
|
|
217
|
+
icon="close"
|
|
218
|
+
variant="action"
|
|
219
|
+
size="m"
|
|
220
|
+
aria-label="Fechar banner"
|
|
221
|
+
part="close-button"
|
|
222
|
+
@click="${this._handleDismiss}"
|
|
223
|
+
></ds-icon-button>
|
|
224
|
+
</div>
|
|
225
|
+
` : ''}
|
|
226
|
+
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
customElements.define('ds-banner', DsBanner);
|