@orangesk/orange-design-system 3.6.2 → 3.8.0

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 (100) hide show
  1. package/build/components/Alert/style.css +1 -1
  2. package/build/components/Alert/style.css.map +1 -1
  3. package/build/components/Bar/style.css +1 -1
  4. package/build/components/Bar/style.css.map +1 -1
  5. package/build/components/Drawer/style.css +2 -0
  6. package/build/components/Drawer/style.css.map +1 -0
  7. package/build/components/Grid/style.css.map +1 -1
  8. package/build/components/Megamenu/style.css +1 -1
  9. package/build/components/Megamenu/style.css.map +1 -1
  10. package/build/components/Modal/style.css +1 -1
  11. package/build/components/Modal/style.css.map +1 -1
  12. package/build/components/StatusIndicator/style.css +2 -0
  13. package/build/components/StatusIndicator/style.css.map +1 -0
  14. package/build/components/Table/style.css +1 -1
  15. package/build/components/Table/style.css.map +1 -1
  16. package/build/components/index.js +1 -1
  17. package/build/components/index.js.map +1 -1
  18. package/build/components/types/index.d.ts +89 -3
  19. package/build/lib/base.css +1 -1
  20. package/build/lib/base.css.map +1 -1
  21. package/build/lib/components.css +1 -1
  22. package/build/lib/components.css.map +1 -1
  23. package/build/lib/footer.js +1 -1
  24. package/build/lib/footer.js.map +1 -1
  25. package/build/lib/megamenu.css +1 -1
  26. package/build/lib/megamenu.css.map +1 -1
  27. package/build/lib/megamenu.js +1 -1
  28. package/build/lib/megamenu.js.map +1 -1
  29. package/build/lib/scripts.js +1 -1
  30. package/build/lib/scripts.js.map +1 -1
  31. package/build/lib/style.css +1 -1
  32. package/build/lib/style.css.map +1 -1
  33. package/build/lib/utilities.css +1 -1
  34. package/build/lib/utilities.css.map +1 -1
  35. package/package.json +29 -29
  36. package/src/components/Alert/Alert.tsx +2 -2
  37. package/src/components/Alert/data.ts +8 -5
  38. package/src/components/Alert/styles/config.scss +4 -4
  39. package/src/components/Alert/styles/mixins.scss +8 -1
  40. package/src/components/Alert/tests/Alert.test.jsx +11 -1
  41. package/src/components/Bar/Bar.tsx +1 -1
  42. package/src/components/Bar/styles/config.scss +1 -0
  43. package/src/components/Bar/tests/Bar.unit.test.jsx +1 -1
  44. package/src/components/Carousel/Carousel.static.ts +103 -12
  45. package/src/components/Carousel/tests/Carousel.static.test.jsx +134 -0
  46. package/src/components/DocumentationSidebar/DocumentationSidebar.tsx +12 -4
  47. package/src/components/Drawer/Drawer.static.ts +281 -0
  48. package/src/components/Drawer/Drawer.tsx +82 -0
  49. package/src/components/Drawer/DrawerBody.tsx +21 -0
  50. package/src/components/Drawer/DrawerFooter.tsx +21 -0
  51. package/src/components/Drawer/DrawerHeader.tsx +21 -0
  52. package/src/components/Drawer/index.ts +5 -0
  53. package/src/components/Drawer/styles/config.scss +17 -0
  54. package/src/components/Drawer/styles/mixins.scss +119 -0
  55. package/src/components/Drawer/styles/style.scss +71 -0
  56. package/src/components/Drawer/tests/Drawer.unit.test.jsx +149 -0
  57. package/src/components/Footer/data.ts +4 -0
  58. package/src/components/Forms/SegmentedControl/SegmentedControl.tsx +122 -0
  59. package/src/components/Forms/SegmentedControl/index.ts +5 -0
  60. package/src/components/Forms/SegmentedControl/styles/mixins.scss +52 -0
  61. package/src/components/Forms/SegmentedControl/styles/style.scss +35 -0
  62. package/src/components/Forms/SegmentedControl/tests/SegmentedControl.conformance.test.jsx +36 -0
  63. package/src/components/Forms/SegmentedControl/tests/SegmentedControl.unit.test.jsx +125 -0
  64. package/src/components/Forms/SelectionTile/PLAN.md +206 -0
  65. package/src/components/Forms/SelectionTile/SelectionTile.tsx +145 -0
  66. package/src/components/Forms/SelectionTile/index.ts +5 -0
  67. package/src/components/Forms/SelectionTile/styles/mixins.scss +83 -0
  68. package/src/components/Forms/SelectionTile/styles/style.scss +58 -0
  69. package/src/components/Forms/SelectionTile/tests/SelectionTile.conformance.test.jsx +32 -0
  70. package/src/components/Forms/SelectionTile/tests/SelectionTile.unit.test.jsx +146 -0
  71. package/src/components/Forms/index.ts +2 -0
  72. package/src/components/Icon/index.ts +1 -0
  73. package/src/components/Megamenu/Megamenu.tsx +20 -16
  74. package/src/components/Megamenu/MegamenuBlog.tsx +10 -7
  75. package/src/components/Megamenu/MyOrangeMobilePanel.tsx +2 -3
  76. package/src/components/Megamenu/constants.ts +3 -1
  77. package/src/components/Megamenu/styles/mixins.scss +53 -18
  78. package/src/components/Megamenu/styles/style.scss +12 -4
  79. package/src/components/Megamenu/tests/Megamenu.unit.test.jsx +32 -0
  80. package/src/components/Modal/styles/config.scss +3 -2
  81. package/src/components/StatusIndicator/StatusIndicator.tsx +29 -0
  82. package/src/components/StatusIndicator/index.ts +2 -0
  83. package/src/components/StatusIndicator/styles/config.scss +12 -0
  84. package/src/components/StatusIndicator/styles/mixins.scss +36 -0
  85. package/src/components/StatusIndicator/styles/style.scss +16 -0
  86. package/src/components/StatusIndicator/tests/StatusIndicator.conformance.test.jsx +22 -0
  87. package/src/components/StatusIndicator/tests/StatusIndicator.unit.test.jsx +70 -0
  88. package/src/components/Table/Table.static.ts +23 -10
  89. package/src/components/Table/Table.tsx +7 -2
  90. package/src/components/Table/styles/mixins.scss +59 -0
  91. package/src/components/Table/styles/style.scss +25 -0
  92. package/src/components/Table/tests/Table.static.unit.test.jsx +72 -0
  93. package/src/components/Table/tests/Table.unit.test.jsx +33 -0
  94. package/src/components/index.ts +13 -0
  95. package/src/styles/tokens/overlay.scss +3 -0
  96. package/src/styles/typography/style.scss +0 -13
  97. package/src/styles/utilities/border.scss +4 -0
  98. package/src/styles/utilities/border.styles.unit.test.jsx +9 -0
  99. package/src/styles/utilities/text.scss +12 -0
  100. package/src/components/Carousel/CHANGELOG.md +0 -11
