@orangesk/orange-design-system 2.0.0-beta.48 → 2.0.0-beta.49

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.
@@ -13,7 +13,7 @@
13
13
  },
14
14
  {
15
15
  "href": "/components/alert",
16
- "content": "Alert If you need to notify user about something specific, or if you want to explain something shortly, this is the element to do it. Variants Title Title and Description Title and Buttons Prihlásiť sa, Skryť, ]} /> Title, Description and Buttons Prihlásiť sa} /> Types There are four types of alert messages you can choose from. - Info - Success - Warning - Danger Info info is default type of alert. It's used for simple, not most important, messages for the user if something happend. }> Success Best feeling is when something went well. You can notify the user about successful things with success type. Warning If you need to warn user about anything use warning type. Danger If warning is not enought and you need to show error message, use danger type. Custom title renderer Alert spacing is quite strict and works 99% of time. In rare cases, the title requires a little more space to breathe. To achieve that, you can render a custom title using the renderTitle prop. ( <h3 className=\"alerttitle mb-medium\">{props.title}</h3> )} description={ } actionButtons={Prihlásiť sa} /> Full width If you need full width Alert, you can use isFullWidth prop. Max width on text remamains the same, because of readability. API React Props | Prop | Type | Default | Description | | --------------- | ---------------------------------------------- | -------- | ---------------------------------------------------------- | | title | string | - | Required.** Main text message | | description | React.ReactNode | - | Description of the alert | | type | \"info\" \\| \"success\" \\| \"warning\" \\| \"danger\" | \"info\" | Type of alert, sets icon and color | | headingLevel | number | 3 | Level of the heading in heading hierarchy (1-6) | | isFullWidth | boolean | - | Sets Alert to full width | | actionButtons | React.ReactNode | - | Additional action buttons to add interactivity | | renderTitle | (props: AlertProps) => React.ReactNode | - | Custom title renderer. Passes props as function parameter. | | onClose | () => void | - | Callback function when close button is clicked | | className | string | - | Additional CSS classes |"
16
+ "content": "Alert If you need to notify user about something specific, or if you want to explain something shortly, this is the element to do it. Variants Title Title and Description Title and Buttons Prihlásiť sa, Skryť, ]} /> Title, Description and Links Popis toho čo by bolo dobré vysvetliť. Aj na viac riadkov. Niekedy veci nie sú samozrejmé, ale ak sa ich pokúsime vysvetliť, tak sa môžme stretnúť s pochopením. </p> } actionButtons={[ Link Base , Link Base , ]} /> Types There are four types of alert messages you can choose from. - Info - Success - Warning - Danger Info info is default type of alert. It's used for simple, not most important, messages for the user if something happend. }> Success Best feeling is when something went well. You can notify the user about successful things with success type. Warning If you need to warn user about anything use warning type. Danger If warning is not enought and you need to show error message, use danger type. Custom title renderer Alert spacing is quite strict and works 99% of time. In rare cases, the title requires a little more space to breathe. To achieve that, you can render a custom title using the renderTitle prop. ( <h3 className=\"alerttitle mb-medium\">{props.title}</h3> )} description={ } actionButtons={Prihlásiť sa} /> Full width If you need full width Alert, you can use isFullWidth prop. Max width on text remamains the same, because of readability. API React Props | Prop | Type | Default | Description | | --------------- | ---------------------------------------------- | -------- | ---------------------------------------------------------- | | title | string | - | Required.** Main text message | | description | React.ReactNode | - | Description of the alert | | type | \"info\" \\| \"success\" \\| \"warning\" \\| \"danger\" | \"info\" | Type of alert, sets icon and color | | headingLevel | number | 3 | Level of the heading in heading hierarchy (1-6) | | isFullWidth | boolean | - | Sets Alert to full width | | actionButtons | React.ReactNode | - | Additional buttons or links to add interactivity | | renderTitle | (props: AlertProps) => React.ReactNode | - | Custom title renderer. Passes props as function parameter. | | onClose | () => void | - | Callback function when close button is clicked | | className | string | - | Additional CSS classes |"
17
17
  },
