@orangesk/orange-design-system 3.7.0 → 3.9.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 (108) hide show
  1. package/README.md +0 -6
  2. package/build/components/Alert/style.css +1 -1
  3. package/build/components/Alert/style.css.map +1 -1
  4. package/build/components/Bar/style.css +1 -1
  5. package/build/components/Bar/style.css.map +1 -1
  6. package/build/components/Drawer/style.css +2 -0
  7. package/build/components/Drawer/style.css.map +1 -0
  8. package/build/components/Footer/style.css +1 -1
  9. package/build/components/Footer/style.css.map +1 -1
  10. package/build/components/Megamenu/style.css +1 -1
  11. package/build/components/Megamenu/style.css.map +1 -1
  12. package/build/components/Modal/style.css +1 -1
  13. package/build/components/Modal/style.css.map +1 -1
  14. package/build/components/StatusIndicator/style.css +2 -0
  15. package/build/components/StatusIndicator/style.css.map +1 -0
  16. package/build/components/Table/style.css +1 -1
  17. package/build/components/Table/style.css.map +1 -1
  18. package/build/components/index.js +1 -1
  19. package/build/components/index.js.map +1 -1
  20. package/build/components/types/index.d.ts +55 -3
  21. package/build/lib/base.css +1 -1
  22. package/build/lib/base.css.map +1 -1
  23. package/build/lib/components.css +1 -1
  24. package/build/lib/components.css.map +1 -1
  25. package/build/lib/footer.css +1 -1
  26. package/build/lib/footer.css.map +1 -1
  27. package/build/lib/footer.js +1 -1
  28. package/build/lib/footer.js.map +1 -1
  29. package/build/lib/megamenu.css +1 -1
  30. package/build/lib/megamenu.css.map +1 -1
  31. package/build/lib/megamenu.js +1 -1
  32. package/build/lib/megamenu.js.map +1 -1
  33. package/build/lib/reset.css +2 -0
  34. package/build/lib/reset.css.map +1 -0
  35. package/build/lib/reset.js +3 -0
  36. package/build/lib/scripts.js +1 -1
  37. package/build/lib/scripts.js.map +1 -1
  38. package/build/lib/style.css +1 -1
  39. package/build/lib/style.css.map +1 -1
  40. package/build/lib/utilities.css +1 -1
  41. package/build/lib/utilities.css.map +1 -1
  42. package/package.json +27 -33
  43. package/src/components/Alert/Alert.tsx +2 -2
  44. package/src/components/Alert/data.ts +8 -5
  45. package/src/components/Alert/styles/config.scss +4 -4
  46. package/src/components/Alert/styles/mixins.scss +8 -1
  47. package/src/components/Alert/tests/Alert.test.jsx +11 -1
  48. package/src/components/Bar/Bar.tsx +1 -1
  49. package/src/components/Bar/styles/config.scss +1 -0
  50. package/src/components/Bar/tests/Bar.unit.test.jsx +1 -1
  51. package/src/components/Carousel/Carousel.static.ts +103 -12
  52. package/src/components/Carousel/tests/Carousel.static.test.jsx +134 -0
  53. package/src/components/DocumentationSidebar/DocumentationSidebar.tsx +12 -4
  54. package/src/components/Drawer/Drawer.static.ts +281 -0
  55. package/src/components/Drawer/Drawer.tsx +82 -0
  56. package/src/components/Drawer/DrawerBody.tsx +21 -0
  57. package/src/components/Drawer/DrawerFooter.tsx +21 -0
  58. package/src/components/Drawer/DrawerHeader.tsx +21 -0
  59. package/src/components/Drawer/index.ts +5 -0
  60. package/src/components/Drawer/styles/config.scss +17 -0
  61. package/src/components/Drawer/styles/mixins.scss +119 -0
  62. package/src/components/Drawer/styles/style.scss +71 -0
  63. package/src/components/Drawer/tests/Drawer.unit.test.jsx +149 -0
  64. package/src/components/Footer/Footer.tsx +1 -4
  65. package/src/components/Footer/styles/mixins.scss +3 -1
  66. package/src/components/Footer/styles/style.scss +31 -0
  67. package/src/components/Footer/tests/Footer.styles.unit.test.jsx +62 -0
  68. package/src/components/Forms/SegmentedControl/SegmentedControl.tsx +122 -0
  69. package/src/components/Forms/SegmentedControl/index.ts +5 -0
  70. package/src/components/Forms/SegmentedControl/styles/mixins.scss +52 -0
  71. package/src/components/Forms/SegmentedControl/styles/style.scss +35 -0
  72. package/src/components/Forms/SegmentedControl/tests/SegmentedControl.conformance.test.jsx +36 -0
  73. package/src/components/Forms/SegmentedControl/tests/SegmentedControl.unit.test.jsx +125 -0
  74. package/src/components/Forms/index.ts +1 -0
  75. package/src/components/Icon/index.ts +1 -0
  76. package/src/components/Megamenu/styles/mixins.scss +24 -18
  77. package/src/components/Modal/styles/config.scss +3 -2
  78. package/src/components/StatusIndicator/StatusIndicator.tsx +29 -0
  79. package/src/components/StatusIndicator/index.ts +2 -0
  80. package/src/components/StatusIndicator/styles/config.scss +12 -0
  81. package/src/components/StatusIndicator/styles/mixins.scss +36 -0
  82. package/src/components/StatusIndicator/styles/style.scss +16 -0
  83. package/src/components/StatusIndicator/tests/StatusIndicator.conformance.test.jsx +22 -0
  84. package/src/components/StatusIndicator/tests/StatusIndicator.unit.test.jsx +70 -0
  85. package/src/components/Table/Table.static.ts +23 -10
  86. package/src/components/Table/Table.tsx +7 -2
  87. package/src/components/Table/styles/mixins.scss +59 -0
  88. package/src/components/Table/styles/style.scss +25 -0
  89. package/src/components/Table/tests/Table.static.unit.test.jsx +72 -0
  90. package/src/components/Table/tests/Table.unit.test.jsx +33 -0
  91. package/src/components/index.ts +11 -0
  92. package/src/styles/tokens/overlay.scss +3 -0
  93. package/src/styles/typography/style.scss +0 -13
  94. package/src/styles/utilities/border.scss +4 -0
  95. package/src/styles/utilities/border.styles.unit.test.jsx +9 -0
  96. package/src/styles/utilities/text.scss +12 -0
  97. package/src/components/Alert/tests/Alert.visual.test.jsx +0 -137
  98. package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-custom-title-chromium-linux.png +0 -0
  99. package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-danger-chromium-linux.png +0 -0
  100. package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-full-width-chromium-linux.png +0 -0
  101. package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-success-chromium-linux.png +0 -0
  102. package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-title-buttons-chromium-linux.png +0 -0
  103. package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-title-chromium-linux.png +0 -0
  104. package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-title-description-buttons-chromium-linux.png +0 -0
  105. package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-title-description-chromium-linux.png +0 -0
  106. package/src/components/Alert/tests/__screenshots__/Alert.visual.test.jsx/alert-preview-warning-chromium-linux.png +0 -0
  107. package/src/components/Carousel/CHANGELOG.md +0 -11
  108. package/src/components/CarouselHero/tests/CarouselHero.visual.test.jsx +0 -90