@@ -4,6 +4,38 @@ import "@testing-library/jest-dom/vitest";
4
4
  import { Megamenu, MegamenuBlog } from "../";
5
5
 
6
6
  describe("Megamenu logo markup", () => {
7
+ it("separates top links from the language switcher without a spacer item", () => {
8
+ const { container } = render(<Megamenu notSticky />);
9
+ const topContent = container.querySelector(".megamenu__top-content");
10
+ const lists = topContent?.querySelectorAll(":scope > ul");
11
+
12
+ expect(lists).toHaveLength(2);
13
+ expect(lists?.[1]).toHaveAttribute("aria-label", "Výber jazyka");
14
+ expect(
15
+ topContent?.querySelector(".megamenu__nav-spacer"),
16
+ ).not.toBeInTheDocument();
17
+ expect(
18
+ topContent?.querySelector(".megamenu__nav-item--push"),
19
+ ).toHaveTextContent("Velké firmy");
20
+ });
21
+
22
+ it("pushes mobile utility controls after the main logo", () => {
23
+ const { container, rerender } = render(<Megamenu notSticky />);
24
+ const mainLogoItem = container.querySelector(
25
+ ".megamenu__main .megamenu__nav-item--main:first-child",
26
+ );
27
+
28
+ expect(mainLogoItem).toHaveClass("megamenu__nav-item--push");
29
+
30
+ rerender(<MegamenuBlog notSticky />);
31
+
32
+ expect(
33
+ container.querySelector(
34
+ ".megamenu__main .megamenu__nav-item--main:first-child",
35
+ ),
36
+ ).toHaveClass("megamenu__nav-item--push");
37
+ });
38
+
7
39
  it("is sticky by default and supports disabling stickiness", () => {
8
40
  const { container, rerender } = render(<Megamenu />);
9
41
  const nav = container.querySelector("[data-megamenu]");
@@ -1,10 +1,11 @@
1
1
  @use "../../../styles/tokens/space";
2
2
  @use "../../../styles/tokens/color";
3
+ @use "../../../styles/tokens/overlay";
3
4
  @use "../../../styles/tools/convert";
4
5
 
5
- $overlay-bg-color: rgba(0, 0, 0, 0.4) !default;
6
+ $overlay-bg-color: overlay.$background !default;
6
7
  $overlay-z-index: 500 !default;
7
- $overlay-e: 0.4s ease 0s !default;
8
+ $overlay-e: overlay.$transition !default;
8
9
 
9
10
  $overlay: (
10
11
  background-color: $overlay-bg-color,
@@ -0,0 +1,29 @@
1
+ import cx from "classnames";
2
+ import type { HTMLAttributes, ReactNode } from "react";
3
+ import React from "react";
4
+
5
+ export interface StatusIndicatorProps extends HTMLAttributes<HTMLSpanElement> {
6
+ children: ReactNode;
7
+ type: "success" | "warning" | "danger";
8
+ }
9
+
10
+ const CLASS_ROOT = "status-indicator";
11
+
12
+ const StatusIndicator: React.FC<StatusIndicatorProps> = ({
13
+ children,
14
+ className,
15
+ type,
16
+ ...other
17
+ }) => (
18
+ <span
19
+ className={cx(CLASS_ROOT, `${CLASS_ROOT}--${type}`, className)}
20
+ {...other}
21
+ >
22
+ <span className={`${CLASS_ROOT}__marker`} aria-hidden="true" />
23
+ <span className={`${CLASS_ROOT}__content`}>{children}</span>
24
+ </span>
25
+ );
26
+
27
+ StatusIndicator.displayName = "StatusIndicator";
28
+
29
+ export { StatusIndicator };
@@ -0,0 +1,2 @@
1
+ export type { StatusIndicatorProps } from "./StatusIndicator";
2
+ export { StatusIndicator } from "./StatusIndicator";
@@ -0,0 +1,12 @@
1
+ @use "../../../styles/tokens/space";
2
+ @use "../../../styles/tools/convert";
3
+
4
+ $base: (
5
+ column-gap: space.get("xsmall"),
6
+ );
7
+
8
+ $marker: (
9
+ width: convert.to-rem(12px),
10
+ height: convert.to-rem(12px),
11
+ margin-top: convert.to-rem(3px),
12
+ );
@@ -0,0 +1,36 @@
1
+ @use "../../../styles/tools/generate";
2
+ @use "../../../styles/typography/config" as typography;
3
+ @use "./config";
4
+
5
+ @mixin base($config: config.$base) {
6
+ display: inline-flex;
7
+ align-items: flex-start;
8
+
9
+ @include generate.css-map($config);
10
+ @include generate.css-map(typography.$caption, "default");
11
+ }
12
+
13
+ @mixin marker($config: config.$marker) {
14
+ flex: 0 0 auto;
15
+ border-radius: 50%;
16
+
17
+ @include generate.css-map($config);
18
+ }
19
+
20
+ @mixin content() {
21
+ min-width: 0;
22
+ }
23
+
24
+ @mixin marker-types() {
25
+ .status-indicator--success & {
26
+ background-color: var(--color-icon-positive);
27
+ }
28
+
29
+ .status-indicator--warning & {
30
+ background-color: var(--color-icon-warning);
31
+ }
32
+
33
+ .status-indicator--danger & {
34
+ background-color: var(--color-icon-negative);
35
+ }
36
+ }
@@ -0,0 +1,16 @@
1
+ @use "./mixins";
2
+
3
+ @layer components {
4
+ .status-indicator {
5
+ @include mixins.base();
6
+
7
+ &__marker {
8
+ @include mixins.marker();
9
+ @include mixins.marker-types();
10
+ }
11
+
12
+ &__content {
13
+ @include mixins.content();
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,22 @@
1
+ import { render } from "@testing-library/react";
2
+ import { axe } from "vitest-axe";
3
+
4
+ import { StatusIndicator } from "../";
5
+
6
+ const example = (
7
+ <>
8
+ <StatusIndicator type="success">Available</StatusIndicator>
9
+ <StatusIndicator type="warning">Limited availability</StatusIndicator>
10
+ <StatusIndicator type="danger">Unavailable</StatusIndicator>
11
+ </>
12
+ );
13
+
14
+ it("is valid html", () => {
15
+ const { container } = render(example);
16
+ expect(container).toHTMLValidate();
17
+ });
18
+
19
+ it("is accessible", async () => {
20
+ const { container } = render(example);
21
+ expect(await axe(container)).toHaveNoViolations();
22
+ });
@@ -0,0 +1,70 @@
1
+ import { render } from "@testing-library/react";
2
+
3
+ import * as components from "../../";
4
+
5
+ describe("StatusIndicator", () => {
6
+ it("is available from the public component entry point", () => {
7
+ expect(components.StatusIndicator).toBeTypeOf("function");
8
+ });
9
+
10
+ it.each([
11
+ ["success", "status-indicator--success"],
12
+ ["warning", "status-indicator--warning"],
13
+ ["danger", "status-indicator--danger"],
14
+ ])("renders the %s type", (type, expectedClass) => {
15
+ const { getByText } = render(
16
+ <components.StatusIndicator type={type}>
17
+ {type}
18
+ </components.StatusIndicator>,
19
+ );
20
+
21
+ expect(getByText(type).closest(".status-indicator")).toHaveClass(
22
+ "status-indicator",
23
+ expectedClass,
24
+ );
25
+ });
26
+
27
+ it("renders a decorative marker before the label", () => {
28
+ const { getByTestId } = render(
29
+ <components.StatusIndicator data-testid="status" type="success">
30
+ Available
31
+ </components.StatusIndicator>,
32
+ );
33
+ const statusIndicator = getByTestId("status");
34
+ const marker = statusIndicator.firstElementChild;
35
+
36
+ expect(marker).toHaveClass("status-indicator__marker");
37
+ expect(marker).toHaveAttribute("aria-hidden", "true");
38
+ });
39
+
40
+ it("groups text and inline controls in one wrapping content item", () => {
41
+ const { getByTestId, getByRole } = render(
42
+ <components.StatusIndicator data-testid="status" type="danger">
43
+ Temporarily unavailable <button type="button">More information</button>
44
+ </components.StatusIndicator>,
45
+ );
46
+ const content = getByTestId("status").querySelector(
47
+ ".status-indicator__content",
48
+ );
49
+
50
+ expect(content).toHaveTextContent("Temporarily unavailable");
51
+ expect(content).toContainElement(
52
+ getByRole("button", { name: "More information" }),
53
+ );
54
+ });
55
+
56
+ it("passes native span props and additional classes", () => {
57
+ const { getByTestId } = render(
58
+ <components.StatusIndicator
59
+ className="custom-class"
60
+ data-testid="status"
61
+ type="warning"
62
+ >
63
+ Limited
64
+ </components.StatusIndicator>,
65
+ );
66
+
67
+ expect(getByTestId("status")).toHaveClass("custom-class");
68
+ expect(getByTestId("status")).toHaveTextContent("Limited");
69
+ });
70
+ });
@@ -1,5 +1,7 @@
1
1
  import toggle from "../../scripts/modules/toggleUtil";
2
2
 
3
+ const SCROLLABLE_OVERFLOW_CLASS = "table-scrollable--overflowing";
4
+
3
5
  interface TableConfig {
4
6
  expandButtonSelector: string;
5
7
  scrollableContainerSelector: string;
@@ -32,18 +34,26 @@ export default class Table {
32
34
 
33
35
  (this.element as any).ODS_Table = this;
34
36
 
35
- this.resizeObserver = new ResizeObserver((entries) => {
37
+ this.resizeObserver = new ResizeObserver(() => {
36
38
  if (!this.scrollableContainer) return;
37
39
 
38
- const containerRect = this.scrollableContainer.getBoundingClientRect();
39
-
40
- entries.forEach((entry) => {
41
- if (entry.contentRect.width > containerRect.width) {
42
- this.scrollableContainer?.setAttribute("tabindex", "0");
43
- } else {
44
- this.scrollableContainer?.removeAttribute("tabindex");
45
- }
46
- });
40
+ const overflowX = getComputedStyle(this.scrollableContainer).overflowX;
41
+ const canScroll = overflowX === "auto" || overflowX === "scroll";
42
+ const hasOverflow =
43
+ this.scrollableContainer.scrollWidth >
44
+ this.scrollableContainer.clientWidth;
45
+ const isScrollable = canScroll && hasOverflow;
46
+
47
+ this.scrollableContainer.classList.toggle(
48
+ SCROLLABLE_OVERFLOW_CLASS,
49
+ isScrollable,
50
+ );
51
+
52
+ if (isScrollable) {
53
+ this.scrollableContainer.setAttribute("tabindex", "0");
54
+ } else {
55
+ this.scrollableContainer.removeAttribute("tabindex");
56
+ }
47
57
  });
48
58
 
49
59
  this.init();
@@ -120,6 +130,7 @@ export default class Table {
120
130
  );
121
131
  if (this.scrollableContainer) {
122
132
  this.resizeObserver.observe(this.element);
133
+ this.resizeObserver.observe(this.scrollableContainer);
123
134
  return true;
124
135
  }
125
136
  return undefined;
@@ -131,6 +142,8 @@ export default class Table {
131
142
  });
132
143
 
133
144
  this.resizeObserver.disconnect();
145
+ this.scrollableContainer?.classList.remove(SCROLLABLE_OVERFLOW_CLASS);
146
+ this.scrollableContainer?.removeAttribute("tabindex");
134
147
 
135
148
  this.expandButtons = [];
136
149
  }
@@ -26,6 +26,8 @@ interface TableProps extends React.TableHTMLAttributes<HTMLTableElement> {
26
26
  isResponsive?: boolean;
27
27
  /** If table has horizontal scrollbar */
28
28
  isScrollable?: boolean;
29
+ /** Keep the first column visible while the table scrolls horizontally */
30
+ isFirstColumnPinned?: boolean;
29
31
  /** Different colors for even and odd rows. */
30
32
  isStriped?: boolean;
31
33
  /** rows to print out in the table. */
@@ -45,6 +47,7 @@ const Table: React.FC<TableProps> = ({
45
47
  isResponsive,
46
48
  isStriped,
47
49
  isScrollable,
50
+ isFirstColumnPinned,
48
51
  expandableRowsCaptions = {
49
52
  header: "Rozšíriteľný",
50
53
  emptyRow: "Nerozšíriteľný riadok",
@@ -60,11 +63,13 @@ const Table: React.FC<TableProps> = ({
60
63
  [`${CLASS_ROOT}--striped`]: isStriped,
61
64
  [`${CLASS_ROOT}--compact`]: isCompact,
62
65
  [`${CLASS_ROOT}--responsive`]: isResponsive,
66
+ [`${CLASS_ROOT}--pinned-first-column`]: isFirstColumnPinned,
63
67
  },
64
68
  className,
65
69
  );
66
70
 
67
71
  const captionId = `${idPrefix}-caption`;
72
+ const hasScrollableRegion = isScrollable || isFirstColumnPinned;
68
73
 
69
74
  const expandableRows = {
70
75
  hasExpandableRows: rows.some(
@@ -98,7 +103,7 @@ const Table: React.FC<TableProps> = ({
98
103
  const table = (
99
104
  <TableContext.Provider value={context}>
100
105
  <ConditionalWrapper
101
- condition={isScrollable || false}
106
+ condition={hasScrollableRegion || false}
102
107
  wrapper={
103
108
  <div
104
109
  className={`${CLASS_ROOT}-scrollable`}
@@ -114,7 +119,7 @@ const Table: React.FC<TableProps> = ({
114
119
  {...other}
115
120
  >
116
121
  {caption && (
117
- <caption {...(isScrollable && { id: captionId })}>
122
+ <caption {...(hasScrollableRegion && { id: captionId })}>
118
123
  {caption}
119
124
  </caption>
120
125
  )}
@@ -57,17 +57,33 @@
57
57
  }
58
58
 
59
59
  @mixin scrollable-wrapper {
60
+ container-type: inline-size;
60
61
  display: block;
61
62
  width: 100%;
62
63
  overflow-x: auto;
63
64
  -webkit-overflow-scrolling: touch;
64
65
 
66
+ caption {
67
+ position: sticky;
68
+ left: 0;
69
+ width: 100cqw;
70
+ }
71
+
65
72
  td,
66
73
  th {
67
74
  white-space: nowrap;
68
75
  }
69
76
  }
70
77
 
78
+ @mixin responsive-scrollable-wrapper {
79
+ overflow-x: visible;
80
+
81
+ td,
82
+ th {
83
+ white-space: normal;
84
+ }
85
+ }
86
+
71
87
  @mixin sort-button {
72
88
  position: relative;
73
89
  display: inline-block;
@@ -117,6 +133,49 @@
117
133
  width: auto;
118
134
  }
119
135
 
136
+ @mixin pinned-first-column {
137
+ tr > :first-child {
138
+ position: sticky;
139
+ left: 0;
140
+ z-index: 1;
141
+ background: var(
142
+ --table-pinned-column-background,
143
+ var(--color-background-primary)
144
+ );
145
+
146
+ &::after {
147
+ content: "";
148
+ display: none;
149
+ position: absolute;
150
+ top: 0;
151
+ right: -12px;
152
+ bottom: 0;
153
+ width: 12px;
154
+ background: linear-gradient(
155
+ to right,
156
+ color-mix(in srgb, var(--color-text-default) 10%, transparent),
157
+ transparent
158
+ );
159
+ pointer-events: none;
160
+ }
161
+ }
162
+
163
+ thead > tr > :first-child {
164
+ z-index: 2;
165
+ }
166
+ }
167
+
168
+ @mixin unpin-first-column {
169
+ tr > :first-child {
170
+ position: static;
171
+ background: transparent;
172
+
173
+ &::after {
174
+ display: none;
175
+ }
176
+ }
177
+ }
178
+
120
179
  @mixin responsive-table {
121
180
  thead {
122
181
  display: none;
@@ -38,20 +38,39 @@
38
38
 
39
39
  &--striped tbody tr:nth-of-type(odd) {
40
40
  @include mixins.odd-row;
41
+ --table-pinned-column-background: var(--color-background-secondary);
41
42
  }
42
43
 
43
44
  &--striped.background-secondary tbody tr:nth-of-type(odd),
44
45
  .background-secondary &--striped tbody tr:nth-of-type(odd) {
45
46
  background-color: config.$striped-color-on-secondary;
47
+ --table-pinned-column-background: var(--color-background-primary);
46
48
  }
47
49
 
48
50
  &--compact {
49
51
  @include mixins.compact;
50
52
  }
51
53
 
54
+ &--pinned-first-column {
55
+ @include mixins.pinned-first-column;
56
+ }
57
+
58
+ &--pinned-first-column.background-secondary,
59
+ .background-secondary &--pinned-first-column {
60
+ --table-pinned-column-background: var(--color-background-secondary);
61
+ }
62
+
63
+ &-scrollable--overflowing &--pinned-first-column tr > :first-child::after {
64
+ display: block;
65
+ }
66
+
52
67
  &--responsive {
53
68
  @include breakpoint.get("md", "down") {
54
69
  @include mixins.responsive-table;
70
+
71
+ &.table--pinned-first-column {
72
+ @include mixins.unpin-first-column;
73
+ }
55
74
  }
56
75
  }
57
76
 
@@ -63,6 +82,12 @@
63
82
  }
64
83
  }
65
84
 
85
+ @include breakpoint.get("md", "down") {
86
+ &-scrollable:has(> &--responsive) {
87
+ @include mixins.responsive-scrollable-wrapper;
88
+ }
89
+ }
90
+
66
91
  &__sort-button {
67
92
  @include mixins.sort-button;
68
93
 
@@ -0,0 +1,72 @@
1
+ import { afterEach, beforeEach, expect, it, vi } from "vitest";
2
+
3
+ import Table from "../Table.static";
4
+
5
+ let resizeCallback;
6
+ let observe;
7
+
8
+ beforeEach(() => {
9
+ observe = vi.fn();
10
+ vi.stubGlobal(
11
+ "ResizeObserver",
12
+ vi.fn(function (callback) {
13
+ resizeCallback = callback;
14
+ this.observe = observe;
15
+ this.unobserve = vi.fn();
16
+ this.disconnect = vi.fn();
17
+ }),
18
+ );
19
+ });
20
+
21
+ afterEach(() => {
22
+ vi.unstubAllGlobals();
23
+ document.body.innerHTML = "";
24
+ });
25
+
26
+ it("does not make a responsive non-scrolling wrapper focusable", () => {
27
+ document.body.innerHTML = `
28
+ <div class="table-scrollable table-scrollable--overflowing" style="overflow-x: visible" tabindex="0">
29
+ <table class="table table--responsive"></table>
30
+ </div>
31
+ `;
32
+ const table = document.querySelector("table");
33
+ const scrollableRegion = document.querySelector(".table-scrollable");
34
+ Object.defineProperties(scrollableRegion, {
35
+ clientWidth: { configurable: true, value: 400 },
36
+ scrollWidth: { configurable: true, value: 800 },
37
+ });
38
+ new Table(table);
39
+
40
+ resizeCallback([{ target: table, contentRect: { width: 800 } }]);
41
+
42
+ expect(scrollableRegion).not.toHaveAttribute("tabindex");
43
+ expect(scrollableRegion).not.toHaveClass("table-scrollable--overflowing");
44
+ });
45
+
46
+ it("marks a horizontally overflowing wrapper", () => {
47
+ document.body.innerHTML = `
48
+ <div class="table-scrollable" style="overflow-x: auto">
49
+ <table class="table table--pinned-first-column"></table>
50
+ </div>
51
+ `;
52
+ const table = document.querySelector("table");
53
+ const scrollableRegion = document.querySelector(".table-scrollable");
54
+ Object.defineProperties(scrollableRegion, {
55
+ clientWidth: { configurable: true, value: 400 },
56
+ scrollWidth: { configurable: true, value: 800 },
57
+ });
58
+ const instance = new Table(table);
59
+
60
+ expect(observe).toHaveBeenCalledWith(table);
61
+ expect(observe).toHaveBeenCalledWith(scrollableRegion);
62
+
63
+ resizeCallback([{ target: table, contentRect: { width: 800 } }]);
64
+
65
+ expect(scrollableRegion).toHaveClass("table-scrollable--overflowing");
66
+ expect(scrollableRegion).toHaveAttribute("tabindex", "0");
67
+
68
+ instance.destroy();
69
+
70
+ expect(scrollableRegion).not.toHaveClass("table-scrollable--overflowing");
71
+ expect(scrollableRegion).not.toHaveAttribute("tabindex");
72
+ });
@@ -194,6 +194,39 @@ describe("rendering Table", () => {
194
194
  expect(getByTestId("test-id")).toHaveClass("table");
195
195
  expect(getByTestId("test-id")).toHaveClass("table--compact");
196
196
  });
197
+ it("pins the first column and enables overflow handling when isFirstColumnPinned is passed", () => {
198
+ const { container, getByTestId } = render(
199
+ <Table
200
+ data-testid="test-id"
201
+ headers={[]}
202
+ rows={[]}
203
+ isFirstColumnPinned
204
+ />,
205
+ );
206
+
207
+ expect(getByTestId("test-id")).toHaveClass("table--pinned-first-column");
208
+ expect(container.querySelector(".table-scrollable")).toContainElement(
209
+ getByTestId("test-id"),
210
+ );
211
+ });
212
+ it("labels the pinned-column scroll region with its caption", () => {
213
+ const { container } = render(
214
+ <Table
215
+ headers={[]}
216
+ rows={[]}
217
+ caption="Pinned table"
218
+ isFirstColumnPinned
219
+ />,
220
+ );
221
+ const scrollableRegion = container.querySelector(".table-scrollable");
222
+ const caption = container.querySelector("caption");
223
+
224
+ expect(caption).toHaveAttribute(
225
+ "id",
226
+ scrollableRegion.getAttribute("aria-labelledby"),
227
+ );
228
+ expect(caption).toHaveTextContent("Pinned table");
229
+ });
197
230
  it("renders div wrapper with class table-scrollable when isScrollable is passed", () => {
198
231
  const { container } = render(
199
232
  <Table headers={[]} rows={[]} isScrollable />,
@@ -29,6 +29,7 @@ import { Controls } from "./Controls";
29
29
  import { Countdown, CountdownTag } from "./Countdown";
30
30
  import { Cover } from "./Cover";
31
31
  import { Divider } from "./Divider";
32
+ import { Drawer, DrawerBody, DrawerFooter, DrawerHeader } from "./Drawer";
32
33
  import {
33
34
  Dropdown,
34
35
  DropdownDivider,
@@ -50,7 +51,10 @@ import {
50
51
  Message,
51
52
  Radio,
52
53
  RangeSlider,
54
+ SegmentedControl,
55
+ SegmentedControlItem,
53
56
  Select,
57
+ SelectionTile,
54
58
  TextArea,
55
59
  TextInput,
56
60
  } from "./Forms";
@@ -88,6 +92,7 @@ import { Skeleton } from "./Skeleton";
88
92
  import { SkipLink } from "./SkipLink";
89
93
  import { SocialButton } from "./SocialButton";
90
94
  import { StarRating } from "./StarRating";
95
+ import { StatusIndicator } from "./StatusIndicator";
91
96
  import { Stepbar } from "./Stepbar";
92
97
  import { Sticker } from "./Sticker";
93
98
  import { Table } from "./Table";
@@ -134,6 +139,10 @@ export {
134
139
  CountdownTag,
135
140
  Cover,
136
141
  Divider,
142
+ Drawer,
143
+ DrawerBody,
144
+ DrawerFooter,
145
+ DrawerHeader,
137
146
  Dropdown,
138
147
  DropdownDivider,
139
148
  DropdownItem,
@@ -185,13 +194,17 @@ export {
185
194
  Radio,
186
195
  RangeSlider,
187
196
  Section,
197
+ SegmentedControl,
198
+ SegmentedControlItem,
188
199
  Select,
200
+ SelectionTile,
189
201
  Skeleton,
190
202
  SkipLink,
191
203
  SocialButton,
192
204
  SpritePathContext,
193
205
  SpritePathProvider,
194
206
  StarRating,
207
+ StatusIndicator,
195
208
  Stepbar,
196
209
  Sticker,
197
210
  Tab,
@@ -0,0 +1,3 @@
1
+ $background: color-mix(in srgb, var(--color-surface-contrast) 75%, transparent);
2
+
3
+ $transition: 0.2s ease-out;