@orangesk/orange-design-system 2.0.0-beta.7 → 2.0.0-beta.9

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 (67) hide show
  1. package/build/components/index.js +4 -4
  2. package/build/components/index.js.map +1 -1
  3. package/build/components/tsconfig.tsbuildinfo +1 -1
  4. package/build/components/types/index.d.ts +12 -8
  5. package/build/components/types/src/components/AnchorNavigation/AnchorNavigation.d.ts +1 -1
  6. package/build/components/types/src/components/AnchorNavigation/AnchorNavigation.static.d.ts +19 -17
  7. package/build/components/types/src/components/Button/Button.d.ts +1 -0
  8. package/build/components/types/src/components/Card/Card.d.ts +2 -1
  9. package/build/components/types/src/components/Carousel/Carousel.d.ts +4 -0
  10. package/build/components/types/src/components/Carousel/Carousel.static.d.ts +1 -0
  11. package/build/components/types/src/components/Carousel/constants.d.ts +2 -0
  12. package/build/components/types/src/components/Megamenu/constants.d.ts +2 -0
  13. package/build/components/types/src/components/Pill/Pill.d.ts +1 -1
  14. package/build/components/types/src/components/PromoBanner/PromoBanner.d.ts +3 -5
  15. package/build/components/types/src/scripts/index.d.ts +5 -0
  16. package/build/lib/after-components.css +1 -1
  17. package/build/lib/after-components.css.map +1 -1
  18. package/build/lib/before-components.css +1 -1
  19. package/build/lib/before-components.css.map +1 -1
  20. package/build/lib/components.css +1 -1
  21. package/build/lib/components.css.map +1 -1
  22. package/build/lib/megamenu.css +1 -1
  23. package/build/lib/megamenu.css.map +1 -1
  24. package/build/lib/megamenu.js +1 -1
  25. package/build/lib/megamenu.js.map +1 -1
  26. package/build/lib/scripts.js +4 -4
  27. package/build/lib/scripts.js.map +1 -1
  28. package/build/lib/style.css +1 -1
  29. package/build/lib/style.css.map +1 -1
  30. package/build/lib/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +16 -16
  32. package/src/components/AnchorNavigation/AnchorNavigation.static.ts +253 -73
  33. package/src/components/AnchorNavigation/AnchorNavigation.tsx +31 -24
  34. package/src/components/AnchorNavigation/styles/mixins.scss +14 -17
  35. package/src/components/AnchorNavigation/tests/AnchorNavigation.conformance.test.js +67 -0
  36. package/src/components/AnchorNavigation/tests/AnchorNavigation.unit.test.js +163 -0
  37. package/src/components/BlockAction/styles/mixins.scss +0 -6
  38. package/src/components/Button/Button.tsx +2 -0
  39. package/src/components/Button/styles/mixins.scss +5 -0
  40. package/src/components/Button/styles/style.scss +4 -0
  41. package/src/components/Card/Card.tsx +5 -1
  42. package/src/components/Card/styles/style.scss +4 -0
  43. package/src/components/Carousel/Carousel.static.ts +67 -1
  44. package/src/components/Carousel/Carousel.tsx +41 -19
  45. package/src/components/Carousel/constants.ts +2 -0
  46. package/src/components/Carousel/styles/config.scss +1 -2
  47. package/src/components/Carousel/styles/mixins.scss +35 -2
  48. package/src/components/Carousel/styles/style.scss +8 -0
  49. package/src/components/Icon/styles/style.scss +11 -0
  50. package/src/components/Icon/tests/Pictogram.unit.test.js +38 -0
  51. package/src/components/Link/styles/style.scss +1 -1
  52. package/src/components/Link/tests/Link.conformance.test.js +5 -20
  53. package/src/components/Link/tests/Link.unit.test.js +1 -10
  54. package/src/components/Megamenu/Megamenu.static.ts +2 -0
  55. package/src/components/Megamenu/Megamenu.tsx +671 -665
  56. package/src/components/Megamenu/MegamenuBlog.tsx +187 -183
  57. package/src/components/Megamenu/constants.ts +2 -0
  58. package/src/components/Megamenu/styles/mixins.scss +30 -1
  59. package/src/components/Megamenu/styles/style.scss +8 -0
  60. package/src/components/Pill/Pill.tsx +1 -1
  61. package/src/components/Pill/styles/config.scss +4 -0
  62. package/src/components/Preview/PreviewGenerator.tsx +48 -21
  63. package/src/components/PromoBanner/PromoBanner.tsx +14 -26
  64. package/src/components/PromoBanner/styles/mixins.scss +20 -21
  65. package/src/components/PromoBanner/styles/style.scss +0 -6
  66. package/src/styles/base/globals.scss +19 -0
  67. package/src/styles/utilities/color.scss +94 -20