18
18
  {
19
19
  "href": "/components/anchor-navigation",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangesk/orange-design-system",
3
- "version": "2.0.0-beta.48",
3
+ "version": "2.0.0-beta.49",
4
4
  "private": false,
5
5
  "engines": {
6
6
  "node": ">=20.x"
@@ -63,7 +63,7 @@
63
63
  "classnames": "^2.5.1",
64
64
  "daypickr": "^0.3.4",
65
65
  "diff2html": "^3.4.56",
66
- "dompurify": "^3.4.5",
66
+ "dompurify": "^3.4.6",
67
67
  "html-react-parser": "6.1.2",
68
68
  "lorem-ipsum": "3.0.0",
69
69
  "minisearch": "7.2.0",
@@ -86,10 +86,10 @@
86
86
  "wnumb": "^1.2.0"
87
87
  },
88
88
  "devDependencies": {
89
- "@babel/core": "^7.29.0",
90
- "@babel/preset-env": "^7.29.5",
91
- "@babel/preset-react": "^7.28.5",
92
- "@babel/preset-typescript": "^7.28.5",
89
+ "@babel/core": "^7.29.7",
90
+ "@babel/preset-env": "^7.29.7",
91
+ "@babel/preset-react": "^7.29.7",
92
+ "@babel/preset-typescript": "^7.29.7",
93
93
  "@biomejs/biome": "latest",
94
94
  "@rollup/plugin-alias": "6.0.0",
95
95
  "@rollup/plugin-babel": "7.0.0",
@@ -116,7 +116,7 @@
116
116
  "canvas": "^3.2.3",
117
117
  "fs-extra": "^11.3.5",
118
118
  "glob": "13.0.6",
119
- "html-validate": "11.2.0",
119
+ "html-validate": "11.4.0",
120
120
  "husky": "^9.1.7",
121
121
  "identity-obj-proxy": "^3.0.0",
122
122
  "jsdom": "29.1.1",
@@ -6,7 +6,7 @@ $card-base: (
6
6
  background-color: var(--color-surface-primary),
7
7
  margin-bottom: space.get("large"),
8
8
  border-radius: convert.to-rem(10px),
9
- border: 1px solid var(--color-border-strong),
9
+ border: 1px solid var(--color-border-subtle),
10
10
  overflow: hidden,
11
11
  );
12
12
 
@@ -128,6 +128,13 @@ export default class CarouselHero {
128
128
  ? parseInt(this.element.getAttribute("data-interval")!) || 0
129
129
  : 0;
130
130
 
131
+ if (typeof this.config.a11y === "object" && this.config.a11y !== null) {
132
+ this.config.a11y = {
133
+ ...this.config.a11y,
134
+ slideRole: this.tabs.length > 0 ? "tabpanel" : "group",
135
+ };
136
+ }
137
+
131
138
  if (this.element.hasAttribute("data-swiper-options")) {
132
139
  try {
133
140
  const customOptions = JSON.parse(
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import cx from "classnames";
4
- import React, { ReactNode } from "react";
4
+ import React, { ReactNode, useId } from "react";
5
5
  import { useStatic } from "@/utils/hooks";
6
6
  import { Container } from "../Container";
7
7
  import { Controls } from "../Controls";
@@ -21,11 +21,35 @@ import {
21
21
  CLASS_VIEWPORT_WRAPPER,
22
22
  } from "./constants";
23
23
 
24
+ function getCarouselHeroSlides(children: ReactNode): React.ReactElement[] {
25
+ const slides: React.ReactElement[] = [];
26
+
27
+ React.Children.forEach(children, (child) => {
28
+ if (!React.isValidElement(child)) return;
29
+
30
+ if (child.type === React.Fragment) {
31
+ slides.push(
32
+ ...getCarouselHeroSlides(
33
+ (child.props as { children?: ReactNode }).children,
34
+ ),
35
+ );
36
+ return;
37
+ }
38
+
39
+ slides.push(child);
40
+ });
41
+
42
+ return slides;
43
+ }
44
+
24
45
  interface TabItem {
25
46
  label: string;
47
+ id?: string;
26
48
  [key: string]: any;
27
49
  }
28
50
 
51
+ type CarouselHeroSlideElement = React.ReactElement<Record<string, any>>;
52
+
29
53
  interface CarouselHeroProps {
30
54
  className?: string;
31
55
  swiperOptions?: Record<string, any>;
@@ -48,6 +72,24 @@ const CarouselHero: React.FC<CarouselHeroProps> = ({
48
72
  const [carouselRef] = useStatic(CarouselHeroStatic);
49
73
 
50
74
  const classes = cx(CLASS_ROOT, className);
75
+ const generatedId = `carousel-hero-${useId().replace(/:/g, "-")}`;
76
+ const carouselId =
77
+ typeof other.id === "string" && other.id ? other.id : generatedId;
78
+ const slideItems = getCarouselHeroSlides(
79
+ children,
80
+ ) as CarouselHeroSlideElement[];
81
+ const hasTabs = tabs.length > 0 && slideItems.length > 0;
82
+ const getTabId = (tab: TabItem | undefined, index: number) =>
83
+ typeof tab?.id === "string" && tab.id
84
+ ? tab.id
85
+ : `${carouselId}-tab-${index}`;
86
+ const getPanelId = (
87
+ slide: CarouselHeroSlideElement | undefined,
88
+ index: number,
89
+ ) =>
90
+ slide && typeof slide.props.id === "string" && slide.props.id
91
+ ? slide.props.id
92
+ : `${carouselId}-panel-${index}`;
51
93
 
52
94
  const elementClasses = {
53
95
  prev: CLASS_PREV,
@@ -75,6 +117,20 @@ const CarouselHero: React.FC<CarouselHeroProps> = ({
75
117
  !!(interval && interval >= 1000));
76
118
 
77
119
  const playPauseIcon = "pause";
120
+ const tabSlides = slideItems.map((slide, index) => {
121
+ if (!hasTabs) {
122
+ return slide;
123
+ }
124
+
125
+ const tabId = getTabId(tabs[index], index);
126
+ const panelId = getPanelId(slide, index);
127
+
128
+ return React.cloneElement(slide as CarouselHeroSlideElement, {
129
+ id: panelId,
130
+ role: "tabpanel",
131
+ "aria-labelledby": tabId,
132
+ });
133
+ });
78
134
 
79
135
  return (
80
136
  <div
@@ -91,21 +147,23 @@ const CarouselHero: React.FC<CarouselHeroProps> = ({
91
147
  >
92
148
  <div className={CLASS_VIEWPORT_WRAPPER}>
93
149
  <div className={CLASS_VIEWPORT}>
94
- <div className={CLASS_TRACK}>{children}</div>
150
+ <div className={CLASS_TRACK}>{tabSlides}</div>
95
151
  </div>
96
152
  </div>
97
153
 
98
154
  {/* Controls container */}
99
155
  <Container className="d-flex">
100
156
  {/* Tab navigation */}
101
- {tabs.length > 0 && (
157
+ {hasTabs && (
102
158
  <div className={elementClasses.tabs} role="tablist">
103
159
  {tabs.map((tab, index) => (
104
160
  <button
161
+ id={getTabId(tab, index)}
105
162
  key={index}
106
163
  type="button"
107
164
  className={elementClasses.tab}
108
165
  role="tab"
166
+ aria-controls={getPanelId(slideItems[index], index)}
109
167
  aria-selected={index === 0 ? "true" : "false"}
110
168
  tabIndex={index === 0 ? 0 : -1}
111
169
  >
@@ -156,13 +156,20 @@ describe("rendering CarouselHero", () => {
156
156
 
157
157
  it("sets proper ARIA attributes on tabs", () => {
158
158
  const { container } = render(
159
- <CarouselHero tabs={tabs}>{children}</CarouselHero>,
159
+ <CarouselHero id="test-carousel" tabs={tabs}>
160
+ {children}
161
+ </CarouselHero>,
160
162
  );
161
163
 
162
164
  const tabButtons = container.querySelectorAll(".carousel-hero__tab");
163
165
 
164
166
  // First tab should be active
165
167
  expect(tabButtons[0]).toHaveAttribute("role", "tab");
168
+ expect(tabButtons[0]).toHaveAttribute("id", "test-carousel-tab-0");
169
+ expect(tabButtons[0]).toHaveAttribute(
170
+ "aria-controls",
171
+ "test-carousel-panel-0",
172
+ );
166
173
  expect(tabButtons[0]).toHaveAttribute("aria-selected", "true");
167
174
  expect(tabButtons[0]).toHaveAttribute("tabIndex", "0");
168
175
 
@@ -173,6 +180,29 @@ describe("rendering CarouselHero", () => {
173
180
  expect(tabButtons[2]).toHaveAttribute("tabIndex", "-1");
174
181
  });
175
182
 
183
+ it("links tabs to corresponding tabpanels", () => {
184
+ const { container } = render(
185
+ <CarouselHero id="test-carousel" tabs={tabs}>
186
+ {children}
187
+ </CarouselHero>,
188
+ );
189
+
190
+ const slides = container.getElementsByClassName("carousel-hero__slide");
191
+
192
+ expect(slides[0]).toHaveAttribute("role", "tabpanel");
193
+ expect(slides[0]).toHaveAttribute("id", "test-carousel-panel-0");
194
+ expect(slides[0]).toHaveAttribute(
195
+ "aria-labelledby",
196
+ "test-carousel-tab-0",
197
+ );
198
+ expect(slides[1]).toHaveAttribute("role", "tabpanel");
199
+ expect(slides[1]).toHaveAttribute("id", "test-carousel-panel-1");
200
+ expect(slides[1]).toHaveAttribute(
201
+ "aria-labelledby",
202
+ "test-carousel-tab-1",
203
+ );
204
+ });
205
+
176
206
  it("shows play/pause button when autoplay is enabled and interval is provided", () => {
177
207
  const { container } = render(
178
208
  <CarouselHero enableAutoplay interval={3000}>
@@ -27,9 +27,21 @@
27
27
  margin-bottom: 0;
28
28
  }
29
29
 
30
+ > .icon,
31
+ > svg {
32
+ width: convert.to-rem(24px);
33
+ height: convert.to-rem(24px);
34
+ }
35
+
30
36
  @include breakpoint.get("md") {
31
37
  flex-direction: column;
32
38
  padding: space.get("large");
39
+
40
+ > .icon,
41
+ > svg {
42
+ width: convert.to-rem(32px);
43
+ height: convert.to-rem(32px);
44
+ }
33
45
  }
34
46
  }
35
47