@orangesk/orange-design-system 2.0.0-beta.46 → 2.0.0-beta.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/build/components/Breadcrumbs/style.css +1 -1
  2. package/build/components/Breadcrumbs/style.css.map +1 -1
  3. package/build/components/Carousel/style.css +1 -1
  4. package/build/components/Carousel/style.css.map +1 -1
  5. package/build/components/Footer/style.css +1 -1
  6. package/build/components/Footer/style.css.map +1 -1
  7. package/build/components/Link/style.css +1 -1
  8. package/build/components/Link/style.css.map +1 -1
  9. package/build/components/Megamenu/style.css +1 -1
  10. package/build/components/Megamenu/style.css.map +1 -1
  11. package/build/components/Stepbar/style.css +1 -1
  12. package/build/components/Stepbar/style.css.map +1 -1
  13. package/build/components/Tabs/style.css +1 -1
  14. package/build/components/Tabs/style.css.map +1 -1
  15. package/build/components/index.js +1 -1
  16. package/build/components/index.js.map +1 -1
  17. package/build/components/tsconfig.tsbuildinfo +1 -1
  18. package/build/components/types/index.d.ts +0 -4
  19. package/build/components/types/src/components/Preview/CodeExample.d.ts +1 -0
  20. package/build/components/types/src/components/Preview/PreviewGenerator.d.ts +1 -0
  21. package/build/components/types/src/components/Preview/getElementDisplayName.d.ts +1 -0
  22. package/build/components/types/src/components/Tabs/Tabs.d.ts +0 -4
  23. package/build/components/types/src/components/Tabs/Tabs.static.d.ts +12 -0
  24. package/build/lib/base.css +1 -1
  25. package/build/lib/base.css.map +1 -1
  26. package/build/lib/components.css +1 -1
  27. package/build/lib/components.css.map +1 -1
  28. package/build/lib/footer.css +1 -1
  29. package/build/lib/footer.css.map +1 -1
  30. package/build/lib/megamenu.css +1 -1
  31. package/build/lib/megamenu.css.map +1 -1
  32. package/build/lib/scripts.js +1 -1
  33. package/build/lib/scripts.js.map +1 -1
  34. package/build/lib/style.css +1 -1
  35. package/build/lib/style.css.map +1 -1
  36. package/build/lib/utilities.css +1 -1
  37. package/build/lib/utilities.css.map +1 -1
  38. package/build/search-index.json +2 -2
  39. package/package.json +12 -12
  40. package/src/components/Breadcrumbs/styles/mixins.scss +15 -8
  41. package/src/components/Carousel/styles/mixins.scss +22 -2
  42. package/src/components/Footer/styles/mixins.scss +2 -1
  43. package/src/components/Forms/Checkbox/styles/style.scss +13 -6
  44. package/src/components/Forms/InputStepper/styles/style.scss +15 -8
  45. package/src/components/Link/styles/mixins.scss +0 -1
  46. package/src/components/Megamenu/Megamenu.tsx +2 -2
  47. package/src/components/Megamenu/MegamenuBlog.tsx +2 -2
  48. package/src/components/Megamenu/styles/mixins.scss +20 -12
  49. package/src/components/Preview/CodeExample.tsx +66 -25
  50. package/src/components/Preview/Preview.tsx +26 -13
  51. package/src/components/Preview/PreviewGenerator.tsx +57 -32
  52. package/src/components/Preview/getElementDisplayName.ts +25 -0
  53. package/src/components/Stepbar/styles/config.scss +34 -17
  54. package/src/components/Stepbar/styles/mixins.scss +5 -3
  55. package/src/components/Tabs/Tabs.static.ts +157 -30
  56. package/src/components/Tabs/Tabs.tsx +62 -67
  57. package/src/components/Tabs/styles/config.scss +18 -25
  58. package/src/components/Tabs/styles/mixins.scss +93 -28
  59. package/src/components/Tabs/styles/style.scss +4 -15
  60. package/src/components/Tabs/tests/Tabs.unit.test.jsx +111 -0
  61. package/src/styles/base/globals.scss +2 -0
  62. package/src/styles/utilities/horizontal-scroll.scss +7 -2
  63. package/src/styles/utilities/text.scss +0 -1
