@orangesk/orange-design-system 2.0.0-beta.8 → 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 (44) 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 +11 -7
  5. package/build/components/types/src/components/Button/Button.d.ts +1 -0
  6. package/build/components/types/src/components/Card/Card.d.ts +2 -1
  7. package/build/components/types/src/components/Carousel/Carousel.d.ts +4 -0
  8. package/build/components/types/src/components/Carousel/Carousel.static.d.ts +1 -0
  9. package/build/components/types/src/components/Carousel/constants.d.ts +2 -0
  10. package/build/components/types/src/components/Pill/Pill.d.ts +1 -1
  11. package/build/components/types/src/components/PromoBanner/PromoBanner.d.ts +3 -5
  12. package/build/lib/after-components.css +1 -1
  13. package/build/lib/after-components.css.map +1 -1
  14. package/build/lib/before-components.css +1 -1
  15. package/build/lib/before-components.css.map +1 -1
  16. package/build/lib/components.css +1 -1
  17. package/build/lib/components.css.map +1 -1
  18. package/build/lib/scripts.js +3 -3
  19. package/build/lib/scripts.js.map +1 -1
  20. package/build/lib/style.css +1 -1
  21. package/build/lib/style.css.map +1 -1
  22. package/build/lib/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +10 -10
  24. package/src/components/Button/Button.tsx +2 -0
  25. package/src/components/Button/styles/mixins.scss +5 -0
  26. package/src/components/Button/styles/style.scss +4 -0
  27. package/src/components/Card/Card.tsx +4 -1
  28. package/src/components/Card/styles/style.scss +4 -0
  29. package/src/components/Carousel/Carousel.static.ts +67 -1
  30. package/src/components/Carousel/Carousel.tsx +41 -19
  31. package/src/components/Carousel/constants.ts +2 -0
  32. package/src/components/Carousel/styles/config.scss +1 -2
  33. package/src/components/Carousel/styles/mixins.scss +35 -2
  34. package/src/components/Carousel/styles/style.scss +8 -0
  35. package/src/components/Icon/styles/style.scss +11 -0
  36. package/src/components/Icon/tests/Pictogram.unit.test.js +38 -0
  37. package/src/components/Pill/Pill.tsx +1 -1
  38. package/src/components/Pill/styles/config.scss +4 -0
  39. package/src/components/Preview/PreviewGenerator.tsx +48 -21
  40. package/src/components/PromoBanner/PromoBanner.tsx +14 -26
  41. package/src/components/PromoBanner/styles/mixins.scss +20 -21
  42. package/src/components/PromoBanner/styles/style.scss +0 -6
  43. package/src/styles/base/globals.scss +1 -0
  44. package/src/styles/utilities/color.scss +90 -20
@@ -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
  });
@@ -3,7 +3,7 @@ import cx from "classnames";
3
3
 
4
4
  const CLASS_ROOT = "pill";
5
5
 
6
- export const pillColors = ["white", "gray", "transparent"] as const;
6
+ export const pillColors = ["white", "gray", "transparent", "orange"] as const;
7
7
  export type PillColor = (typeof pillColors)[number];
8
8
 