@@ -0,0 +1,82 @@
1
+ "use client";
2
+
3
+ import cx from "classnames";
4
+ import React, { useEffect, useMemo } from "react";
5
+ import { useStatic } from "../../utils/hooks";
6
+ import DrawerStatic from "./Drawer.static";
7
+
8
+ const CLASS_ROOT = "drawer";
9
+
10
+ interface DrawerProps extends React.HTMLAttributes<HTMLDivElement> {
11
+ id: string;
12
+ isActive?: boolean;
13
+ onHide?: () => void;
14
+ children?: React.ReactNode;
15
+ className?: string;
16
+ }
17
+
18
+ const Drawer: React.FC<DrawerProps> = ({
19
+ "aria-label": ariaLabel = "Drawer",
20
+ "aria-labelledby": ariaLabelledBy,
21
+ className,
22
+ children,
23
+ id,
24
+ isActive,
25
+ onHide,
26
+ ...other
27
+ }) => {
28
+ const isReactControlled = isActive !== undefined;
29
+ const drawerConfig = useMemo(
30
+ () => (isReactControlled ? { disablePortal: true } : {}),
31
+ [isReactControlled],
32
+ );
33
+ const [drawerRef, instance] = useStatic(DrawerStatic, drawerConfig);
34
+
35
+ useEffect(() => {
36
+ if (!instance.current || !onHide) return;
37
+
38
+ const handleHide = () => onHide();
39
+ instance.current.instance.on("hide", handleHide);
40
+
41
+ return () => {
42
+ instance.current?.instance.off("hide", handleHide);
43
+ };
44
+ }, [instance, onHide]);
45
+
46
+ useEffect(() => {
47
+ if (!instance.current) return;
48
+
49
+ if (isActive) {
50
+ instance.current.show();
51
+ } else if (isActive === false) {
52
+ instance.current.hide();
53
+ }
54
+ }, [instance, isActive]);
55
+
56
+ return (
57
+ <div
58
+ id={id}
59
+ {...(!isReactControlled && { "data-drawer": true })}
60
+ className={cx(CLASS_ROOT, className)}
61
+ ref={drawerRef}
62
+ role="dialog"
63
+ aria-hidden="true"
64
+ aria-modal="true"
65
+ aria-label={ariaLabelledBy ? undefined : ariaLabel}
66
+ aria-labelledby={ariaLabelledBy}
67
+ >
68
+ <div
69
+ className={`${CLASS_ROOT}__overlay`}
70
+ tabIndex={-1}
71
+ data-a11y-dialog-hide
72
+ />
73
+ <div className={`${CLASS_ROOT}__panel`} role="document" {...other}>
74
+ {children}
75
+ </div>
76
+ </div>
77
+ );
78
+ };
79
+
80
+ Drawer.displayName = "Drawer";
81
+
82
+ export { Drawer };
@@ -0,0 +1,21 @@
1
+ import cx from "classnames";
2
+ import React from "react";
3
+
4
+ interface DrawerBodyProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ children?: React.ReactNode;
6
+ className?: string;
7
+ }
8
+
9
+ const DrawerBody: React.FC<DrawerBodyProps> = ({
10
+ children,
11
+ className,
12
+ ...other
13
+ }) => (
14
+ <div className={cx("drawer__body", className)} {...other}>
15
+ {children}
16
+ </div>
17
+ );
18
+
19
+ DrawerBody.displayName = "DrawerBody";
20
+
21
+ export { DrawerBody };
@@ -0,0 +1,21 @@
1
+ import cx from "classnames";
2
+ import React from "react";
3
+
4
+ interface DrawerFooterProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ children?: React.ReactNode;
6
+ className?: string;
7
+ }
8
+
9
+ const DrawerFooter: React.FC<DrawerFooterProps> = ({
10
+ children,
11
+ className,
12
+ ...other
13
+ }) => (
14
+ <div className={cx("drawer__footer", className)} {...other}>
15
+ {children}
16
+ </div>
17
+ );
18
+
19
+ DrawerFooter.displayName = "DrawerFooter";
20
+
21
+ export { DrawerFooter };
@@ -0,0 +1,21 @@
1
+ import cx from "classnames";
2
+ import React from "react";
3
+
4
+ interface DrawerHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ children?: React.ReactNode;
6
+ className?: string;
7
+ }
8
+
9
+ const DrawerHeader: React.FC<DrawerHeaderProps> = ({
10
+ children,
11
+ className,
12
+ ...other
13
+ }) => (
14
+ <div className={cx("drawer__header", className)} {...other}>
15
+ {children}
16
+ </div>
17
+ );
18
+
19
+ DrawerHeader.displayName = "DrawerHeader";
20
+
21
+ export { DrawerHeader };
@@ -0,0 +1,5 @@
1
+ export { Drawer } from "./Drawer";
2
+ export { default as DrawerStatic } from "./Drawer.static";
3
+ export { DrawerBody } from "./DrawerBody";
4
+ export { DrawerFooter } from "./DrawerFooter";
5
+ export { DrawerHeader } from "./DrawerHeader";
@@ -0,0 +1,17 @@
1
+ @use "../../../styles/tools/convert";
2
+ @use "../../../styles/tokens/overlay";
3
+
4
+ $overlay-bg-color: overlay.$background !default;
5
+ $overlay-z-index: 500 !default;
6
+ $overlay-e: overlay.$transition !default;
7
+
8
+ $overlay: (
9
+ background-color: $overlay-bg-color,
10
+ );
11
+
12
+ $panel: (
13
+ max-width: convert.to-rem(700px),
14
+ max-height: min(80vh, convert.to-rem(520px)),
15
+ desktop-width: convert.to-rem(380px),
16
+ desktop-max-width: 90vw,
17
+ );
@@ -0,0 +1,119 @@
1
+ @use "../../../styles/tokens/space";
2
+ @use "../../../styles/tools/convert";
3
+ @use "../../../styles/tools/generate";
4
+ @use "sass:map";
5
+ @use "./config";
6
+
7
+ @mixin position {
8
+ position: fixed;
9
+ inset: 0;
10
+ }
11
+
12
+ @mixin hide {
13
+ visibility: hidden;
14
+ pointer-events: none;
15
+ transition: visibility 0s linear 0.2s;
16
+ }
17
+
18
+ @mixin drawer {
19
+ @include position;
20
+
21
+ z-index: 999;
22
+ display: flex;
23
+ align-items: flex-end;
24
+ }
25
+
26
+ @mixin overlay {
27
+ @include position;
28
+ @include generate.css-map(config.$overlay);
29
+
30
+ z-index: config.$overlay-z-index;
31
+ width: 0;
32
+ height: 0;
33
+ opacity: 0;
34
+ transform: scale(1.1);
35
+ transition:
36
+ opacity config.$overlay-e,
37
+ transform config.$overlay-e;
38
+ }
39
+
40
+ @mixin overlay-active {
41
+ width: 100%;
42
+ height: 100%;
43
+ opacity: 1;
44
+ transform: scale(1);
45
+ }
46
+
47
+ @mixin panel {
48
+ position: relative;
49
+ z-index: config.$overlay-z-index + 1;
50
+ display: flex;
51
+ flex-flow: column;
52
+ width: 100%;
53
+ max-height: map.get(config.$panel, max-height);
54
+ margin: 0 auto;
55
+ overflow: hidden;
56
+ background: var(--color-surface-primary);
57
+ border-radius: 0;
58
+ transform: translateY(100%);
59
+ transition: transform config.$overlay-e;
60
+ -webkit-overflow-scrolling: touch;
61
+ }
62
+
63
+ @mixin panel-active {
64
+ transform: translateY(0);
65
+ }
66
+
67
+ @mixin desktop {
68
+ justify-content: flex-end;
69
+ }
70
+
71
+ @mixin panel-desktop {
72
+ width: map.get(config.$panel, desktop-width);
73
+ max-width: map.get(config.$panel, desktop-max-width);
74
+ max-height: 100%;
75
+ height: 100%;
76
+ margin: 0;
77
+ transform: translateX(100%);
78
+ }
79
+
80
+ @mixin panel-active-desktop {
81
+ transform: translateX(0);
82
+ }
83
+
84
+ @mixin header {
85
+ position: sticky;
86
+ top: 0;
87
+ z-index: 1;
88
+ flex: 0 0 auto;
89
+ padding: space.get("medium") space.get("small");
90
+ background: var(--color-surface-primary);
91
+
92
+ *:last-child {
93
+ margin-bottom: 0px;
94
+ }
95
+ }
96
+
97
+ @mixin body {
98
+ flex: 1 1 auto;
99
+ min-height: 0;
100
+ overflow-y: auto;
101
+ padding: 0 space.get("small");
102
+
103
+ *:last-child {
104
+ margin-bottom: 0px;
105
+ }
106
+ }
107
+
108
+ @mixin footer {
109
+ flex: 0 0 auto;
110
+ padding: space.get("medium") space.get("small");
111
+
112
+ *:last-child {
113
+ margin-bottom: 0px;
114
+ }
115
+ }
116
+
117
+ @mixin reduced-motion {
118
+ transition-duration: 0.01ms;
119
+ }
@@ -0,0 +1,71 @@
1
+ @use "../../../styles/tokens/breakpoint";
2
+ @use "../../../styles/tokens/space";
3
+ @use "./mixins";
4
+
5
+ @layer components {
6
+ .drawer {
7
+ @include mixins.drawer;
8
+
9
+ &[aria-hidden="true"] {
10
+ @include mixins.hide;
11
+ }
12
+ }
13
+
14
+ .drawer__overlay {
15
+ @include mixins.overlay;
16
+
17
+ .drawer.is-active & {
18
+ @include mixins.overlay-active;
19
+ }
20
+ }
21
+
22
+ .drawer__panel {
23
+ @include mixins.panel;
24
+
25
+ // Keep a form wrapper equivalent to the panel's original flex layout.
26
+ > form {
27
+ display: flex;
28
+ flex: 1 1 auto;
29
+ flex-flow: column;
30
+ min-height: 0;
31
+ }
32
+
33
+ .drawer.is-active & {
34
+ @include mixins.panel-active;
35
+ }
36
+ }
37
+
38
+ .drawer__header {
39
+ @include mixins.header;
40
+ }
41
+
42
+ .drawer__body {
43
+ @include mixins.body;
44
+ }
45
+
46
+ .drawer__footer {
47
+ @include mixins.footer;
48
+ }
49
+
50
+ @include breakpoint.get("lg") {
51
+ .drawer {
52
+ @include mixins.desktop;
53
+ }
54
+
55
+ .drawer__panel {
56
+ @include mixins.panel-desktop;
57
+ }
58
+
59
+ .drawer.is-active .drawer__panel {
60
+ @include mixins.panel-active-desktop;
61
+ }
62
+ }
63
+
64
+ @media (prefers-reduced-motion: reduce) {
65
+ .drawer,
66
+ .drawer__overlay,
67
+ .drawer__panel {
68
+ @include mixins.reduced-motion;
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,149 @@
1
+ import { fireEvent } from "@testing-library/dom";
2
+ import { axe } from "vitest-axe";
3
+
4
+ import Drawer from "../Drawer.static";
5
+
6
+ const instances = [];
7
+
8
+ const renderDrawer = () => {
9
+ document.body.innerHTML = `
10
+ <div id="root">
11
+ <button id="open-drawer" data-a11y-dialog-show="drawer">Open</button>
12
+ </div>
13
+ <div id="root-drawers">
14
+ <div id="drawer" class="drawer" data-drawer role="dialog" aria-modal="true"
15
+ aria-hidden="true" aria-labelledby="drawer-title">
16
+ <div class="drawer__overlay" tabindex="-1" data-a11y-dialog-hide></div>
17
+ <div class="drawer__panel" role="document">
18
+ <header class="drawer__header">
19
+ <h2 id="drawer-title" data-a11y-dialog-initial-focus>Drawer title</h2>
20
+ <button class="drawer__close" data-a11y-dialog-hide>Close</button>
21
+ </header>
22
+ <div class="drawer__body"><p>Drawer content</p></div>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ `;
27
+
28
+ const drawer = document.querySelector("#drawer");
29
+ const instance = new Drawer(drawer);
30
+ instances.push(instance);
31
+
32
+ return { drawer, instance, opener: document.querySelector("#open-drawer") };
33
+ };
34
+
35
+ afterEach(() => {
36
+ instances.forEach((instance) => instance.destroy());
37
+ instances.length = 0;
38
+ document.body.innerHTML = "";
39
+ document.body.removeAttribute("data-lock-scrolltop");
40
+ document.body.className = "";
41
+ document.body.style.cssText = "";
42
+ });
43
+
44
+ it("moves the drawer into root-drawers and starts hidden", () => {
45
+ const { drawer } = renderDrawer();
46
+
47
+ expect(drawer.parentElement.id).toBe("root-drawers");
48
+ expect(drawer).toHaveAttribute("aria-hidden", "true");
49
+ });
50
+
51
+ it("warns when the drawer root is missing", () => {
52
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
53
+ document.body.innerHTML = `
54
+ <div id="drawer" class="drawer" data-drawer role="dialog" aria-modal="true">
55
+ <div class="drawer__overlay" tabindex="-1" data-a11y-dialog-hide></div>
56
+ <div class="drawer__panel" role="document"></div>
57
+ </div>
58
+ `;
59
+
60
+ new Drawer(document.querySelector("#drawer"));
61
+
62
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining("#root-drawers"));
63
+ warn.mockRestore();
64
+ });
65
+
66
+ it("opens, locks body scroll, and closes when the overlay is clicked", () => {
67
+ const { drawer, opener } = renderDrawer();
68
+
69
+ fireEvent.click(opener);
70
+ expect(drawer).toHaveClass("is-active");
71
+ expect(drawer).not.toHaveAttribute("aria-hidden");
72
+ expect(document.body).toHaveClass("has-modal");
73
+
74
+ fireEvent.click(drawer.querySelector(".drawer__overlay"));
75
+ expect(drawer).not.toHaveClass("is-active");
76
+ expect(drawer).toHaveAttribute("aria-hidden", "true");
77
+ expect(document.body).not.toHaveClass("has-modal");
78
+ });
79
+
80
+ it("closes after a downward mobile swipe", () => {
81
+ Object.defineProperty(window, "innerWidth", {
82
+ configurable: true,
83
+ value: 375,
84
+ });
85
+ const { drawer, opener } = renderDrawer();
86
+ fireEvent.click(opener);
87
+
88
+ fireEvent.pointerDown(drawer.querySelector(".drawer__panel"), {
89
+ pointerId: 1,
90
+ isPrimary: true,
91
+ pointerType: "mouse",
92
+ clientX: 100,
93
+ clientY: 100,
94
+ });
95
+ fireEvent.pointerMove(drawer.querySelector(".drawer__panel"), {
96
+ pointerId: 1,
97
+ isPrimary: true,
98
+ pointerType: "mouse",
99
+ clientX: 100,
100
+ clientY: 200,
101
+ });
102
+ fireEvent.pointerUp(drawer.querySelector(".drawer__panel"), {
103
+ pointerId: 1,
104
+ isPrimary: true,
105
+ pointerType: "mouse",
106
+ clientX: 100,
107
+ clientY: 200,
108
+ });
109
+
110
+ expect(drawer).toHaveAttribute("aria-hidden", "true");
111
+ });
112
+
113
+ it("closes after a rightward desktop swipe", () => {
114
+ Object.defineProperty(window, "innerWidth", {
115
+ configurable: true,
116
+ value: 1280,
117
+ });
118
+ const { drawer, opener } = renderDrawer();
119
+ fireEvent.click(opener);
120
+
121
+ fireEvent.pointerDown(drawer.querySelector(".drawer__panel"), {
122
+ pointerId: 1,
123
+ isPrimary: true,
124
+ pointerType: "mouse",
125
+ clientX: 100,
126
+ clientY: 100,
127
+ });
128
+ fireEvent.pointerMove(drawer.querySelector(".drawer__panel"), {
129
+ pointerId: 1,
130
+ pointerType: "mouse",
131
+ clientX: 220,
132
+ clientY: 100,
133
+ });
134
+ fireEvent.pointerUp(drawer.querySelector(".drawer__panel"), {
135
+ pointerId: 1,
136
+ pointerType: "mouse",
137
+ clientX: 220,
138
+ clientY: 100,
139
+ });
140
+
141
+ expect(drawer).toHaveAttribute("aria-hidden", "true");
142
+ });
143
+
144
+ it("is accessible while open", async () => {
145
+ const { drawer, opener } = renderDrawer();
146
+
147
+ fireEvent.click(opener);
148
+ expect(await axe(drawer)).toHaveNoViolations();
149
+ });
@@ -140,10 +140,7 @@ const Footer: React.FC<FooterProps> = ({ className, data = footerData }) => {
140
140
  id={`${accordionIdPrefix}-footer-section-panel-${index}`}
141
141
  >
142
142
  {column.links.map((link, linkIndex) => (
143
- <li
144
- className="mb-small"
145
- key={`footer-link-${index}-${linkIndex}`}
146
- >
143
+ <li key={`footer-link-${index}-${linkIndex}`}>
147
144
  <a href={link.url}>{link.label}</a>
148
145
  </li>
149
146
  ))}
@@ -48,7 +48,9 @@
48
48
  @include base.focusring;
49
49
  }
50
50
 
51
- * {
51
+ *,
52
+ *::before,
53
+ *::after {
52
54
  box-sizing: border-box;
53
55
  }
54
56
 
@@ -3,6 +3,9 @@
3
3
  @use "../../../styles/tools/convert";
4
4
  @use "../../../styles/tokens/breakpoint";
5
5
  @use "../../Megamenu/styles/mixins" as megamenuMixins;
6
+ @use "../../SocialButton/styles/config" as socialButtonConfig;
7
+ @use "../../SocialButton/styles/mixins" as socialButtonMixins;
8
+ @use "../../Icon/styles/mixins" as iconMixins;
6
9
 
7
10
  #{config.$class-root} {
8
11
  @include mixins.globals;
@@ -112,4 +115,32 @@
112
115
  margin-bottom: convert.to-rem(30px);
113
116
  }
114
117
  }
118
+
119
+ // Keep shared component styles local so the standalone bundle works on legacy pages.
120
+ .social-button {
121
+ @include socialButtonMixins.base;
122
+ @include socialButtonMixins.size;
123
+
124
+ &:hover,
125
+ &:focus,
126
+ &:active {
127
+ text-decoration: none;
128
+ }
129
+ }
130
+
131
+ @each $name, $props in socialButtonConfig.$social-colors {
132
+ .social-button--#{$name} {
133
+ @include socialButtonMixins.color($name);
134
+ }
135
+ }
136
+
137
+ .icon {
138
+ @include iconMixins.base;
139
+ @include iconMixins.sizes;
140
+ @include iconMixins.colors;
141
+
142
+ use {
143
+ pointer-events: none;
144
+ }
145
+ }
115
146
  }