@@ -0,0 +1,163 @@
1
+ import { render } from "@testing-library/react";
2
+
3
+ import { AnchorNavigation } from "../AnchorNavigation";
4
+
5
+ const basicItems = [
6
+ { label: "Key Features", href: "#features", isActive: true },
7
+ { label: "Pricing", href: "#pricing" },
8
+ { label: "Getting Started", href: "#getting-started" },
9
+ { label: "Contact", href: "#contact" },
10
+ ];
11
+
12
+ const moreItems = [
13
+ { label: "Overview", href: "#overview", isActive: true },
14
+ { label: "Features", href: "#features" },
15
+ { label: "Documentation", href: "#docs" },
16
+ { label: "API Reference", href: "#api" },
17
+ { label: "Examples", href: "#examples" },
18
+ { label: "Support", href: "#support" },
19
+ ];
20
+
21
+ describe("rendering AnchorNavigation", () => {
22
+ describe("initial state", () => {
23
+ it("has default class anchor-navigation", () => {
24
+ const { getByTestId } = render(
25
+ <AnchorNavigation data-testid="test-id" items={basicItems} />,
26
+ );
27
+ expect(getByTestId("test-id")).toHaveClass("anchor-navigation");
28
+ });
29
+
30
+ it("has data-anchor-navigation attribute", () => {
31
+ const { getByTestId } = render(
32
+ <AnchorNavigation data-testid="test-id" items={basicItems} />,
33
+ );
34
+ expect(getByTestId("test-id")).toHaveAttribute("data-anchor-navigation");
35
+ });
36
+
37
+ it("renders container with anchor-navigation__content class", () => {
38
+ const { container } = render(<AnchorNavigation items={basicItems} />);
39
+ expect(
40
+ container.querySelector(".anchor-navigation__content"),
41
+ ).toBeInTheDocument();
42
+ });
43
+
44
+ it("renders grid with correct classes", () => {
45
+ const { container } = render(<AnchorNavigation items={basicItems} />);
46
+ const grid = container.querySelector(".anchor-navigation__content-left");
47
+ expect(grid).toBeInTheDocument();
48
+ expect(grid).toHaveClass("list-inline");
49
+ expect(grid).toHaveClass("horizontal-scroll");
50
+ expect(grid).toHaveClass("mb-none");
51
+ });
52
+ });
53
+
54
+ describe("passed props", () => {
55
+ it("renders all navigation items", () => {
56
+ const { getByText } = render(<AnchorNavigation items={basicItems} />);
57
+
58
+ expect(getByText("Key Features")).toBeInTheDocument();
59
+ expect(getByText("Pricing")).toBeInTheDocument();
60
+ expect(getByText("Getting Started")).toBeInTheDocument();
61
+ expect(getByText("Contact")).toBeInTheDocument();
62
+ });
63
+
64
+ it("renders navigation items as links with correct href", () => {
65
+ const { container } = render(<AnchorNavigation items={basicItems} />);
66
+
67
+ const featuresLink = container.querySelector('a[href="#features"]');
68
+ const pricingLink = container.querySelector('a[href="#pricing"]');
69
+
70
+ expect(featuresLink).toBeInTheDocument();
71
+ expect(featuresLink).toHaveTextContent("Key Features");
72
+ expect(pricingLink).toBeInTheDocument();
73
+ expect(pricingLink).toHaveTextContent("Pricing");
74
+ });
75
+
76
+ it("applies active class to items with isActive prop", () => {
77
+ const { container } = render(<AnchorNavigation items={basicItems} />);
78
+
79
+ const activeLink = container.querySelector('a[href="#features"]');
80
+ const inactiveLink = container.querySelector('a[href="#pricing"]');
81
+
82
+ expect(activeLink).toHaveClass("is-active");
83
+ expect(inactiveLink).not.toHaveClass("is-active");
84
+ });
85
+
86
+ it("applies anchor-navigation__item class to all links", () => {
87
+ const { container } = render(<AnchorNavigation items={basicItems} />);
88
+
89
+ const links = container.querySelectorAll(".anchor-navigation__item");
90
+ expect(links).toHaveLength(basicItems.length);
91
+ });
92
+
93
+ it("renders multiple items correctly", () => {
94
+ const { getByText } = render(<AnchorNavigation items={moreItems} />);
95
+
96
+ expect(getByText("Overview")).toBeInTheDocument();
97
+ expect(getByText("Features")).toBeInTheDocument();
98
+ expect(getByText("Documentation")).toBeInTheDocument();
99
+ expect(getByText("API Reference")).toBeInTheDocument();
100
+ expect(getByText("Examples")).toBeInTheDocument();
101
+ expect(getByText("Support")).toBeInTheDocument();
102
+ });
103
+
104
+ it("applies custom className", () => {
105
+ const { getByTestId } = render(
106
+ <AnchorNavigation
107
+ data-testid="test-id"
108
+ items={basicItems}
109
+ className="custom-class"
110
+ />,
111
+ );
112
+ expect(getByTestId("test-id")).toHaveClass("anchor-navigation");
113
+ expect(getByTestId("test-id")).toHaveClass("custom-class");
114
+ });
115
+ });
116
+
117
+ describe("children content", () => {
118
+ it("renders children in content-right section when provided", () => {
119
+ const { container, getByText } = render(
120
+ <AnchorNavigation items={basicItems}>
121
+ <div>Additional Content</div>
122
+ </AnchorNavigation>,
123
+ );
124
+
125
+ const contentRight = container.querySelector(
126
+ ".anchor-navigation__content-right",
127
+ );
128
+ expect(contentRight).toBeInTheDocument();
129
+ expect(getByText("Additional Content")).toBeInTheDocument();
130
+ });
131
+
132
+ it("does not render content-right section when no children", () => {
133
+ const { container } = render(<AnchorNavigation items={basicItems} />);
134
+
135
+ const contentRight = container.querySelector(
136
+ ".anchor-navigation__content-right",
137
+ );
138
+ expect(contentRight).not.toBeInTheDocument();
139
+ });
140
+
141
+ it("renders complex children content", () => {
142
+ const { getByText, getByRole } = render(
143
+ <AnchorNavigation items={basicItems}>
144
+ <div className="pricing">
145
+ <div className="bold">2 €</div>
146
+ <div>No commitment</div>
147
+ </div>
148
+ <button>Get Started</button>
149
+ </AnchorNavigation>,
150
+ );
151
+
152
+ expect(getByText("2 €")).toBeInTheDocument();
153
+ expect(getByText("No commitment")).toBeInTheDocument();
154
+ expect(getByRole("button", { name: "Get Started" })).toBeInTheDocument();
155
+ });
156
+ });
157
+
158
+ describe("displayName", () => {
159
+ it("has correct displayName", () => {
160
+ expect(AnchorNavigation.displayName).toBe("AnchorNavigation");
161
+ });
162
+ });
163
+ });
@@ -3,12 +3,6 @@
3
3
 