9
9
  interface PillProps extends HTMLAttributes<HTMLSpanElement> {
@@ -31,6 +31,10 @@ $colors: (
31
31
  background-color: var(--color-surface-subtle),
32
32
  color: var(--color-text-default),
33
33
  ),
34
+ "orange": (
35
+ background-color: var(--color-icon-brand),
36
+ color: var(--color-text-inverse),
37
+ ),
34
38
  "transparent": (
35
39
  background-color: rgba(#ffffff, 0.5),
36
40
  color: var(--color-text-default),
@@ -115,29 +115,36 @@ const getBackgroundsAsArray = (
115
115
  return acc;
116
116
  }, []);
117
117
 
118
- // Map human-friendly color names to bg-* utility classes
118
+ // Map names to only the 6 allowed background classes
119
119
  const getBgClassFromName = (name: string): string => {
120
120
  const normalized = name.toLowerCase().replace(/\s+/g, "-");
121
- const n = normalized === "grey" ? "gray" : normalized;
122
- if (n === "none") return "bg-none";
123
- return `bg-${n}`; // e.g. white -> bg-white, orange-dark -> bg-orange-dark
124
- };
125
121
 
126
- // Determine contrast helper class for nested component styling
127
- const getThemeClassFromName = (name: string): string => {
128
- const n = name.toLowerCase();
129
- // Only black switches global dark tokens; other colors keep light tokens
130
- return n === "black" ? "is-dark" : "is-light";
122
+ // Only allow these background classes
123
+ const allowedBackgrounds = [
124
+ "background-primary",
125
+ "background-secondary",
126
+ "background-contrast",
127
+ "background-accent",
128
+ "background-accent1-blog",
129
+ "background-accent2-blog",
130
+ "background-none",
131
+ ];
132
+
133
+ // Return the class if it's in the allowed list, otherwise default to background-primary
134
+ return allowedBackgrounds.includes(normalized)
135
+ ? normalized
136
+ : "background-primary";
131
137
  };
132
138
 
133
139
  const PreviewGenerator: FunctionComponent<PreviewProps> = ({
134
- bgTheme = "white",
140
+ bgTheme = "background-primary",
135
141
  bgThemeColors = {
136
- White: "bg-white",
137
- Gray: "bg-gray",
138
- Black: "bg-black",
139
- "Accent1 Blog": "bg-accent1-blog",
140
- "Accent2 Blog": "bg-accent2-blog",
142
+ "Background Primary": "background-primary",
143
+ "Background Secondary": "background-secondary",
144
+ "Background Contrast": "background-contrast",
145
+ "Background Accent": "background-accent",
146
+ "Background Accent1 Blog": "background-accent1-blog",
147
+ "Background Accent2 Blog": "background-accent2-blog",
141
148
  },
142
149
  bgThemeExcludedColors = [],
143
150
  children,
@@ -153,6 +160,7 @@ const PreviewGenerator: FunctionComponent<PreviewProps> = ({
153
160
  }) => {
154
161
  const [isCodeShown, setIsCodeShown] = useState(false);
155
162
  const [isFullscreen, setIsFullscreen] = useState(false);
163
+ const [isDarkMode, setIsDarkMode] = useState(false);
156
164
  // Ensure unique dropdown id per Preview instance to avoid id collisions (SSR-safe)
157
165
  const backgroundDropdownId = `background-select-${useId()}`;
158
166
  const [previewBackground, setPreviewBackground] = useState<PreviewBackground>(
@@ -185,10 +193,10 @@ const PreviewGenerator: FunctionComponent<PreviewProps> = ({
185
193
 
186
194
  return {
187
195
  label: bgTheme,
188
- value: bgThemeColors[bgTheme] || "bg-white",
196
+ value: bgThemeColors[bgTheme] || "background-primary",
189
197
  };
190
198
  }
191
- return { label: "white", value: "bg-white" };
199
+ return { label: "Background Primary", value: "background-primary" };
192
200
  },
193
201
  );
194
202
 
@@ -204,7 +212,11 @@ const PreviewGenerator: FunctionComponent<PreviewProps> = ({
204
212
  setIsFullscreen(!isFullscreen);
205
213
  }
206
214
 
207
- const themeClass = getThemeClassFromName(previewBackground.label);
215
+ function handleToggleDarkMode() {
216
+ setIsDarkMode(!isDarkMode);
217
+ }
218
+
219
+ const themeClass = isDarkMode ? "is-dark" : "is-light";
208
220
  const bgClass = getBgClassFromName(previewBackground.label);
209
221
 
210
222
  const classes = cx(CLASS_ROOT, themeClass, bgClass, className);
@@ -220,6 +232,23 @@ const PreviewGenerator: FunctionComponent<PreviewProps> = ({
220
232
  bgThemeExcludedColors,
221
233
  );
222
234
 
235
+ // Add theme toggle button
236
+ actions.push(
237
+ <Button key="theme-toggle" onClick={handleToggleDarkMode} type="ghost">
238
+ <Icon
239
+ name={isDarkMode ? "sleep" : "flashlight"}
240
+ size="medium"
241
+ fill={"currentColor"}
242
+ style={{ marginRight: "0.5rem" }}
243
+ />
244
+ {isDarkMode ? (
245
+ <code className="small bold">{".is-dark"}</code>
246
+ ) : (
247
+ <code className="small bold">{".is-light"}</code>
248
+ )}
249
+ </Button>,
250
+ );
251
+
223
252
  if (bgColorsOptions.length) {
224
253
  actions.push(
225
254
  <Dropdown
@@ -352,6 +381,4 @@ const PreviewGenerator: FunctionComponent<PreviewProps> = ({
352
381
  );
353
382
  };
354
383
 
355
- PreviewGenerator.displayName = "PreviewGenerator";
356
-
357
384
  export { PreviewGenerator };
@@ -2,27 +2,22 @@ import React from "react";
2
2
  import cx from "classnames";
3
3
  import { Grid, GridCol } from "../Grid";
4
4
  import { Image } from "../Image";
5
- import { Button } from "../Button";
6
5
 
7
6
  interface PromoBannerProps {
8
7
  colorScheme?: "light" | "dark";
9
8
  className?: string;
10
- title: string;
11
- secondaryTitle?: string;
12
- description: string;
13
- buttonText: string;
14
9
  image?: string;
10
+ alignImage?: "bottom";
11
+ children?: React.ReactNode;
15
12
  }
16
13
 
17
14
  const CLASS_ROOT = "promo-banner";
18
15
  export const PromoBanner = ({
19
- title,
20
- secondaryTitle,
21
- description,
22
- buttonText,
23
16
  image,
24
17
  colorScheme,
25
18
  className,
19
+ alignImage,
20
+ children,
26
21
  }: PromoBannerProps) => {
27
22
  const classes = cx(
28
23
  CLASS_ROOT,
@@ -33,30 +28,23 @@ export const PromoBanner = ({
33
28
  className,
34
29
  );
35
30
  return (
36
- <div className={classes}>
31
+ <div className={cx(classes)}>
37
32
  <Grid>
38
33
  <GridCol
39
- size={{ xs: 12, md: image ? 6 : 8 }}
34
+ size={{ xs: 12, md: image ? 6 : 7 }}
40
35
  className={`${CLASS_ROOT}__item`}
41
36
  >
42
- <h2 className="display-3 thin mb-medium">
43
- <span
44
- className={`${secondaryTitle ? `${CLASS_ROOT}__item-title` : ""}`}
45
- >
46
- {title}
47
- </span>
48
- {secondaryTitle && (
49
- <span className="color-orange ml-small">{secondaryTitle}</span>
50
- )}
51
- </h2>
52
- <p className="mb-large">{description}</p>
53
- <Button type="fill" size="large">
54
- {buttonText}
55
- </Button>
37
+ {children}
56
38
  </GridCol>
57
39
 
58
40
  {image && (
59
- <GridCol size={{ xs: 12, md: 6 }} className={`${CLASS_ROOT}__image`}>
41
+ <GridCol
42
+ size={{ xs: 12, md: 6 }}
43
+ className={cx(
44
+ `${CLASS_ROOT}__image`,
45
+ alignImage === "bottom" && "align-self-bottom",
46
+ )}
47
+ >
60
48
  <Image src={image} alt="" className="mb-none" />
61
49
  </GridCol>
62
50
  )}
@@ -1,34 +1,33 @@
1
+ @use "sass:map" as sassmap;
1
2
  @use "./../../../styles/tools/convert";
3
+ @use "./../../../styles/tools/generate";
2
4
  @use "./../../../styles/tokens/breakpoint";
3
5
  @use "./../../../styles/tokens/space";
4
-
5
- @mixin promo-banner-base() {
6
- background-color: var(--color-background-primary);
7
- color: var(--color-text-default);
8
- padding: space.get("xlarge") convert.to-rem(70px);
9
-
10
- @include breakpoint.get("lg", "down") {
11
- padding: space.get("xlarge") convert.to-rem(20px);
12
- }
13
-
14
- @include breakpoint.get("sm", "down") {
15
- padding: convert.to-rem(30px) space.get("small");
16
- }
17
- }
6
+ @use "./../../Section/styles/mixins" as section;
18
7
 
19
8
  @mixin promo-banner-image() {
20
- text-align: center;
9
+ text-align: end;
21
10
  align-self: center;
11
+
12
+ @include breakpoint.get("md", "down") {
13
+ text-align: center;
14
+ }
22
15
  }
23
16
 
24
17
  @mixin promo-banner-item() {
25
- padding-right: space.get("xlarge") !important;
18
+ padding-top: space.get("xlarge");
19
+ padding-bottom: space.get("xlarge");
26
20
 
27
- @include breakpoint.get("lg", "down") {
28
- padding-right: convert.to-rem(10px) !important;
21
+ @include breakpoint.get("md", "down") {
22
+ padding-top: space.get("large");
23
+ padding-bottom: space.get("medium");
29
24
  }
30
- }
31
25
 
32
- @mixin promo-banner-item-title() {
33
- margin-right: space.get("small") !important;
26
+ display: flex;
27
+ flex-direction: column;
28
+ justify-content: center;
29
+
30
+ > * {
31
+ width: fit-content !important;
32
+ }
34
33
  }
@@ -2,18 +2,12 @@
2
2
 
3
3
  @layer components {
4
4
  .promo-banner {
5
- @include mixins.promo-banner-base;
6
-
7
5
  &__image {
8
6
  @include mixins.promo-banner-image;
9
7
  }
10
8
 
11
9
  &__item {
12
10
  @include mixins.promo-banner-item;
13
-
14
- &-title {
15
- @include mixins.promo-banner-item-title;
16
- }
17
11
  }
18
12
  }
19
13
  }
@@ -48,6 +48,7 @@
48
48
  html {
49
49
  font-size: base.$font-size;
50
50
  scroll-padding-top: unset;
51
+ overflow-x: hidden;
51
52
  }
52
53
 
53
54
  html,