@@ -0,0 +1,62 @@
1
+ import * as sass from "sass";
2
+
3
+ describe("Footer standalone styles", () => {
4
+ let stylesheet;
5
+ let rules;
6
+
7
+ beforeAll(() => {
8
+ stylesheet = document.createElement("style");
9
+ stylesheet.textContent = sass.compile(
10
+ "src/components/Footer/styles/style.scss",
11
+ ).css;
12
+ document.head.append(stylesheet);
13
+
14
+ const flattenRules = (cssRules) =>
15
+ Array.from(cssRules).flatMap((rule) =>
16
+ rule.selectorText ? [rule] : flattenRules(rule.cssRules || []),
17
+ );
18
+ rules = flattenRules(stylesheet.sheet.cssRules);
19
+ });
20
+
21
+ afterAll(() => stylesheet.remove());
22
+
23
+ const styleFor = (selector) =>
24
+ rules.find((rule) => rule.selectorText === selector)?.style;
25
+
26
+ it("sizes social buttons and icons without global ODS styles", () => {
27
+ expect(styleFor(".footer .social-button")?.display).toBe("inline-flex");
28
+ expect(styleFor(".footer .social-button")?.width).toBe("2.5rem");
29
+ expect(styleFor(".footer .icon")?.getPropertyValue("fill")).toBe(
30
+ "currentcolor",
31
+ );
32
+ expect(styleFor(".footer .icon")?.width).toBe("var(--icon-size)");
33
+ expect(styleFor(".footer .icon")?.height).toBe("var(--icon-size)");
34
+ expect(
35
+ styleFor(".footer .icon--medium")?.getPropertyValue("--icon-size"),
36
+ ).toBe("1.5rem");
37
+ });
38
+
39
+ it.each(["facebook-2", "x", "linkedin-2", "youtube", "instagram"])(
40
+ "includes theme and interaction colors for %s",
41
+ (name) => {
42
+ const selector = `.footer .social-button--${name}`;
43
+ expect(styleFor(selector)?.color).toBe("var(--color-text-default)");
44
+ expect(styleFor(`${selector}:hover`)?.color).toBe("rgb(255, 255, 255)");
45
+ expect(styleFor(`${selector}:active, ${selector}.is-active`)?.color).toBe(
46
+ "var(--color-text-inverse)",
47
+ );
48
+ },
49
+ );
50
+
51
+ it("keeps reused component styles inside the footer", () => {
52
+ const componentRules = rules.filter((rule) =>
53
+ /\.(social-button|icon)\b/.test(rule.selectorText),
54
+ );
55
+ expect(componentRules.length).toBeGreaterThan(0);
56
+ for (const rule of componentRules) {
57
+ for (const selector of rule.selectorText.split(",")) {
58
+ expect(selector.trim()).toMatch(/^\.footer /);
59
+ }
60
+ }
61
+ });
62
+ });