4
4
  @mixin layout-base {
5
5
  position: relative;
6
-
7
- &:focus-within,
8
- &:hover {
9
- outline: 2px solid var(--color-border-accent);
10
- outline-offset: 1px;
11
- }
12
6
  }
13
7
 
14
8
  @mixin control {
@@ -16,6 +16,7 @@ interface CommonProps {
16
16
  type?: ButtonType;
17
17
  className?: string;
18
18
  children?: React.ReactNode;
19
+ preserveWidth?: boolean;
19
20
  }
20
21
 
21
22
  type AnchorProps = CommonProps &
@@ -62,6 +63,7 @@ const Button = React.forwardRef<any, ButtonProps>((props, ref) => {
62
63
  [`${CLASS_ROOT}--${size}`]: size,
63
64
  [`${CLASS_ROOT}--${type}`]: type,
64
65
  [`${CLASS_ROOT}--default`]: isDefault,
66
+ [`${CLASS_ROOT}--preserve-width`]: props.preserveWidth,
65
67
  "is-dark": colorScheme === "dark",
66
68
  "is-light": colorScheme === "light",
67
69
  "is-active": isActive,
@@ -153,3 +153,8 @@
153
153
  cursor: not-allowed;
154
154
  }
155
155
  }
156
+
157
+ @mixin preserve-width {
158
+ width: initial !important;
159
+ min-width: initial !important;
160
+ }
@@ -43,4 +43,8 @@
43
43
  .btn--large .icon--large {
44
44
  @include mixins.large-button-large-icon-size-fix();
45
45
  }
46
+
47
+ .btn--preserve-width {
48
+ @include mixins.preserve-width;
49
+ }
46
50
  }
