@orangesk/orange-design-system 2.0.0-beta.46 → 2.0.0-beta.48
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/build/components/BlockAction/style.css +1 -1
- package/build/components/BlockAction/style.css.map +1 -1
- package/build/components/Breadcrumbs/style.css +1 -1
- package/build/components/Breadcrumbs/style.css.map +1 -1
- package/build/components/Carousel/style.css +1 -1
- package/build/components/Carousel/style.css.map +1 -1
- package/build/components/DocumentationSidebar/style.css +1 -1
- package/build/components/DocumentationSidebar/style.css.map +1 -1
- package/build/components/Footer/style.css +1 -1
- package/build/components/Footer/style.css.map +1 -1
- package/build/components/Icon/style.css +1 -1
- package/build/components/Icon/style.css.map +1 -1
- package/build/components/Link/style.css +1 -1
- package/build/components/Link/style.css.map +1 -1
- package/build/components/Loader/style.css +1 -1
- package/build/components/Loader/style.css.map +1 -1
- package/build/components/Megamenu/style.css +1 -1
- package/build/components/Megamenu/style.css.map +1 -1
- package/build/components/Pagination/style.css +1 -1
- package/build/components/Pagination/style.css.map +1 -1
- package/build/components/PromotionCard/style.css +1 -1
- package/build/components/PromotionCard/style.css.map +1 -1
- package/build/components/Section/style.css +1 -1
- package/build/components/Section/style.css.map +1 -1
- package/build/components/SocialButton/style.css +1 -1
- package/build/components/SocialButton/style.css.map +1 -1
- package/build/components/Stepbar/style.css +1 -1
- package/build/components/Stepbar/style.css.map +1 -1
- package/build/components/Table/style.css +1 -1
- package/build/components/Table/style.css.map +1 -1
- package/build/components/Tabs/style.css +1 -1
- package/build/components/Tabs/style.css.map +1 -1
- package/build/components/Tag/style.css +1 -1
- package/build/components/Tag/style.css.map +1 -1
- package/build/components/Tile/style.css +1 -1
- package/build/components/Tile/style.css.map +1 -1
- package/build/components/index.js +1 -1
- package/build/components/index.js.map +1 -1
- package/build/components/tsconfig.tsbuildinfo +1 -1
- package/build/components/types/index.d.ts +4 -15
- package/build/components/types/src/components/Carousel/Carousel.static.d.ts +4 -1
- package/build/components/types/src/components/Forms/Autocomplete/Autocomplete.static.d.ts +11 -0
- package/build/components/types/src/components/Forms/Group/Group.d.ts +1 -1
- package/build/components/types/src/components/Preview/CodeExample.d.ts +1 -0
- package/build/components/types/src/components/Preview/PreviewGenerator.d.ts +1 -0
- package/build/components/types/src/components/Preview/getElementDisplayName.d.ts +1 -0
- package/build/components/types/src/components/Tabs/Tabs.d.ts +0 -4
- package/build/components/types/src/components/Tabs/Tabs.static.d.ts +12 -0
- package/build/components/types/src/components/Tile/Tile.d.ts +3 -11
- package/build/lib/base.css +1 -1
- package/build/lib/base.css.map +1 -1
- package/build/lib/components.css +1 -1
- package/build/lib/components.css.map +1 -1
- package/build/lib/footer.css +1 -1
- package/build/lib/footer.css.map +1 -1
- package/build/lib/megamenu.css +1 -1
- package/build/lib/megamenu.css.map +1 -1
- package/build/lib/scripts.js +1 -1
- package/build/lib/scripts.js.map +1 -1
- package/build/lib/style.css +1 -1
- package/build/lib/style.css.map +1 -1
- package/build/lib/tsconfig.tsbuildinfo +1 -1
- package/build/lib/utilities.css +1 -1
- package/build/lib/utilities.css.map +1 -1
- package/build/search-index.json +6 -6
- package/package.json +24 -24
- package/src/components/BlockAction/styles/style.scss +6 -4
- package/src/components/Breadcrumbs/styles/mixins.scss +15 -8
- package/src/components/Breadcrumbs/styles/style.scss +2 -1
- package/src/components/Carousel/Carousel.static.ts +29 -1
- package/src/components/Carousel/styles/mixins.scss +22 -2
- package/src/components/Carousel/tests/Carousel.static.test.jsx +50 -0
- package/src/components/DocumentationSidebar/DocumentationSidebar.tsx +21 -34
- package/src/components/DocumentationSidebar/styles/style.scss +0 -6
- package/src/components/Footer/styles/mixins.scss +2 -1
- package/src/components/Forms/Autocomplete/Autocomplete.static.ts +190 -14
- package/src/components/Forms/Autocomplete/styles/style.scss +61 -8
- package/src/components/Forms/Autocomplete/tests/Autocomplete.static.test.ts +187 -0
- package/src/components/Forms/Checkbox/styles/style.scss +13 -6
- package/src/components/Forms/DatePicker/styles/style.scss +1 -2
- package/src/components/Forms/Group/Group.tsx +4 -1
- package/src/components/Forms/Group/styles/config.scss +1 -1
- package/src/components/Forms/Group/styles/mixins.scss +17 -0
- package/src/components/Forms/Group/tests/Group.unit.test.jsx +9 -0
- package/src/components/Forms/InputStepper/styles/style.scss +15 -8
- package/src/components/Forms/TextArea/styles/config.scss +1 -0
- package/src/components/Forms/TextArea/styles/mixins.scss +7 -1
- package/src/components/Forms/TextInput/styles/config.scss +3 -1
- package/src/components/Forms/TextInput/styles/mixins.scss +7 -1
- package/src/components/Forms/TextInput/styles/style.scss +17 -12
- package/src/components/Forms/styles/config.scss +3 -2
- package/src/components/Icon/styles/style.scss +2 -1
- package/src/components/Link/styles/mixins.scss +0 -1
- package/src/components/Loader/styles/style.scss +0 -2
- package/src/components/Megamenu/Megamenu.tsx +2 -2
- package/src/components/Megamenu/MegamenuBlog.tsx +2 -2
- package/src/components/Megamenu/styles/mixins.scss +20 -12
- package/src/components/Pagination/styles/mixins.scss +8 -6
- package/src/components/Pagination/styles/style.scss +0 -4
- package/src/components/Preview/CodeExample.tsx +66 -25
- package/src/components/Preview/Preview.tsx +26 -13
- package/src/components/Preview/PreviewGenerator.tsx +72 -34
- package/src/components/Preview/getElementDisplayName.ts +25 -0
- package/src/components/PromotionCard/styles/mixins.scss +2 -1
- package/src/components/Section/styles/mixins.scss +27 -5
- package/src/components/SocialButton/styles/config.scss +2 -2
- package/src/components/Stepbar/styles/config.scss +34 -17
- package/src/components/Stepbar/styles/mixins.scss +5 -3
- package/src/components/Stepbar/styles/style.scss +4 -2
- package/src/components/Table/styles/mixins.scss +6 -3
- package/src/components/Tabs/Tabs.static.ts +157 -30
- package/src/components/Tabs/Tabs.tsx +62 -67
- package/src/components/Tabs/styles/config.scss +18 -25
- package/src/components/Tabs/styles/mixins.scss +93 -28
- package/src/components/Tabs/styles/style.scss +4 -15
- package/src/components/Tabs/tests/Tabs.unit.test.jsx +111 -0
- package/src/components/Tag/styles/config.scss +1 -1
- package/src/components/Tag/styles/style.scss +22 -5
- package/src/components/Tile/Tile.tsx +18 -42
- package/src/components/Tile/styles/mixins.scss +45 -47
- package/src/components/Tile/styles/style.scss +5 -17
- package/src/components/Tile/tests/Tile.unit.test.jsx +9 -78
- package/src/styles/base/globals.scss +2 -0
- package/src/styles/tokens/color-vars.scss +0 -2
- package/src/styles/utilities/color.scss +0 -153
- package/src/styles/utilities/horizontal-scroll.scss +7 -2
- package/src/styles/utilities/text.scss +0 -1
- package/src/components/Tile/CHANGELOG.md +0 -28
- package/src/components/Tile/styles/config.scss +0 -7
|
@@ -13,12 +13,19 @@ export const configDocs = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export default class Tabs {
|
|
16
|
+
private static readonly SCROLL_ALIGNMENT_TOLERANCE = 1;
|
|
17
|
+
private static readonly SCROLL_EDGE_TOLERANCE = 1;
|
|
18
|
+
|
|
16
19
|
private element: HTMLElement;
|
|
17
20
|
private config: TabsConfig;
|
|
18
21
|
private tabs: HTMLElement[];
|
|
19
22
|
private tabPanels: (HTMLElement | null)[];
|
|
20
23
|
private activeTabIndex: number | null;
|
|
21
24
|
private rovingTabindex: RovingTabindex | null;
|
|
25
|
+
private resizeObserver: ResizeObserver | null;
|
|
26
|
+
private horizontalScrollHandler: () => void;
|
|
27
|
+
private resizeHandler: () => void;
|
|
28
|
+
private viewportElement: HTMLElement | null;
|
|
22
29
|
|
|
23
30
|
constructor(element: HTMLElement, config?: Partial<TabsConfig>) {
|
|
24
31
|
this.element = element;
|
|
@@ -29,9 +36,14 @@ export default class Tabs {
|
|
|
29
36
|
this.activeTabIndex = null;
|
|
30
37
|
|
|
31
38
|
this.rovingTabindex = null;
|
|
39
|
+
this.resizeObserver = null;
|
|
40
|
+
this.viewportElement = null;
|
|
32
41
|
|
|
33
42
|
this.handleClick = this.handleClick.bind(this);
|
|
34
43
|
this.handleTabFocus = this.handleTabFocus.bind(this);
|
|
44
|
+
this.horizontalScrollHandler =
|
|
45
|
+
this.updateHorizontalOverflowState.bind(this);
|
|
46
|
+
this.resizeHandler = this.updateHorizontalOverflowState.bind(this);
|
|
35
47
|
|
|
36
48
|
(this.element as any).ODS_Tabs = this;
|
|
37
49
|
|
|
@@ -73,13 +85,17 @@ export default class Tabs {
|
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
if (this.activeTabIndex !== null) {
|
|
76
|
-
this.
|
|
88
|
+
this.setActiveTab(this.activeTabIndex);
|
|
77
89
|
} else if (this.tabs.length > 0) {
|
|
78
90
|
this.activateNthTab(0);
|
|
79
91
|
}
|
|
92
|
+
|
|
93
|
+
this.setupOverflowObserver();
|
|
80
94
|
}
|
|
81
95
|
|
|
82
96
|
destroy(): void {
|
|
97
|
+
this.teardownOverflowObserver();
|
|
98
|
+
|
|
83
99
|
this.tabs.map((tab) => {
|
|
84
100
|
tab.removeEventListener("click", this.handleClick);
|
|
85
101
|
tab.removeEventListener("focus", this.handleTabFocus);
|
|
@@ -94,6 +110,66 @@ export default class Tabs {
|
|
|
94
110
|
(this.element as any).ODS_Tabs = null;
|
|
95
111
|
}
|
|
96
112
|
|
|
113
|
+
private setupOverflowObserver(): void {
|
|
114
|
+
this.teardownOverflowObserver();
|
|
115
|
+
|
|
116
|
+
this.viewportElement = this.element.parentElement?.classList.contains(
|
|
117
|
+
"tab-list__viewport",
|
|
118
|
+
)
|
|
119
|
+
? this.element.parentElement
|
|
120
|
+
: null;
|
|
121
|
+
|
|
122
|
+
this.element.addEventListener("scroll", this.horizontalScrollHandler, {
|
|
123
|
+
passive: true,
|
|
124
|
+
});
|
|
125
|
+
window.addEventListener("resize", this.resizeHandler);
|
|
126
|
+
|
|
127
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
128
|
+
this.resizeObserver = new ResizeObserver(this.resizeHandler);
|
|
129
|
+
this.resizeObserver.observe(this.element);
|
|
130
|
+
this.tabs.forEach((tab) => this.resizeObserver?.observe(tab));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.updateHorizontalOverflowState();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private teardownOverflowObserver(): void {
|
|
137
|
+
this.element.removeEventListener("scroll", this.horizontalScrollHandler);
|
|
138
|
+
window.removeEventListener("resize", this.resizeHandler);
|
|
139
|
+
|
|
140
|
+
if (this.viewportElement) {
|
|
141
|
+
this.viewportElement.classList.remove(
|
|
142
|
+
"has-left-overflow",
|
|
143
|
+
"has-right-overflow",
|
|
144
|
+
);
|
|
145
|
+
this.viewportElement = null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (this.resizeObserver) {
|
|
149
|
+
this.resizeObserver.disconnect();
|
|
150
|
+
this.resizeObserver = null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private updateHorizontalOverflowState(): void {
|
|
155
|
+
if (!this.viewportElement) return;
|
|
156
|
+
|
|
157
|
+
const maxScrollLeft = this.element.scrollWidth - this.element.clientWidth;
|
|
158
|
+
const hasOverflow = maxScrollLeft > Tabs.SCROLL_EDGE_TOLERANCE;
|
|
159
|
+
const atStart = this.element.scrollLeft <= Tabs.SCROLL_EDGE_TOLERANCE;
|
|
160
|
+
const atEnd =
|
|
161
|
+
this.element.scrollLeft >= maxScrollLeft - Tabs.SCROLL_EDGE_TOLERANCE;
|
|
162
|
+
|
|
163
|
+
this.viewportElement.classList.toggle(
|
|
164
|
+
"has-left-overflow",
|
|
165
|
+
hasOverflow && !atStart,
|
|
166
|
+
);
|
|
167
|
+
this.viewportElement.classList.toggle(
|
|
168
|
+
"has-right-overflow",
|
|
169
|
+
hasOverflow && !atEnd,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
97
173
|
update(): void {
|
|
98
174
|
this.destroy();
|
|
99
175
|
this.init();
|
|
@@ -105,31 +181,80 @@ export default class Tabs {
|
|
|
105
181
|
private isSelected = (el: HTMLElement): boolean =>
|
|
106
182
|
el.getAttribute("aria-selected") === "true";
|
|
107
183
|
|
|
108
|
-
private
|
|
109
|
-
const clickedTab = e.currentTarget as HTMLElement;
|
|
110
|
-
|
|
111
|
-
if (clickedTab.hasAttribute("aria-disabled")) {
|
|
112
|
-
e.preventDefault();
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
184
|
+
private setActiveTab(index: number, forceCenter: boolean = false): void {
|
|
116
185
|
this.tabs.map((tab, i) => {
|
|
117
|
-
if (
|
|
186
|
+
if (i === index) {
|
|
118
187
|
this.activeTabIndex = i;
|
|
119
188
|
return this.toggleTab(tab, "on");
|
|
120
189
|
}
|
|
121
190
|
return this.toggleTab(tab, "off");
|
|
122
191
|
});
|
|
123
192
|
|
|
124
|
-
this.tabPanels.map((tabPanel) => {
|
|
125
|
-
if (
|
|
126
|
-
tabPanel &&
|
|
127
|
-
tabPanel.getAttribute("id") === clickedTab.getAttribute("aria-controls")
|
|
128
|
-
) {
|
|
193
|
+
this.tabPanels.map((tabPanel, i) => {
|
|
194
|
+
if (i === index) {
|
|
129
195
|
return this.toggleTabPanel(tabPanel, "on");
|
|
130
196
|
}
|
|
131
197
|
return this.toggleTabPanel(tabPanel, "off");
|
|
132
198
|
});
|
|
199
|
+
|
|
200
|
+
const activeTab = this.tabs[index];
|
|
201
|
+
if (activeTab) {
|
|
202
|
+
this.scrollTabIntoView(activeTab, forceCenter);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private scrollTabIntoView(
|
|
207
|
+
activeTab: HTMLElement,
|
|
208
|
+
forceCenter: boolean = false,
|
|
209
|
+
): void {
|
|
210
|
+
const maxScrollLeft = Math.max(
|
|
211
|
+
0,
|
|
212
|
+
this.element.scrollWidth - this.element.clientWidth,
|
|
213
|
+
);
|
|
214
|
+
const contentRect = this.element.getBoundingClientRect();
|
|
215
|
+
const itemRect = activeTab.getBoundingClientRect();
|
|
216
|
+
const itemCenterWithinContent =
|
|
217
|
+
itemRect.left -
|
|
218
|
+
contentRect.left +
|
|
219
|
+
this.element.scrollLeft +
|
|
220
|
+
itemRect.width / 2;
|
|
221
|
+
const targetScrollLeft =
|
|
222
|
+
itemCenterWithinContent - this.element.clientWidth / 2;
|
|
223
|
+
const behavior = window.innerWidth < 768 ? "auto" : "smooth";
|
|
224
|
+
const nextScrollLeft = Math.min(
|
|
225
|
+
maxScrollLeft,
|
|
226
|
+
Math.max(0, targetScrollLeft),
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const isAlreadyAligned =
|
|
230
|
+
Math.abs(this.element.scrollLeft - nextScrollLeft) <=
|
|
231
|
+
Tabs.SCROLL_ALIGNMENT_TOLERANCE;
|
|
232
|
+
if (!forceCenter && isAlreadyAligned) return;
|
|
233
|
+
|
|
234
|
+
if (typeof this.element.scrollTo === "function") {
|
|
235
|
+
this.element.scrollTo({
|
|
236
|
+
left: nextScrollLeft,
|
|
237
|
+
behavior,
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
this.element.scrollLeft = nextScrollLeft;
|
|
243
|
+
this.updateHorizontalOverflowState();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private handleClick(e: MouseEvent): void {
|
|
247
|
+
const clickedTab = e.currentTarget as HTMLElement;
|
|
248
|
+
|
|
249
|
+
if (clickedTab.hasAttribute("aria-disabled")) {
|
|
250
|
+
e.preventDefault();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const clickedIndex = this.tabs.findIndex((tab) => tab === clickedTab);
|
|
255
|
+
if (clickedIndex >= 0) {
|
|
256
|
+
this.setActiveTab(clickedIndex, true);
|
|
257
|
+
}
|
|
133
258
|
}
|
|
134
259
|
|
|
135
260
|
private toggleTab(el: HTMLElement, state: "on" | "off"): void {
|
|
@@ -161,25 +286,27 @@ export default class Tabs {
|
|
|
161
286
|
}
|
|
162
287
|
|
|
163
288
|
activateNthTab(index: number): void {
|
|
164
|
-
this.
|
|
165
|
-
if (i === index) {
|
|
166
|
-
this.activeTabIndex = i;
|
|
167
|
-
return this.toggleTab(tab, "on");
|
|
168
|
-
}
|
|
169
|
-
return this.toggleTab(tab, "off");
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
this.tabPanels.map((tabPanel, i) => {
|
|
173
|
-
if (i === index) {
|
|
174
|
-
return this.toggleTabPanel(tabPanel, "on");
|
|
175
|
-
}
|
|
176
|
-
return this.toggleTabPanel(tabPanel, "off");
|
|
177
|
-
});
|
|
289
|
+
this.setActiveTab(index, true);
|
|
178
290
|
}
|
|
179
291
|
|
|
180
|
-
private handleTabFocus(): void {
|
|
292
|
+
private handleTabFocus(event: FocusEvent): void {
|
|
181
293
|
if (this.rovingTabindex && !this.rovingTabindex.isActive) {
|
|
182
294
|
this.rovingTabindex.init();
|
|
183
295
|
}
|
|
296
|
+
|
|
297
|
+
const focusedTab = event.currentTarget as HTMLElement | null;
|
|
298
|
+
if (focusedTab && this.shouldCenterFocusedTab(focusedTab)) {
|
|
299
|
+
this.scrollTabIntoView(focusedTab);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private shouldCenterFocusedTab(focusedTab: HTMLElement): boolean {
|
|
304
|
+
if (typeof focusedTab.matches !== "function") return true;
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
return focusedTab.matches(":focus-visible");
|
|
308
|
+
} catch {
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
184
311
|
}
|
|
185
312
|
}
|
|
@@ -8,10 +8,6 @@ import { Tab } from "./Tab";
|
|
|
8
8
|
import type { TabPanelProps } from "./TabPanel";
|
|
9
9
|
import TabsStatic from "./Tabs.static";
|
|
10
10
|
|
|
11
|
-
export const variants = ["standard", "light"] as const;
|
|
12
|
-
|
|
13
|
-
export type TabsVariant = (typeof variants)[number];
|
|
14
|
-
|
|
15
11
|
interface TabsProps extends React.HTMLAttributes<HTMLUListElement> {
|
|
16
12
|
/** Active Tab index */
|
|
17
13
|
activeTabIndex?: number;
|
|
@@ -23,8 +19,6 @@ interface TabsProps extends React.HTMLAttributes<HTMLUListElement> {
|
|
|
23
19
|
isFullWidth?: boolean;
|
|
24
20
|
/** Tabs behave as links rather than tabs */
|
|
25
21
|
isLink?: boolean;
|
|
26
|
-
/** Visual variant of the tabs */
|
|
27
|
-
variant?: TabsVariant;
|
|
28
22
|
/** Tab panels as children */
|
|
29
23
|
children: React.ReactElement<TabPanelProps>[];
|
|
30
24
|
}
|
|
@@ -36,7 +30,6 @@ const Tabs: React.FC<TabsProps> = ({
|
|
|
36
30
|
isFullWidth,
|
|
37
31
|
isLink,
|
|
38
32
|
hasEqualTabWidth,
|
|
39
|
-
variant = "standard",
|
|
40
33
|
children,
|
|
41
34
|
...other
|
|
42
35
|
}) => {
|
|
@@ -46,80 +39,82 @@ const Tabs: React.FC<TabsProps> = ({
|
|
|
46
39
|
CLASS_TABNAV,
|
|
47
40
|
{ [`${CLASS_TABNAV}--equal`]: hasEqualTabWidth },
|
|
48
41
|
{ [`${CLASS_TABNAV}--fullwidth`]: isFullWidth },
|
|
49
|
-
{ [`${CLASS_TABNAV}--light`]: variant === "light" },
|
|
50
42
|
classesTabNav,
|
|
51
43
|
className,
|
|
52
44
|
);
|
|
53
45
|
|
|
54
46
|
const [tabListRef] = useStatic(TabsStatic);
|
|
55
47
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const isActive = i === activeTabIndex;
|
|
61
|
-
const props = tabPanel.props || {};
|
|
62
|
-
const {
|
|
63
|
-
renderTab = undefined,
|
|
64
|
-
id,
|
|
65
|
-
isDisabled,
|
|
66
|
-
tab,
|
|
67
|
-
href,
|
|
68
|
-
} = props as TabPanelProps;
|
|
69
|
-
|
|
70
|
-
// Generate ID if not provided
|
|
71
|
-
const tabId = id || `tab-${i}`;
|
|
72
|
-
|
|
73
|
-
if (!renderTab) {
|
|
74
|
-
return (
|
|
75
|
-
<Tab
|
|
76
|
-
key={tabId}
|
|
77
|
-
controls={!href ? tabId : ""}
|
|
78
|
-
href={href}
|
|
79
|
-
isDisabled={isDisabled}
|
|
80
|
-
isActive={isActive}
|
|
81
|
-
>
|
|
82
|
-
{tab}
|
|
83
|
-
</Tab>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
48
|
+
const tabPanels = React.Children.toArray(children).filter(
|
|
49
|
+
(tabPanel): tabPanel is React.ReactElement<TabPanelProps> =>
|
|
50
|
+
React.isValidElement(tabPanel),
|
|
51
|
+
);
|
|
86
52
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
53
|
+
function getTabs() {
|
|
54
|
+
return tabPanels.map((tabPanel, i) => {
|
|
55
|
+
const isActive = i === activeTabIndex;
|
|
56
|
+
const props = tabPanel.props || {};
|
|
57
|
+
const {
|
|
58
|
+
renderTab = undefined,
|
|
59
|
+
id,
|
|
60
|
+
isDisabled,
|
|
61
|
+
tab,
|
|
62
|
+
href,
|
|
63
|
+
} = props as TabPanelProps;
|
|
64
|
+
|
|
65
|
+
// Generate ID if not provided
|
|
66
|
+
const tabId = id || `tab-${i}`;
|
|
67
|
+
|
|
68
|
+
if (!renderTab) {
|
|
69
|
+
return (
|
|
70
|
+
<Tab
|
|
71
|
+
key={tabId}
|
|
72
|
+
controls={!href ? tabId : ""}
|
|
73
|
+
href={href}
|
|
74
|
+
isDisabled={isDisabled}
|
|
75
|
+
isActive={isActive}
|
|
76
|
+
>
|
|
77
|
+
{tab}
|
|
78
|
+
</Tab>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const renderedTab = renderTab(props as TabPanelProps);
|
|
83
|
+
return React.cloneElement(renderedTab, {
|
|
84
|
+
key: tabId,
|
|
85
|
+
isActive,
|
|
86
|
+
} as React.HTMLAttributes<HTMLElement>);
|
|
87
|
+
});
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
function getPanels() {
|
|
96
|
-
return
|
|
97
|
-
|
|
98
|
-
.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
} as Partial<TabPanelProps>);
|
|
108
|
-
});
|
|
91
|
+
return tabPanels.map((tabPanel, i) => {
|
|
92
|
+
const isActive = i === activeTabIndex;
|
|
93
|
+
const props = tabPanel.props || {};
|
|
94
|
+
const { id } = props;
|
|
95
|
+
const tabId = id || `tab-${i}`;
|
|
96
|
+
return React.cloneElement(tabPanel, {
|
|
97
|
+
key: tabId,
|
|
98
|
+
isActive,
|
|
99
|
+
tab: undefined,
|
|
100
|
+
} as Partial<TabPanelProps>);
|
|
101
|
+
});
|
|
109
102
|
}
|
|
110
103
|
|
|
111
104
|
return (
|
|
112
105
|
<>
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
106
|
+
<div className="tab-list__viewport">
|
|
107
|
+
<ul
|
|
108
|
+
className={tabNavClasses}
|
|
109
|
+
role={isLink ? "navigation" : "tablist"}
|
|
110
|
+
ref={tabListRef}
|
|
111
|
+
data-tabs={isLink ? undefined : "true"}
|
|
112
|
+
{...other}
|
|
113
|
+
>
|
|
114
|
+
{getTabs()}
|
|
115
|
+
{isFullWidth && <li role="presentation" className="tab-list__hr" />}
|
|
116
|
+
</ul>
|
|
117
|
+
</div>
|
|
123
118
|
{!isLink ? getPanels() : null}
|
|
124
119
|
</>
|
|
125
120
|
);
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
@use "../../../styles/tokens/base";
|
|
1
2
|
@use "../../../styles/tokens/space";
|
|
2
3
|
@use "../../../styles/tools/convert";
|
|
3
4
|
|
|
4
5
|
$outline-width: convert.to-rem(2px);
|
|
5
|
-
$
|
|
6
|
+
$focusring-space: convert.to-rem(
|
|
7
|
+
base.$focusring-width + base.$focusring-offset
|
|
8
|
+
);
|
|
9
|
+
$negative-outline-width: $focusring-space * -1;
|
|
6
10
|
|
|
7
11
|
$tablist-spacing: (
|
|
8
12
|
margin: $negative-outline-width $negative-outline-width
|
|
@@ -10,50 +14,39 @@ $tablist-spacing: (
|
|
|
10
14
|
space.get() + $negative-outline-width,
|
|
11
15
|
)
|
|
12
16
|
$negative-outline-width,
|
|
13
|
-
padding: $
|
|
17
|
+
padding: $focusring-space,
|
|
14
18
|
);
|
|
15
19
|
|
|
16
20
|
$tab: (
|
|
17
21
|
default: (
|
|
18
22
|
color: var(--color-text-default),
|
|
19
|
-
|
|
23
|
+
background-color: transparent,
|
|
24
|
+
box-shadow: inset 0 -1px 0 0 var(--color-surface-moderate),
|
|
20
25
|
),
|
|
21
26
|
active: (
|
|
22
|
-
color: var(--color-text-
|
|
27
|
+
color: var(--color-text-default),
|
|
28
|
+
background-color: var(--color-fill-moderate),
|
|
23
29
|
box-shadow: (
|
|
24
|
-
inset
|
|
25
|
-
inset 0
|
|
26
|
-
inset 2px 0 0 0 var(--color-border-contrast),
|
|
30
|
+
inset 0 4px 0 0 var(--color-border-accent),
|
|
31
|
+
inset 0 -1px 0 0 var(--color-fill-moderate),
|
|
27
32
|
),
|
|
28
33
|
),
|
|
29
34
|
hover: (
|
|
30
35
|
color: var(--color-text-accent),
|
|
36
|
+
background-color: transparent,
|
|
37
|
+
box-shadow: inset 0 -1px 0 0 var(--color-surface-moderate),
|
|
31
38
|
),
|
|
32
39
|
disabled: (
|
|
33
|
-
color: var(--color-
|
|
34
|
-
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
$tab-light: (
|
|
38
|
-
default: (
|
|
39
|
-
box-shadow: none "!important",
|
|
40
|
-
),
|
|
41
|
-
active: (
|
|
42
|
-
box-shadow: inset 0 -4px 0 0 var(--color-border-accent) "!important",
|
|
43
|
-
),
|
|
44
|
-
hover: (
|
|
45
|
-
box-shadow: none "!important",
|
|
46
|
-
),
|
|
47
|
-
disabled: (
|
|
48
|
-
box-shadow: none "!important",
|
|
40
|
+
color: var(--color-text-disabled),
|
|
41
|
+
background-color: transparent,
|
|
49
42
|
),
|
|
50
43
|
);
|
|
51
44
|
|
|
52
45
|
$tab-sizes: (
|
|
53
46
|
default: (
|
|
54
|
-
padding:
|
|
47
|
+
padding: convert.to-rem(15px) convert.to-rem(20px),
|
|
55
48
|
),
|
|
56
49
|
large: (
|
|
57
|
-
padding:
|
|
50
|
+
padding: convert.to-rem(20px),
|
|
58
51
|
),
|
|
59
52
|
);
|
|
@@ -1,10 +1,61 @@
|
|
|
1
1
|
@use "./config";
|
|
2
2
|
@use "../../../styles/tokens/base";
|
|
3
3
|
@use "../../../styles/tokens/space";
|
|
4
|
+
@use "../../../styles/tools/convert";
|
|
4
5
|
@use "../../../styles/tools/generate";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
$tab-focus-radius: convert.to-rem(5px);
|
|
8
|
+
$tab-focusring-space: convert.to-rem(
|
|
9
|
+
base.$focusring-width + base.$focusring-offset
|
|
10
|
+
);
|
|
11
|
+
$tab-overflow-fade-width: convert.to-rem(72px);
|
|
12
|
+
|
|
13
|
+
@mixin list-viewport {
|
|
7
14
|
position: relative;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
padding: $tab-focusring-space $tab-focusring-space 0;
|
|
17
|
+
|
|
18
|
+
&::before,
|
|
19
|
+
&::after {
|
|
20
|
+
z-index: 2;
|
|
21
|
+
width: $tab-overflow-fade-width;
|
|
22
|
+
content: "";
|
|
23
|
+
position: absolute;
|
|
24
|
+
top: 0;
|
|
25
|
+
height: 100%;
|
|
26
|
+
pointer-events: none;
|
|
27
|
+
opacity: 0;
|
|
28
|
+
transition: opacity 0.2s ease;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&::before {
|
|
32
|
+
left: 0;
|
|
33
|
+
background: linear-gradient(
|
|
34
|
+
to left,
|
|
35
|
+
transparent 0%,
|
|
36
|
+
var(--color-background-primary) 100%
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&::after {
|
|
41
|
+
right: 0;
|
|
42
|
+
background: linear-gradient(
|
|
43
|
+
to right,
|
|
44
|
+
transparent 0%,
|
|
45
|
+
var(--color-background-primary) 100%
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&.has-left-overflow::before {
|
|
50
|
+
opacity: 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&.has-right-overflow::after {
|
|
54
|
+
opacity: 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@mixin list-base($spacing: config.$tablist-spacing) {
|
|
8
59
|
display: flex;
|
|
9
60
|
overflow-x: auto;
|
|
10
61
|
overflow-y: visible;
|
|
@@ -16,6 +67,10 @@
|
|
|
16
67
|
|
|
17
68
|
@mixin list-equal {
|
|
18
69
|
flex-grow: 1;
|
|
70
|
+
|
|
71
|
+
.tab-list__tab {
|
|
72
|
+
text-align: center;
|
|
73
|
+
}
|
|
19
74
|
}
|
|
20
75
|
|
|
21
76
|
@mixin list-item {
|
|
@@ -28,54 +83,68 @@
|
|
|
28
83
|
flex: 1 1 auto;
|
|
29
84
|
display: flex;
|
|
30
85
|
flex-flow: column;
|
|
86
|
+
justify-content: center;
|
|
31
87
|
height: initial;
|
|
32
88
|
position: relative;
|
|
33
|
-
font-weight:
|
|
89
|
+
font-weight: 700;
|
|
34
90
|
background: transparent;
|
|
35
91
|
border-width: 0;
|
|
36
92
|
text-align: left;
|
|
93
|
+
white-space: nowrap;
|
|
94
|
+
cursor: pointer;
|
|
95
|
+
|
|
96
|
+
&::after {
|
|
97
|
+
content: "";
|
|
98
|
+
position: absolute;
|
|
99
|
+
inset: $tab-focusring-space * -1;
|
|
100
|
+
border: base.$focus-outline;
|
|
101
|
+
border-radius: calc(#{$tab-focus-radius} + #{$tab-focusring-space});
|
|
102
|
+
opacity: 0;
|
|
103
|
+
pointer-events: none;
|
|
104
|
+
}
|
|
37
105
|
|
|
38
106
|
&:focus {
|
|
39
107
|
z-index: 1;
|
|
108
|
+
outline-width: 0;
|
|
109
|
+
outline-color: transparent;
|
|
40
110
|
}
|
|
41
111
|
|
|
42
|
-
|
|
43
|
-
|
|
112
|
+
&:focus::after {
|
|
113
|
+
opacity: 1;
|
|
114
|
+
}
|
|
44
115
|
|
|
45
|
-
|
|
46
|
-
|
|
116
|
+
&:focus-visible {
|
|
117
|
+
outline-width: 0;
|
|
118
|
+
outline-color: transparent;
|
|
119
|
+
}
|
|
47
120
|
|
|
48
|
-
|
|
49
|
-
|
|
121
|
+
&:focus-visible::after {
|
|
122
|
+
opacity: 1;
|
|
50
123
|
}
|
|
51
124
|
|
|
52
|
-
&:
|
|
53
|
-
|
|
125
|
+
&:focus:not(:focus-visible)::after {
|
|
126
|
+
opacity: 0;
|
|
54
127
|
}
|
|
55
128
|
|
|
56
|
-
&[aria-
|
|
57
|
-
|
|
129
|
+
&[aria-selected="true"] {
|
|
130
|
+
z-index: 1;
|
|
58
131
|
}
|
|
59
132
|
}
|
|
60
133
|
|
|
61
|
-
@mixin
|
|
62
|
-
@include generate.css-map(config
|
|
63
|
-
|
|
64
|
-
&:hover {
|
|
65
|
-
@include generate.css-map(config.$tab-light, "hover");
|
|
66
|
-
}
|
|
134
|
+
@mixin color-variant($config: config.$tab) {
|
|
135
|
+
@include generate.css-map($config, "default");
|
|
67
136
|
|
|
68
137
|
&[aria-selected="true"] {
|
|
69
|
-
@include generate.css-map(config
|
|
138
|
+
@include generate.css-map($config, "active");
|
|
70
139
|
}
|
|
71
140
|
|
|
72
|
-
|
|
73
|
-
@include generate.css-map(config.$tab
|
|
141
|
+
&:hover:not([aria-selected="true"]):not([aria-disabled="true"]) {
|
|
142
|
+
@include generate.css-map(config.$tab, "hover");
|
|
74
143
|
}
|
|
75
|
-
}
|
|
76
144
|
|
|
77
|
-
|
|
78
|
-
|
|
145
|
+
&[aria-disabled="true"]:not([aria-selected="true"]) {
|
|
146
|
+
@include generate.css-map(config.$tab, "disabled");
|
|
147
|
+
}
|
|
79
148
|
}
|
|
80
149
|
|
|
81
150
|
@mixin tab-size($size, $config: config.$tab-sizes) {
|
|
@@ -85,10 +154,6 @@
|
|
|
85
154
|
@mixin underline {
|
|
86
155
|
flex: 1 1 auto;
|
|
87
156
|
max-width: none;
|
|
88
|
-
box-shadow: inset 0 -2px 0 0 var(--color-border-contrast);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
@mixin underline-light {
|
|
92
157
|
box-shadow: inset 0 -1px 0 0 var(--color-surface-moderate);
|
|
93
158
|
}
|
|
94
159
|
|