@@ -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.toggleTabPanel(this.tabPanels[this.activeTabIndex], "on");
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 handleClick(e: MouseEvent): void {
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 (tab === clickedTab) {
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.tabs.map((tab, i) => {
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
- function getTabs() {
57
- return children
58
- .filter((tabPanel) => tabPanel && React.isValidElement(tabPanel))
59
- .map((tabPanel, i) => {
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
- const renderedTab = renderTab(props as TabPanelProps);
88
- return React.cloneElement(renderedTab, {
89
- key: tabId,
90
- isActive,
91
- } as React.HTMLAttributes<HTMLElement>);
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 children
97
- .filter((tabPanel) => tabPanel && React.isValidElement(tabPanel))
98
- .map((tabPanel, i) => {
99
- const isActive = i === activeTabIndex;
100
- const props = tabPanel.props || {};
101
- const { id } = props;
102
- const tabId = id || `tab-${i}`;
103
- return React.cloneElement(tabPanel, {
104
- key: tabId,
105
- isActive,
106
- tab: undefined,
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
- <ul
114
- className={tabNavClasses}
115
- role={isLink ? "navigation" : "tablist"}
116
- ref={tabListRef}
117
- data-tabs={isLink ? undefined : "true"}
118
- {...other}
119
- >
120
- {getTabs()}
121
- {isFullWidth && <li role="presentation" className="tab-list__hr" />}
122
- </ul>
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
- $negative-outline-width: $outline-width * -1;
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: $outline-width,
17
+ padding: $focusring-space,
14
18
  );
15
19
 
16
20
  $tab: (
17
21
  default: (
18
22
  color: var(--color-text-default),
19
- box-shadow: inset 0 -2px 0 0 var(--color-border-contrast),
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-accent),
27
+ color: var(--color-text-default),
28
+ background-color: var(--color-fill-moderate),
23
29
  box-shadow: (
24
- inset -2px 0 0 0 var(--color-border-contrast),
25
- inset 0 2px 0 0 var(--color-border-contrast),
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-surface-moderate),
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: space.get("small") space.get(),
47
+ padding: convert.to-rem(15px) convert.to-rem(20px),
55
48
  ),
56
49
  large: (
57
- padding: space.get("small") space.get(),
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
- @mixin list-base($spacing: config.$tablist-spacing) {
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: 600;
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
- @include base.focusring-flush;
43
- }
112
+ &:focus::after {
113
+ opacity: 1;
114
+ }
44
115
 
45
- @mixin color-variant($config: config.$tab) {
46
- @include generate.css-map($config, "default");
116
+ &:focus-visible {
117
+ outline-width: 0;
118
+ outline-color: transparent;
119
+ }
47
120
 
48
- &[aria-selected="true"] {
49
- @include generate.css-map($config, "active");
121
+ &:focus-visible::after {
122
+ opacity: 1;
50
123
  }
51
124
 
52
- &:hover {
53
- @include generate.css-map(config.$tab, "hover");
125
+ &:focus:not(:focus-visible)::after {
126
+ opacity: 0;
54
127
  }
55
128
 
56
- &[aria-disabled="true"] {
57
- @include generate.css-map(config.$tab, "disabled");
129
+ &[aria-selected="true"] {
130
+ z-index: 1;
58
131
  }
59
132
  }
60
133
 
61
- @mixin tab-light {
62
- @include generate.css-map(config.$tab-light, "default");
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.$tab-light, "active");
138
+ @include generate.css-map($config, "active");
70
139
  }
71
140
 
72
- &[aria-disabled="true"] {
73
- @include generate.css-map(config.$tab-light, "disabled");
141
+ &:hover:not([aria-selected="true"]):not([aria-disabled="true"]) {
142
+ @include generate.css-map(config.$tab, "hover");
74
143
  }
75
- }
76
144
 
77
- @mixin tab-centered {
78
- align-items: center;
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