@@ -6,6 +6,7 @@ export const cardColors = [
6
6
  "black",
7
7
  "orange",
8
8
  "gray",
9
+ "gray-lighter",
9
10
  "blue",
10
11
  "green",
11
12
  "pink",
@@ -14,22 +15,25 @@ export const cardColors = [
14
15
  "accent1-blog",
15
16
  "accent2-blog",
16
17
  "accent",
18
+ "none",
17
19
  ] as const;
18
20
  export type CardColor = (typeof cardColors)[number];
19
21
 
20
22
  export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
21
23
  /** Custom background color */
22
24
  color?: CardColor;
25
+ noBorder?: boolean;
23
26
  }
24
27
 
25
28
  export const CLASS_ROOT = "card";
26
29
 
27
30
  const Card = React.forwardRef<HTMLDivElement, CardProps>(
28
- ({ className, color = "white", ...other }, ref) => {
31
+ ({ className, color = "white", noBorder, ...other }, ref) => {
29
32
  const classes = cx(
30
33
  CLASS_ROOT,
31
34
  {
32
35
  [`bg-${color}`]: color !== "white",
36
+ [`${CLASS_ROOT}--no-border`]: noBorder,
33
37
  },
34
38
  className,
35
39
  );
@@ -35,5 +35,9 @@
35
35
  &__product-header--space-small &__product-content {
36
36
  @include mixins.card-product-header(space.get());
37
37
  }
38
+
39
+ &--no-border {
40
+ border: none;
41
+ }
38
42
  }
39
43
  }
@@ -26,6 +26,7 @@ import {
26
26
  SELECTOR_ACTIVE,
27
27
  CLASS_SLIDE_NEXT,
28
28
  CLASS_SLIDE_PREV,
29
+ CLASS_BLEED_RIGHT,
29
30
  } from "./constants";
30
31
 
31
32
  export const defaultConfig: SwiperOptions = {
@@ -124,6 +125,11 @@ export default class Carousel {
124
125
  slideChange: this.handleSlideChange,
125
126
  },
126
127
  });
128
+
129
+ // Check if this is a bleed-right carousel and adjust after initialization
130
+ if (this.element.classList.contains(CLASS_BLEED_RIGHT)) {
131
+ this.adjustConfigForBleedRight();
132
+ }
127
133
  }
128
134
 
129
135
  getElements() {
@@ -148,7 +154,7 @@ export default class Carousel {
148
154
  ...((this.config.pagination ?? {}) as object),
149
155
  el: paginationEl,
150
156
  clickable: true,
151
- bulletClass: "carousel__pagination-item",
157
+ bulletClass: CLASS_DOT,
152
158
  bulletActiveClass: CLASS_ACTIVE,
153
159
  },
154
160
  };
@@ -172,6 +178,66 @@ export default class Carousel {
172
178
  };
173
179
  }
174
180
 
