@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,194 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @element ds-breadcrumbs
|
|
5
|
+
* @summary A navigation container for breadcrumb items.
|
|
6
|
+
*
|
|
7
|
+
* @slot - Default slot for ds-breadcrumb-item elements.
|
|
8
|
+
*/
|
|
9
|
+
export class DsBreadcrumbs extends LitElement {
|
|
10
|
+
static properties = {
|
|
11
|
+
mode: { type: String, reflect: true },
|
|
12
|
+
expanded: { type: Boolean, reflect: true }
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
static styles = css`
|
|
16
|
+
:host {
|
|
17
|
+
display: block;
|
|
18
|
+
width: 100%;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
ol {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-wrap: wrap;
|
|
24
|
+
list-style: none;
|
|
25
|
+
margin: 0;
|
|
26
|
+
padding: 0;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: var(--ds-space-sm, 8px);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Scroll Mode, Collapse Mode, and formatting */
|
|
32
|
+
:host([mode="scroll"]) ol,
|
|
33
|
+
:host([mode="collapse"]) ol {
|
|
34
|
+
flex-wrap: nowrap;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* When expanded in collapse mode: ALWAYS scroll */
|
|
38
|
+
:host([mode="collapse"][expanded]) ol {
|
|
39
|
+
flex-wrap: nowrap;
|
|
40
|
+
overflow-x: auto;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
:host([mode="scroll"]) ol,
|
|
44
|
+
:host([mode="collapse"]:not([expanded])) ol {
|
|
45
|
+
overflow-x: auto;
|
|
46
|
+
white-space: nowrap;
|
|
47
|
+
scrollbar-width: none;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Hide scrollbar for strictly scrolled views (collapsed state) */
|
|
51
|
+
:host([mode="scroll"]) ol::-webkit-scrollbar,
|
|
52
|
+
:host([mode="collapse"]:not([expanded])) ol::-webkit-scrollbar {
|
|
53
|
+
display: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Prevent shrinking */
|
|
57
|
+
:host([mode="scroll"]) ::slotted(ds-breadcrumb-item),
|
|
58
|
+
:host([mode="collapse"]) ::slotted(ds-breadcrumb-item) {
|
|
59
|
+
flex-shrink: 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Hide the separator of the last item */
|
|
63
|
+
::slotted(ds-breadcrumb-item:last-child) {
|
|
64
|
+
--ds-breadcrumb-separator-display: none;
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
constructor() {
|
|
69
|
+
super();
|
|
70
|
+
this.mode = 'wrap';
|
|
71
|
+
this.expanded = false;
|
|
72
|
+
this._resizeObserver = null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
connectedCallback() {
|
|
76
|
+
super.connectedCallback();
|
|
77
|
+
this.addEventListener('ds-expand', this._handleExpand);
|
|
78
|
+
|
|
79
|
+
this.updateComplete.then(() => {
|
|
80
|
+
this._setupResizeObserver();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
disconnectedCallback() {
|
|
85
|
+
super.disconnectedCallback();
|
|
86
|
+
this.removeEventListener('ds-expand', this._handleExpand);
|
|
87
|
+
if (this._resizeObserver) {
|
|
88
|
+
this._resizeObserver.disconnect();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
updated(changedProperties) {
|
|
93
|
+
if (changedProperties.has('mode')) {
|
|
94
|
+
this.expanded = false;
|
|
95
|
+
|
|
96
|
+
if (this.mode === 'collapse') {
|
|
97
|
+
this._setupResizeObserver();
|
|
98
|
+
this._checkOverflow();
|
|
99
|
+
} else {
|
|
100
|
+
if (this._resizeObserver) {
|
|
101
|
+
this._resizeObserver.disconnect();
|
|
102
|
+
this._resizeObserver = null;
|
|
103
|
+
}
|
|
104
|
+
this._resetItems();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (changedProperties.has('expanded') && this.expanded) {
|
|
109
|
+
// If expanded changed to true, ensured items are visible
|
|
110
|
+
this._resetItems();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_setupResizeObserver() {
|
|
115
|
+
if (this.mode !== 'collapse') return;
|
|
116
|
+
if (this._resizeObserver) return;
|
|
117
|
+
|
|
118
|
+
this._resizeObserver = new ResizeObserver((entries) => {
|
|
119
|
+
window.requestAnimationFrame(() => {
|
|
120
|
+
if (!this.expanded) {
|
|
121
|
+
this._checkOverflow();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
this._resizeObserver.observe(this);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
_checkOverflow() {
|
|
129
|
+
if (this.mode !== 'collapse' || this.expanded) return;
|
|
130
|
+
|
|
131
|
+
const ol = this.shadowRoot.querySelector('ol');
|
|
132
|
+
if (!ol) return;
|
|
133
|
+
|
|
134
|
+
// 1. Reset to full width to measure
|
|
135
|
+
this._resetItems();
|
|
136
|
+
|
|
137
|
+
// 2. Check if overflowing
|
|
138
|
+
if (Math.round(ol.scrollWidth) > Math.round(ol.clientWidth)) {
|
|
139
|
+
this._applyCollapse();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
_resetItems() {
|
|
144
|
+
const slot = this.shadowRoot.querySelector('slot');
|
|
145
|
+
if (!slot) return;
|
|
146
|
+
|
|
147
|
+
const items = slot.assignedElements();
|
|
148
|
+
items.forEach(item => {
|
|
149
|
+
item.style.display = '';
|
|
150
|
+
item.removeAttribute('collapsed');
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
_applyCollapse() {
|
|
155
|
+
const slot = this.shadowRoot.querySelector('slot');
|
|
156
|
+
if (!slot) return;
|
|
157
|
+
|
|
158
|
+
const items = slot.assignedElements();
|
|
159
|
+
if (items.length <= 2) return;
|
|
160
|
+
|
|
161
|
+
// Logic: Keep First, Keep Last. Transform [1] to trigger. Hide others.
|
|
162
|
+
const trigger = items[1];
|
|
163
|
+
const last = items[items.length - 1];
|
|
164
|
+
|
|
165
|
+
if (!trigger || trigger === last) return;
|
|
166
|
+
|
|
167
|
+
trigger.setAttribute('collapsed', '');
|
|
168
|
+
|
|
169
|
+
for (let i = 2; i < items.length - 1; i++) {
|
|
170
|
+
items[i].style.display = 'none';
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
_handleExpand() {
|
|
175
|
+
if (this.mode === 'collapse') {
|
|
176
|
+
this._manuallyExpanded = true;
|
|
177
|
+
this._resetItems();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
render() {
|
|
182
|
+
return html`
|
|
183
|
+
<nav aria-label="Breadcrumb">
|
|
184
|
+
<ol>
|
|
185
|
+
<slot></slot>
|
|
186
|
+
</ol>
|
|
187
|
+
</nav>
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!customElements.get('ds-breadcrumbs')) {
|
|
193
|
+
customElements.define('ds-breadcrumbs', DsBreadcrumbs);
|
|
194
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import './ds-breadcrumbs.js';
|
|
3
|
+
import '../ds-breadcrumb-item/ds-breadcrumb-item.js';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Components/Breadcrumbs',
|
|
7
|
+
component: 'ds-breadcrumbs',
|
|
8
|
+
argTypes: {
|
|
9
|
+
mode: {
|
|
10
|
+
control: { type: 'select' },
|
|
11
|
+
options: ['wrap', 'scroll', 'collapse']
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const Playground = (args) => html`
|
|
18
|
+
<div style="max-width: 800px; border: 1px dashed #ccc; padding: 1rem; resize: horizontal; overflow: hidden; height: 100px; width: 100%;">
|
|
19
|
+
<ds-breadcrumbs mode="${args.mode || 'wrap'}">
|
|
20
|
+
<ds-breadcrumb-item href="/" label="Home"></ds-breadcrumb-item>
|
|
21
|
+
<ds-breadcrumb-item href="/level1" label="Level 1"></ds-breadcrumb-item>
|
|
22
|
+
<ds-breadcrumb-item href="/level2" label="Level 2"></ds-breadcrumb-item>
|
|
23
|
+
<ds-breadcrumb-item href="/level3" label="Level 3"></ds-breadcrumb-item>
|
|
24
|
+
<ds-breadcrumb-item href="/level4" label="Level 4"></ds-breadcrumb-item>
|
|
25
|
+
<ds-breadcrumb-item href="/level5" label="Level 5"></ds-breadcrumb-item>
|
|
26
|
+
<ds-breadcrumb-item href="/level6" label="Level 6"></ds-breadcrumb-item>
|
|
27
|
+
<ds-breadcrumb-item href="/level7" label="Level 7"></ds-breadcrumb-item>
|
|
28
|
+
<ds-breadcrumb-item label="Current Page" current></ds-breadcrumb-item>
|
|
29
|
+
</ds-breadcrumbs>
|
|
30
|
+
</div>
|
|
31
|
+
<p style="font-size: 12px; color: #666; margin-top: 8px;">
|
|
32
|
+
<strong>Current Mode:</strong> ${args.mode}<br>
|
|
33
|
+
Resize the dashed box to test responsive behaviors. If in collapse mode, click '...' to expand (will scroll).
|
|
34
|
+
</p>
|
|
35
|
+
`;
|
|
36
|
+
Playground.args = {
|
|
37
|
+
mode: 'collapse',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const Default = () => html`
|
|
41
|
+
<ds-breadcrumbs>
|
|
42
|
+
<ds-breadcrumb-item href="/" label="Home"></ds-breadcrumb-item>
|
|
43
|
+
<ds-breadcrumb-item href="/components" label="Components"></ds-breadcrumb-item>
|
|
44
|
+
<ds-breadcrumb-item label="Breadcrumbs" current></ds-breadcrumb-item>
|
|
45
|
+
</ds-breadcrumbs>
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
export const TrailingLink = () => html`
|
|
49
|
+
<ds-breadcrumbs>
|
|
50
|
+
<ds-breadcrumb-item href="/" label="Home"></ds-breadcrumb-item>
|
|
51
|
+
<ds-breadcrumb-item href="/section" label="Section"></ds-breadcrumb-item>
|
|
52
|
+
<ds-breadcrumb-item href="/page" label="Page Label (Link)"></ds-breadcrumb-item>
|
|
53
|
+
</ds-breadcrumbs>
|
|
54
|
+
`;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import './ds-breadcrumbs.js';
|
|
3
|
+
import '../ds-breadcrumb-item/ds-breadcrumb-item.js';
|
|
4
|
+
|
|
5
|
+
describe('ds-breadcrumbs', () => {
|
|
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 accessible navigation structure', async () => {
|
|
18
|
+
container.innerHTML = `
|
|
19
|
+
<ds-breadcrumbs>
|
|
20
|
+
<ds-breadcrumb-item href="#" label="Home"></ds-breadcrumb-item>
|
|
21
|
+
</ds-breadcrumbs>
|
|
22
|
+
`;
|
|
23
|
+
const el = container.querySelector('ds-breadcrumbs');
|
|
24
|
+
await new Promise(r => setTimeout(r, 50));
|
|
25
|
+
|
|
26
|
+
const nav = el.shadowRoot.querySelector('nav');
|
|
27
|
+
expect(nav).toBeTruthy();
|
|
28
|
+
expect(nav.getAttribute('aria-label')).toBe('Breadcrumb');
|
|
29
|
+
|
|
30
|
+
const ol = el.shadowRoot.querySelector('ol');
|
|
31
|
+
expect(ol).toBeTruthy();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import axe from 'axe-core';
|
|
3
|
+
import './ds-button.js';
|
|
4
|
+
|
|
5
|
+
describe('ds-button 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 all variants', async () => {
|
|
18
|
+
const variants = ['primary', 'secondary', 'outline', 'action', 'tertiary'];
|
|
19
|
+
|
|
20
|
+
// Disable color-contrast rule for now since we are in a test environment without full styles context
|
|
21
|
+
// and some tokens might not evaluate to actual colors if CSS vars aren't fully resolved in JSDOM
|
|
22
|
+
const options = {
|
|
23
|
+
rules: {
|
|
24
|
+
'color-contrast': { enabled: false }
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
for (const variant of variants) {
|
|
29
|
+
container.innerHTML = `<ds-button variant="${variant}">Button</ds-button>`;
|
|
30
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
31
|
+
|
|
32
|
+
const results = await axe.run(container, options);
|
|
33
|
+
if (results.violations.length > 0) {
|
|
34
|
+
console.log(`Violations for ${variant}:`, JSON.stringify(results.violations, null, 2));
|
|
35
|
+
}
|
|
36
|
+
expect(results.violations).toHaveLength(0);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should be accessible when disabled', async () => {
|
|
41
|
+
container.innerHTML = '<ds-button disabled>Disabled Button</ds-button>';
|
|
42
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
43
|
+
|
|
44
|
+
const results = await axe.run(container, {
|
|
45
|
+
rules: { 'color-contrast': { enabled: false } }
|
|
46
|
+
});
|
|
47
|
+
expect(results.violations).toHaveLength(0);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Button component for the Design System
|
|
5
|
+
*
|
|
6
|
+
* @element ds-button
|
|
7
|
+
*
|
|
8
|
+
* @prop {string} variant - Button style variant: 'primary' | 'secondary' | 'outline' | 'action' | 'tertiary' (default: 'primary')
|
|
9
|
+
* @prop {boolean} disabled - Whether the button is disabled
|
|
10
|
+
*
|
|
11
|
+
* @slot - Button label content
|
|
12
|
+
* @slot icon-start - Icon to display before the label
|
|
13
|
+
* @slot icon-end - Icon to display after the label
|
|
14
|
+
*/
|
|
15
|
+
export class DsButton extends LitElement {
|
|
16
|
+
static properties = {
|
|
17
|
+
variant: { type: String, reflect: true },
|
|
18
|
+
disabled: { type: Boolean, reflect: true }
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };
|
|
22
|
+
|
|
23
|
+
static styles = css`
|
|
24
|
+
:host {
|
|
25
|
+
display: inline-block;
|
|
26
|
+
vertical-align: middle;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
:host([full-width]) {
|
|
30
|
+
display: block;
|
|
31
|
+
width: 100%;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:host([full-width]) button,
|
|
35
|
+
:host([style*="width: 100%"]) button {
|
|
36
|
+
width: 100%;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
button {
|
|
40
|
+
appearance: none;
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
/* Layout */
|
|
45
|
+
/* Height = 20px (line-height) + 10px (padding-y) + 2px (border) = 32px */
|
|
46
|
+
/* Padding calculation to achieve 32px height:
|
|
47
|
+
Target 32px - 20px (line-height) - 2px (border) = 10px total vertical padding
|
|
48
|
+
Top/Bottom padding = 5px
|
|
49
|
+
*/
|
|
50
|
+
padding: 5px var(--ds-space-sm);
|
|
51
|
+
border: 1px solid transparent; /* Always present border to maintain layout consistency */
|
|
52
|
+
border-radius: var(--ds-radius-action, 999px);
|
|
53
|
+
background: transparent;
|
|
54
|
+
width: 100%;
|
|
55
|
+
|
|
56
|
+
/* Typography */
|
|
57
|
+
color: inherit;
|
|
58
|
+
font: var(--ds-typo-content-body-bold);
|
|
59
|
+
text-decoration: none;
|
|
60
|
+
|
|
61
|
+
/* Interaction */
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
appearance: none;
|
|
64
|
+
box-sizing: border-box;
|
|
65
|
+
transition: background-color 0.2s, color 0.2s, border-color 0.2s;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Label slot wrapper */
|
|
69
|
+
.label {
|
|
70
|
+
padding: 0 var(--ds-space-xs); /* 4px horizontal padding */
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Disabled state */
|
|
74
|
+
:host([disabled]) button {
|
|
75
|
+
cursor: not-allowed;
|
|
76
|
+
pointer-events: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
:host([disabled]) {
|
|
80
|
+
pointer-events: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Focus */
|
|
84
|
+
button:focus-visible {
|
|
85
|
+
outline: 2px solid var(--ds-color-border-focus);
|
|
86
|
+
outline-offset: 2px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
* VARIANTS
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/* PRIMARY */
|
|
94
|
+
:host([variant="primary"]) button {
|
|
95
|
+
background: var(--ds-color-bg-brand);
|
|
96
|
+
color: var(--ds-color-text-inverse);
|
|
97
|
+
}
|
|
98
|
+
:host([variant="primary"]:not([disabled])) button:hover {
|
|
99
|
+
background: var(--ds-color-bg-brand-hover);
|
|
100
|
+
}
|
|
101
|
+
:host([variant="primary"]:not([disabled])) button:active {
|
|
102
|
+
background: var(--ds-color-bg-brand-pressed);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* SECONDARY */
|
|
106
|
+
:host([variant="secondary"]) button {
|
|
107
|
+
background: var(--ds-color-bg-secondary);
|
|
108
|
+
color: var(--ds-color-text-default);
|
|
109
|
+
border-color: var(--ds-color-border-strong);
|
|
110
|
+
}
|
|
111
|
+
:host([variant="secondary"]:not([disabled])) button:hover {
|
|
112
|
+
background: var(--ds-color-bg-hover);
|
|
113
|
+
}
|
|
114
|
+
:host([variant="secondary"]:not([disabled])) button:active {
|
|
115
|
+
background: var(--ds-color-bg-pressed);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* OUTLINE */
|
|
119
|
+
:host([variant="outline"]) button {
|
|
120
|
+
background: transparent;
|
|
121
|
+
color: var(--ds-color-text-brand);
|
|
122
|
+
border-color: var(--ds-color-border-brand);
|
|
123
|
+
}
|
|
124
|
+
:host([variant="outline"]:not([disabled])) button:hover {
|
|
125
|
+
background: var(--ds-color-bg-hover);
|
|
126
|
+
color: var(--ds-color-text-brand-hover);
|
|
127
|
+
border-color: var(--ds-color-border-brand-hover);
|
|
128
|
+
}
|
|
129
|
+
:host([variant="outline"]:not([disabled])) button:active {
|
|
130
|
+
background: var(--ds-color-bg-pressed);
|
|
131
|
+
color: var(--ds-color-text-brand-pressed);
|
|
132
|
+
border-color: var(--ds-color-border-brand-pressed);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* ACTION */
|
|
136
|
+
:host([variant="action"]) button {
|
|
137
|
+
background: transparent;
|
|
138
|
+
color: var(--ds-color-text-default);
|
|
139
|
+
}
|
|
140
|
+
:host([variant="action"]:not([disabled])) button:hover {
|
|
141
|
+
background: var(--ds-color-bg-hover);
|
|
142
|
+
}
|
|
143
|
+
:host([variant="action"]:not([disabled])) button:active {
|
|
144
|
+
background: var(--ds-color-bg-pressed);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* TERTIARY */
|
|
148
|
+
:host([variant="tertiary"]) button {
|
|
149
|
+
background: transparent;
|
|
150
|
+
color: var(--ds-color-text-brand);
|
|
151
|
+
}
|
|
152
|
+
:host([variant="tertiary"]:not([disabled])) button:hover {
|
|
153
|
+
background: var(--ds-color-bg-hover);
|
|
154
|
+
color: var(--ds-color-text-brand-hover);
|
|
155
|
+
}
|
|
156
|
+
:host([variant="tertiary"]:not([disabled])) button:active {
|
|
157
|
+
background: var(--ds-color-bg-pressed);
|
|
158
|
+
color: var(--ds-color-text-brand-pressed);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* DISABLED STATE for all variants */
|
|
162
|
+
:host([disabled]) button {
|
|
163
|
+
background: var(--ds-color-bg-disabled);
|
|
164
|
+
color: var(--ds-color-text-disabled);
|
|
165
|
+
border-color: transparent;
|
|
166
|
+
}
|
|
167
|
+
:host([variant="secondary"][disabled]) button {
|
|
168
|
+
border-color: var(--ds-color-border-disabled); /* Secondary keeps border or not? Usually disabled buttons have no border or disabled border. Let's use border-disabled if border exists. */
|
|
169
|
+
}
|
|
170
|
+
:host([variant="outline"][disabled]) button {
|
|
171
|
+
border-color: var(--ds-color-border-disabled);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
:host([variant="action"][disabled]) button,
|
|
175
|
+
:host([variant="tertiary"][disabled]) button {
|
|
176
|
+
background: transparent;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Icon styling within button */
|
|
180
|
+
::slotted(ds-icon) {
|
|
181
|
+
/* Icons inherit color from parent button text color (currentColor) */
|
|
182
|
+
--size: var(--ds-icon-size-sm, 20px);
|
|
183
|
+
}
|
|
184
|
+
`;
|
|
185
|
+
|
|
186
|
+
constructor() {
|
|
187
|
+
super();
|
|
188
|
+
this.variant = 'primary';
|
|
189
|
+
this.disabled = false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
render() {
|
|
193
|
+
return html`
|
|
194
|
+
<button ?disabled=${this.disabled} part="button">
|
|
195
|
+
<slot name="icon-start"></slot>
|
|
196
|
+
<span class="label">
|
|
197
|
+
<slot></slot>
|
|
198
|
+
</span>
|
|
199
|
+
<slot name="icon-end"></slot>
|
|
200
|
+
</button>
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
customElements.define('ds-button', DsButton);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Meta, Canvas, Controls } from '@storybook/blocks';
|
|
2
|
+
import * as DsButtonStories from './ds-button.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={DsButtonStories} />
|
|
5
|
+
|
|
6
|
+
# Button
|
|
7
|
+
|
|
8
|
+
Buttons allow users to initiate or confirm actions.
|
|
9
|
+
|
|
10
|
+
## Playground
|
|
11
|
+
Interact with the button properties using the controls below.
|
|
12
|
+
|
|
13
|
+
<Canvas of={DsButtonStories.Primary} />
|
|
14
|
+
<Controls of={DsButtonStories.Primary} />
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
### When to use
|
|
19
|
+
- To initiate or confirm an action.
|
|
20
|
+
- To display specific actions that require clear labeling.
|
|
21
|
+
|
|
22
|
+
### When not to use
|
|
23
|
+
- To display static or read-only information.
|
|
24
|
+
- For navigation to other pages or external resources—use a text link instead.
|
|
25
|
+
|
|
26
|
+
## Best Practices
|
|
27
|
+
|
|
28
|
+
### General
|
|
29
|
+
- ✅ **Single Primary:** Keep a single primary button per screen to guide the user's main action.
|
|
30
|
+
- ✅ **Clarity:** Buttons must always have a label for clarity and accessibility.
|
|
31
|
+
- ✅ **Icons:** Place icons to emphasize the action or add clarity; never use icons purely for decoration.
|
|
32
|
+
- ✅ **Width:** Maintain a minimum width of **72px** for buttons to ensure touch accessibility.
|
|
33
|
+
|
|
34
|
+
### Grouping & Layout
|
|
35
|
+
- ✅ **Limit:** Group no more than **three buttons** together.
|
|
36
|
+
- ✅ **Spacing:** Maintain at least **8px** spacing between grouped buttons.
|
|
37
|
+
- ✅ **Alignment:**
|
|
38
|
+
- **Left (F-pattern):** On full screens.
|
|
39
|
+
- **Right (Z-pattern):** In dialogs, modals, and dropdowns.
|
|
40
|
+
- ✅ **Wizards:** In multi-step tasks, place "Next" on the right.
|
|
41
|
+
- ✅ **Headers:** In page headers, the primary action may be placed on the right.
|
|
42
|
+
- ✅ **Mobile:** Stack buttons vertically in small containers, with the main action on top.
|
|
43
|
+
- ✅ **Text Wrapping:** Button labels should wrap to a new line if they are too long for the available space.
|
|
44
|
+
|
|
45
|
+
## Types
|
|
46
|
+
|
|
47
|
+
### Primary
|
|
48
|
+
The primary button should draw attention to the most important action on a screen, so reserve it for actions that are essential to the experience. This helps create a clear visual hierarchy and keeps users focused on what matters most.
|
|
49
|
+
|
|
50
|
+
<Canvas of={DsButtonStories.Primary} />
|
|
51
|
+
|
|
52
|
+
### Secondary
|
|
53
|
+
The secondary button supports less critical actions and complements the primary button.
|
|
54
|
+
|
|
55
|
+
<Canvas of={DsButtonStories.Secondary} />
|
|
56
|
+
|
|
57
|
+
### Outline
|
|
58
|
+
Outline buttons have less prominence than a primary button and slightly more prominence than a tertiary button. It can be used on its own or when there is no clear distinction between multiple important actions on a screen.
|
|
59
|
+
|
|
60
|
+
<Canvas of={DsButtonStories.Outline} />
|
|
61
|
+
|
|
62
|
+
### Tertiary
|
|
63
|
+
Tertiary buttons have the lowest prominence, so they should be used for low impact actions and/or actions that are not directly related with the primary button.
|
|
64
|
+
|
|
65
|
+
<Canvas of={DsButtonStories.Tertiary} />
|
|
66
|
+
|
|
67
|
+
### Action
|
|
68
|
+
Action buttons let users complete routine tasks or make selections within a workflow. They’re designed to be subtle controls and usually appear inside containers like action bars, so they don’t compete with primary call-to-action elements.
|
|
69
|
+
|
|
70
|
+
<Canvas of={DsButtonStories.Action} />
|
|
71
|
+
|
|
72
|
+
## Functionality & States
|
|
73
|
+
|
|
74
|
+
### Icon Start
|
|
75
|
+
Use an icon at the start of the label to reinforce the action's meaning or add visual context.
|
|
76
|
+
|
|
77
|
+
<Canvas of={DsButtonStories.WithIconStart} />
|
|
78
|
+
|
|
79
|
+
### Icon End
|
|
80
|
+
Use an icon at the end of the label to indicate direction (e.g., "Next", "Continue") or a dropdown action.
|
|
81
|
+
|
|
82
|
+
<Canvas of={DsButtonStories.WithIconEnd} />
|
|
83
|
+
|
|
84
|
+
### Both Icons
|
|
85
|
+
Buttons can support icons on both sides if the context requires it, though this should be used sparingly.
|
|
86
|
+
|
|
87
|
+
<Canvas of={DsButtonStories.WithBothIcons} />
|
|
88
|
+
|
|
89
|
+
### Disabled
|
|
90
|
+
Use the disabled state to indicate that an action is currently unavailable (e.g., missing form data).
|
|
91
|
+
|
|
92
|
+
<Canvas of={DsButtonStories.Disabled} />
|
|
93
|
+
|
|
94
|
+
## Accessibility
|
|
95
|
+
|
|
96
|
+
<table style={{ width: '100%' }}>
|
|
97
|
+
<thead>
|
|
98
|
+
<tr>
|
|
99
|
+
<th>Attribute</th>
|
|
100
|
+
<th>Value</th>
|
|
101
|
+
<th>Notes</th>
|
|
102
|
+
</tr>
|
|
103
|
+
</thead>
|
|
104
|
+
<tbody>
|
|
105
|
+
<tr>
|
|
106
|
+
<td><code>role</code></td>
|
|
107
|
+
<td><code>button</code></td>
|
|
108
|
+
<td>Native button element provides this</td>
|
|
109
|
+
</tr>
|
|
110
|
+
<tr>
|
|
111
|
+
<td><code>disabled</code></td>
|
|
112
|
+
<td><code>true/false</code></td>
|
|
113
|
+
<td>Disables interaction and updates <code>aria-disabled</code></td>
|
|
114
|
+
</tr>
|
|
115
|
+
</tbody>
|
|
116
|
+
</table>
|
|
117
|
+
|
|
118
|
+
### Keyboard Support
|
|
119
|
+
|
|
120
|
+
<table style={{ width: '100%' }}>
|
|
121
|
+
<thead>
|
|
122
|
+
<tr>
|
|
123
|
+
<th>Key</th>
|
|
124
|
+
<th>Action</th>
|
|
125
|
+
</tr>
|
|
126
|
+
</thead>
|
|
127
|
+
<tbody>
|
|
128
|
+
<tr>
|
|
129
|
+
<td><kbd>Enter</kbd> / <kbd>Space</kbd></td>
|
|
130
|
+
<td>Activates the button</td>
|
|
131
|
+
</tr>
|
|
132
|
+
<tr>
|
|
133
|
+
<td><kbd>Tab</kbd></td>
|
|
134
|
+
<td>Moves focus to next focusable element</td>
|
|
135
|
+
</tr>
|
|
136
|
+
<tr>
|
|
137
|
+
<td><kbd>Shift</kbd> + <kbd>Tab</kbd></td>
|
|
138
|
+
<td>Moves focus to previous focusable element</td>
|
|
139
|
+
</tr>
|
|
140
|
+
</tbody>
|
|
141
|
+
</table>
|