181
+ adjustConfigForBleedRight() {
182
+ // For bleed-right variant, calculate the exact bleed amount dynamically
183
+ // This allows the last slide to scroll back to align with container edge
184
+
185
+ // Wait for next frame to ensure container has final dimensions
186
+ requestAnimationFrame(() => {
187
+ // Find the container element
188
+ const container = this.element.closest(".container") as HTMLElement;
189
+ if (!container || !this.instance) return;
190
+
191
+ // Calculate the actual bleed amount with limits to match CSS
192
+ const containerWidth = container.offsetWidth;
193
+ const halfViewportWidth = window.innerWidth / 2;
194
+ const halfContainerWidth = containerWidth / 2;
195
+ let bleedAmount = halfViewportWidth - halfContainerWidth;
196
+
197
+ // On very large screens (above 2K), stop the bleeding effect entirely
198
+ if (window.innerWidth >= 2560) {
199
+ bleedAmount = 0;
200
+ }
201
+
202
+ // Only add offset if there's actual bleeding (positive value)
203
+ if (bleedAmount > 0) {
204
+ // Update the Swiper configuration
205
+ this.instance.params.slidesOffsetAfter = bleedAmount;
206
+ this.instance.update();
207
+ } else {
208
+ // Remove offset if no bleeding should occur
209
+ this.instance.params.slidesOffsetAfter = 0;
210
+ this.instance.update();
211
+ }
212
+
213
+ // Also listen for window resize to recalculate
214
+ const handleResize = () => {
215
+ if (this.instance && container) {
216
+ const newContainerWidth = container.offsetWidth;
217
+ const newHalfViewportWidth = window.innerWidth / 2;
218
+ const newHalfContainerWidth = newContainerWidth / 2;
219
+ let newBleedAmount = newHalfViewportWidth - newHalfContainerWidth;
220
+
221
+ // Apply same limits as above
222
+ if (window.innerWidth >= 2560) {
223
+ newBleedAmount = 0;
224
+ }
225
+
226
+ if (newBleedAmount > 0) {
227
+ this.instance.params.slidesOffsetAfter = newBleedAmount;
228
+ } else {
229
+ this.instance.params.slidesOffsetAfter = 0;
230
+ }
231
+ this.instance.update();
232
+ }
233
+ };
234
+
235
+ // Remove any existing resize listener to avoid duplicates
236
+ window.removeEventListener("resize", handleResize);
237
+ window.addEventListener("resize", handleResize);
238
+ });
239
+ }
240
+
175
241
  /**
176
242
  * Handles the slide change event on the carousel.
177
243
  * Updates the tooltip position for the active slide and hides tooltips for non-active slides.
@@ -27,6 +27,10 @@ interface CarouselProps extends HTMLAttributes<HTMLDivElement> {
27
27
  colorScheme?: "light" | "dark";
28
28
  /** Carousel items */
29
29
  items: ReactNode[];
30
+ /** Always show scrollbar and hide dots */
31
+ showScrollbar?: boolean;
32
+ /** Make carousel bleed to the right edge of screen while keeping left aligned with container */
33
+ bleedRight?: boolean;
30
34
  className?: string;
31
35
  }
32
36
 
@@ -35,6 +39,8 @@ const Carousel: React.FC<CarouselProps> = ({
35
39
  swiperOptions,
36
40
  items,
37
41
  colorScheme,
42
+ showScrollbar = false,
43
+ bleedRight = false,
38
44
  ...other
39
45
  }) => {
40
46
  const [carouselRef] = useStatic(CarouselStatic);
@@ -46,21 +52,33 @@ const Carousel: React.FC<CarouselProps> = ({
46
52
  const classes = cx(CLASS_ROOT, className, {
47
53
  "is-dark": colorScheme === "dark",
48
54
  "is-light": colorScheme === "light",
55
+ [`${CLASS_ROOT}--scrollbar`]: showScrollbar,
56
+ [`${CLASS_ROOT}--bleed-right`]: bleedRight,
49
57
  });
50
58
 
51
59
  const elementClasses = {
52
60
  prev: cx(CLASS_PREV),
53
61
  next: cx(CLASS_NEXT),
54
- dots: cx(CLASS_DOTS, "show-md"),
62
+ dots: cx(CLASS_DOTS, showScrollbar ? "hide" : "show-md"),
55
63
  };
56
64
 
65
+ // Only override pagination when showScrollbar is true
66
+ const customSwiperOptions = showScrollbar
67
+ ? {
68
+ pagination: {
69
+ enabled: false,
70
+ },
71
+ ...swiperOptions,
72
+ }
73
+ : swiperOptions;
74
+
57
75
  return (
58
76
  <div
59
77
  className={classes}
60
78
  ref={carouselRef}
61
79
  data-carousel
62
- {...(swiperOptions
63
- ? { "data-swiper-options": JSON.stringify(swiperOptions) }
80
+ {...(showScrollbar || swiperOptions
81
+ ? { "data-swiper-options": JSON.stringify(customSwiperOptions) }
64
82
  : {})}
65
83
  {...other}
66
84
  >
@@ -68,25 +86,29 @@ const Carousel: React.FC<CarouselProps> = ({
68
86
  <div className={CLASS_VIEWPORT}>
69
87
  <div className={CLASS_TRACK}>{carouselItems}</div>
70
88
  </div>
71
- <button
72
- type="button"
73
- aria-label="Naspäť"
74
- className={elementClasses.prev}
75
- >
76
- <Icon size="medium" name="chevron-left" />
77
- </button>
78
- <button
79
- type="button"
80
- aria-label="Ďalej"
81
- className={elementClasses.next}
82
- >
83
- <Icon size="medium" name="chevron-right" />
84
- </button>
89
+ {showScrollbar ? null : (
90
+ <>
91
+ <button
92
+ type="button"
93
+ aria-label="Naspäť"
94
+ className={elementClasses.prev}
95
+ >
96
+ <Icon size="medium" name="chevron-left" />
97
+ </button>
98
+ <button
99
+ type="button"
100
+ aria-label="Ďalej"
101
+ className={elementClasses.next}
102
+ >
103
+ <Icon size="medium" name="chevron-right" />
104
+ </button>
105
+ </>
106
+ )}
85
107
  </div>
86
108
 
87
- <div role="tablist" className={elementClasses.dots} />
109
+ {!showScrollbar && <div role="tablist" className={elementClasses.dots} />}
88
110
 
89
- <div className={cx(CLASS_SCROLLBAR, "hide-md")}>
111
+ <div className={cx(CLASS_SCROLLBAR, !showScrollbar && "hide-md")}>
90
112
  <div className={CLASS_SCROLLBAR_DRAG} />
91
113
  </div>
92
114
  </div>
@@ -14,6 +14,8 @@ export const CLASS_SCROLLBAR = `${CLASS_ROOT}__scrollbar`;
14
14
  export const CLASS_SCROLLBAR_HORIZONTAL = `${CLASS_ROOT}__scrollbar-horizontal`;
15
15
  export const CLASS_SCROLLBAR_DRAG = `${CLASS_ROOT}__scrollbar-drag`;
16
16
  export const CLASS_ACTIVE = "is-active";
17
+ export const CLASS_SCROLLBAR_VARIANT = `${CLASS_ROOT}--scrollbar`;
18
+ export const CLASS_BLEED_RIGHT = `${CLASS_ROOT}--bleed-right`;
17
19
 
18
20
  export const SELECTOR_VIEWPORT = `.${CLASS_VIEWPORT}`;
19
21
  export const SELECTOR_TRACK = `.${CLASS_TRACK}`;
@@ -25,7 +25,6 @@ $breakout-buttons-breakpoints: (
25
25
  );
26
26
 
27
27
  $viewport-horizontal-spacing: (
28
- "default": -#{space.get("small")},
29
28
  // button dimension + space - slide padding,
30
- "md": convert.to-rem(50px),
29
+ "md": convert.to-rem(50px)
31
30
  );
@@ -43,6 +43,11 @@
43
43
  }
44
44
  }
45
45
 
46
+ .carousel--scrollbar & {
47
+ margin-left: 0;
48
+ margin-right: 0;
49
+ }
50
+
46
51
  @include breakpoint.get("sm", "down") {
47
52
  padding-bottom: math.div(config.$space, 2);
48
53
  }
@@ -88,15 +93,19 @@
88
93
  margin-right: -#{convert.to-rem(60px)};
89
94
  }
90
95
  }
96
+
97
+ &.carousel--bleed-right {
98
+ margin-left: 0;
99
+ margin-right: 0;
100
+ }
91
101
  }
92
102
 
93
103
  @mixin slide-base {
94
104
  display: flex;
95
105
  flex-direction: column;
96
106
  max-width: 100%;
97
- // width: auto !important;
98
107
  flex: 0 0 auto;
99
- padding: 0 math.div(config.$space, 2);
108
+ padding: 0 convert.to-rem(20px) 0 0;
100
109
  user-select: none;
101
110
 
102
111
  > * {
@@ -193,4 +202,28 @@
193
202
  opacity: 1;
194
203
  background-color: var(--color-fill-contrast);
195
204
  border-radius: 99px;
205
+ height: convert.to-rem(6px);
206
+ }
207
+
208
+ @mixin scrollbar-variant {
209
+ margin-left: 0 !important;
210
+ margin-right: 0 !important;
211
+ }
212
+
213
+ @mixin bleed-right-variant {
214
+ .carousel__viewport {
215
+ // Extend the viewport to the right edge of the screen
216
+ width: calc(100% + 50vw - 50%);
217
+ margin-right: 0;
218
+
219
+ // Keep left margin for container alignment
220
+ @include breakpoint.get("md") {
221
+ margin-left: convert.to-rem(50px);
222
+ }
223
+
224
+ // On very large screens (above 2K), stop the bleeding effect entirely
225
+ @media (min-width: 2560px) {
226
+ width: 100%;
227
+ }
228
+ }
196
229
  }
@@ -5,6 +5,14 @@
5
5
  .carousel {
6
6
  @include mixins.base;
7
7
 
8
+ &--scrollbar {
9
+ @include mixins.scrollbar-variant;
10
+ }
11
+
12
+ &--bleed-right {
13
+ @include mixins.bleed-right-variant;
14
+ }
15
+
8
16
  .container > & {
9
17
  @include mixins.base-breakout-buttons;
10
18
  }
@@ -28,6 +28,17 @@
28
28
  display: block;
29
29
  }
30
30
  }
31
+
32
+ // In light theme, explicitly show light and hide dark
33
+ .is-light & {
34
+ &--light {
35
+ display: block;
36
+ }
37
+
38
+ &--dark {
39
+ display: none;
40
+ }
41
+ }
31
42
  }
32
43
 
33
44
  &--with-text-end {
@@ -117,4 +117,42 @@ describe("Pictogram component", () => {
117
117
  expect(getByTestId("test-pictogram")).toHaveClass("custom-class");
118
118
  });
119
119
  });
120
+
121
+ describe("theme switching", () => {
122
+ it("shows light version by default", () => {
123
+ const { container } = render(<Pictogram name="pictogram-skylink" />);
124
+ const lightUse = container.querySelector(".icon__use--light");
125
+ const darkUse = container.querySelector(".icon__use--dark");
126
+
127
+ expect(lightUse).toBeInTheDocument();
128
+ expect(darkUse).toBeInTheDocument();
129
+ // Note: CSS display logic is tested via CSS rules, not inline styles
130
+ });
131
+
132
+ it("renders correctly within is-light context", () => {
133
+ const { container } = render(
134
+ <div className="is-light">
135
+ <Pictogram name="pictogram-skylink" />
136
+ </div>,
137
+ );
138
+ const lightUse = container.querySelector(".icon__use--light");
139
+ const darkUse = container.querySelector(".icon__use--dark");
140
+
141
+ expect(lightUse).toBeInTheDocument();
142
+ expect(darkUse).toBeInTheDocument();
143
+ });
144
+
145
+ it("renders correctly within is-dark context", () => {
146
+ const { container } = render(
147
+ <div className="is-dark">
148
+ <Pictogram name="pictogram-skylink" />
149
+ </div>,
150
+ );
151
+ const lightUse = container.querySelector(".icon__use--light");
152
+ const darkUse = container.querySelector(".icon__use--dark");
153
+
154
+ expect(lightUse).toBeInTheDocument();
155
+ expect(darkUse).toBeInTheDocument();
156
+ });
157
+ });
120
158
  });
@@ -24,7 +24,7 @@
24
24
  @include mixins.base-inherit();
25
25
 
26
26
  &:hover {
27
- color: var(--color-fill-tertiary);
27
+ color: var(--color-text-accent);
28
28
  text-decoration: underline;
29
29
  }
30
30
  &:focus {
@@ -125,21 +125,6 @@ describe("Link Conformance Tests", () => {
125
125
  expect(results).toHaveNoViolations();
126
126
  });
127
127
 
128
- it("has no accessibility violations for inverse links", async () => {
129
- const { container } = render(
130
- <div>
131
- <Link href="#" isInverse>
132
- Inverse link
133
- </Link>
134
- <Link href="#" color="orange" isInverse>
135
- Orange inverse link
136
- </Link>
137
- </div>,
138
- );
139
- const results = await axe(container);
140
- expect(results).toHaveNoViolations();
141
- });
142
-
143
128
  it("has no accessibility violations for links with custom attributes", async () => {
144
129
  const { container } = render(
145
130
  <div>
@@ -212,11 +197,11 @@ describe("Link Conformance Tests", () => {
212
197
  describe("Cross-browser Compatibility", () => {
213
198
  it("renders consistently across different prop combinations", () => {
214
199
  const testCases = [
215
- { href: "/", color: "default", isInverse: false },
216
- { href: "/", color: "orange", isInverse: true },
217
- { href: "/", color: "black", isInverse: false },
218
- { tag: "button", type: "button", color: "default", isInverse: false },
219
- { tag: "button", type: "button", color: "orange", isInverse: true },
200
+ { href: "/", color: "default" },
201
+ { href: "/", color: "orange" },
202
+ { href: "/", color: "black" },
203
+ { tag: "button", type: "button", color: "default" },
204
+ { tag: "button", type: "button", color: "orange" },
220
205
  ];
221
206
 
222
207
  testCases.forEach((props, index) => {