@orangesk/orange-design-system 3.1.0 → 3.3.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.
- package/build/components/Avatar/style.css +2 -0
- package/build/components/Avatar/style.css.map +1 -0
- package/build/components/BodyBanner/style.css +1 -1
- package/build/components/BodyBanner/style.css.map +1 -1
- package/build/components/Carousel/style.css +1 -1
- package/build/components/Carousel/style.css.map +1 -1
- package/build/components/Countdown/style.css +2 -0
- package/build/components/Countdown/style.css.map +1 -0
- package/build/components/DocumentationSidebar/style.css +1 -1
- package/build/components/DocumentationSidebar/style.css.map +1 -1
- package/build/components/Footer/style.css +1 -1
- package/build/components/Footer/style.css.map +1 -1
- package/build/components/List/style.css +1 -1
- package/build/components/List/style.css.map +1 -1
- package/build/components/Megamenu/style.css +1 -1
- package/build/components/Megamenu/style.css.map +1 -1
- package/build/components/Pill/style.css +1 -1
- package/build/components/Pill/style.css.map +1 -1
- package/build/components/Preview/style.css +1 -1
- package/build/components/Preview/style.css.map +1 -1
- package/build/components/StarRating/style.css +2 -0
- package/build/components/StarRating/style.css.map +1 -0
- package/build/components/index.js +1 -1
- package/build/components/index.js.map +1 -1
- package/build/components/tsconfig.tsbuildinfo +1 -1
- package/build/components/types/index.d.ts +65 -15
- package/build/components/types/src/components/Avatar/Avatar.d.ts +7 -0
- package/build/components/types/src/components/Avatar/index.d.ts +2 -0
- package/build/components/types/src/components/Carousel/Carousel.d.ts +10 -3
- package/build/components/types/src/components/Carousel/constants.d.ts +2 -0
- package/build/components/types/src/components/Countdown/Countdown.d.ts +10 -0
- package/build/components/types/src/components/Countdown/Countdown.react.d.ts +8 -0
- package/build/components/types/src/components/Countdown/Countdown.shared.d.ts +54 -0
- package/build/components/types/src/components/Countdown/Countdown.static.d.ts +21 -0
- package/build/components/types/src/components/Countdown/CountdownTag.d.ts +9 -0
- package/build/components/types/src/components/Countdown/CountdownTag.static.d.ts +16 -0
- package/build/components/types/src/components/Countdown/constants.d.ts +28 -0
- package/build/components/types/src/components/Countdown/index.d.ts +3 -0
- package/build/components/types/src/components/DocumentationSidebar/DocumentationSidebar.d.ts +1 -0
- package/build/components/types/src/components/Preview/PreviewGenerator.d.ts +1 -0
- package/build/components/types/src/components/PromoBanner/PromoBanner.d.ts +5 -1
- package/build/components/types/src/components/StarRating/StarRating.d.ts +9 -0
- package/build/components/types/src/components/StarRating/index.d.ts +1 -0
- package/build/components/types/src/components/index.d.ts +5 -3
- package/build/components/types/src/scripts/index.d.ts +10 -0
- package/build/components/types/src/styles/export/color.d.ts +2 -0
- package/build/lib/base.css +1 -1
- package/build/lib/base.css.map +1 -1
- package/build/lib/components.css +1 -1
- package/build/lib/components.css.map +1 -1
- package/build/lib/footer.css +1 -1
- package/build/lib/footer.css.map +1 -1
- package/build/lib/megamenu.css +1 -1
- package/build/lib/megamenu.css.map +1 -1
- package/build/lib/scripts.js +1 -1
- package/build/lib/scripts.js.map +1 -1
- package/build/lib/style.css +1 -1
- package/build/lib/style.css.map +1 -1
- package/build/lib/tsconfig.tsbuildinfo +1 -1
- package/build/lib/utilities.css +1 -1
- package/build/lib/utilities.css.map +1 -1
- package/build/sprite.svg +1 -1
- package/package.json +30 -29
- package/src/assets/icons/add.svg +4 -1
- package/src/assets/icons/close.svg +3 -3
- package/src/assets/icons/menu.svg +4 -1
- package/src/assets/icons/pause.svg +3 -3
- package/src/assets/icons/play.svg +2 -2
- package/src/assets/icons/quote.svg +1 -1
- package/src/assets/icons/remove.svg +4 -1
- package/src/assets/icons/search.svg +4 -1
- package/src/assets/icons/star-empty-thin.svg +4 -0
- package/src/components/Avatar/Avatar.tsx +36 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/Avatar/styles/config.scss +11 -0
- package/src/components/Avatar/styles/mixins.scss +53 -0
- package/src/components/Avatar/styles/style.scss +20 -0
- package/src/components/Avatar/tests/Avatar.conformance.test.jsx +23 -0
- package/src/components/Avatar/tests/Avatar.unit.test.jsx +45 -0
- package/src/components/BodyBanner/styles/mixins.scss +5 -1
- package/src/components/Carousel/Carousel.static.ts +17 -3
- package/src/components/Carousel/Carousel.tsx +55 -32
- package/src/components/Carousel/constants.ts +2 -0
- package/src/components/Carousel/styles/config.scss +5 -8
- package/src/components/Carousel/styles/mixins.scss +48 -78
- package/src/components/Carousel/styles/style.scss +8 -21
- package/src/components/Carousel/tests/Carousel.unit.test.jsx +77 -5
- package/src/components/Countdown/Countdown.react.ts +67 -0
- package/src/components/Countdown/Countdown.shared.ts +228 -0
- package/src/components/Countdown/Countdown.static.ts +201 -0
- package/src/components/Countdown/Countdown.tsx +105 -0
- package/src/components/Countdown/CountdownTag.static.ts +140 -0
- package/src/components/Countdown/CountdownTag.tsx +99 -0
- package/src/components/Countdown/constants.ts +36 -0
- package/src/components/Countdown/index.ts +3 -0
- package/src/components/Countdown/styles/config.scss +17 -0
- package/src/components/Countdown/styles/mixins.scss +193 -0
- package/src/components/Countdown/styles/style.scss +82 -0
- package/src/components/{Testimonial/tests/Testimonial.conformance.test.jsx → Countdown/tests/Countdown.conformance.test.jsx} +7 -2
- package/src/components/Countdown/tests/Countdown.static.test.jsx +117 -0
- package/src/components/Countdown/tests/Countdown.unit.test.jsx +101 -0
- package/src/components/Countdown/tests/CountdownTag.conformance.test.jsx +21 -0
- package/src/components/Countdown/tests/CountdownTag.static.test.jsx +30 -0
- package/src/components/Countdown/tests/CountdownTag.unit.test.jsx +67 -0
- package/src/components/DocumentationSidebar/DocumentationSidebar.tsx +14 -1
- package/src/components/DocumentationSidebar/styles/style.scss +11 -0
- package/src/components/Forms/Autocomplete/Autocomplete.static.ts +14 -1
- package/src/components/Forms/Autocomplete/styles/mixins.scss +3 -2
- package/src/components/Forms/Autocomplete/tests/Autocomplete.styles.unit.test.jsx +20 -0
- package/src/components/List/styles/config.scss +1 -1
- package/src/components/Pill/styles/style.scss +2 -1
- package/src/components/Preview/PreviewGenerator.tsx +137 -103
- package/src/components/Preview/styles/style.scss +15 -9
- package/src/components/PromoBanner/PromoBanner.tsx +24 -7
- package/src/components/PromoBanner/tests/PromoBanner.unit.test.jsx +31 -0
- package/src/components/StarRating/StarRating.tsx +52 -0
- package/src/components/StarRating/index.ts +1 -0
- package/src/components/StarRating/styles/style.scss +28 -0
- package/src/components/StarRating/tests/StarRating.unit.test.jsx +62 -0
- package/src/components/index.ts +9 -3
- package/src/styles/export/color.js +2 -0
- package/src/styles/tokens/color.scss +2 -0
- package/src/styles/typography/mixins.scss +4 -0
- package/src/styles/utilities/text.scss +9 -0
- package/build/components/Testimonial/style.css +0 -2
- package/build/components/Testimonial/style.css.map +0 -1
- package/build/components/types/src/components/Testimonial/Testimonial.d.ts +0 -10
- package/build/components/types/src/components/Testimonial/index.d.ts +0 -1
- package/build/search-index.json +0 -430
- package/src/components/Testimonial/Testimonial.tsx +0 -38
- package/src/components/Testimonial/index.ts +0 -1
- package/src/components/Testimonial/styles/config.scss +0 -42
- package/src/components/Testimonial/styles/mixins.scss +0 -44
- package/src/components/Testimonial/styles/style.scss +0 -16
- package/src/components/Testimonial/tests/Testimonial.unit.test.jsx +0 -43
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
@use "../../Button/styles/mixins" as button-mixins;
|
|
12
12
|
@use "./config";
|
|
13
13
|
|
|
14
|
-
/* stylelint-disable value-no-vendor-prefix, property-no-vendor-prefix */
|
|
15
|
-
|
|
16
14
|
@mixin base {
|
|
17
15
|
display: flex;
|
|
18
16
|
flex-direction: column;
|
|
@@ -21,8 +19,8 @@
|
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
@mixin viewport-wrapper {
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
width: 100%;
|
|
23
|
+
max-width: 100%;
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
@mixin viewport {
|
|
@@ -34,26 +32,15 @@
|
|
|
34
32
|
-ms-overflow-style: none;
|
|
35
33
|
transform: translateZ(0);
|
|
36
34
|
scrollbar-width: none; // hides scrollbar on FF
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
margin-right: $size;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
35
|
+
min-width: 0;
|
|
36
|
+
width: 100%;
|
|
37
|
+
max-width: 100%;
|
|
38
|
+
margin-bottom: convert.to-rem(30px);
|
|
44
39
|
|
|
45
40
|
.carousel--scrollbar & {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@include breakpoint.get("sm", "down") {
|
|
51
|
-
padding-bottom: math.div(config.$space, 2);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
@include breakpoint.get("md") {
|
|
55
|
-
margin-bottom: config.$space;
|
|
56
|
-
overflow-x: hidden; // hide scrollbar on larger screens
|
|
41
|
+
@include breakpoint.get("md", "down") {
|
|
42
|
+
margin-bottom: convert.to-rem(20px);
|
|
43
|
+
}
|
|
57
44
|
}
|
|
58
45
|
|
|
59
46
|
&.is-draggable {
|
|
@@ -68,6 +55,21 @@
|
|
|
68
55
|
}
|
|
69
56
|
}
|
|
70
57
|
|
|
58
|
+
@mixin navigation {
|
|
59
|
+
display: grid;
|
|
60
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
61
|
+
align-items: center;
|
|
62
|
+
column-gap: config.$navigation-gap;
|
|
63
|
+
row-gap: convert.to-rem(12px);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@mixin controls {
|
|
67
|
+
display: flex;
|
|
68
|
+
flex: 0 0 auto;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: convert.to-rem(8px);
|
|
71
|
+
}
|
|
72
|
+
|
|
71
73
|
@mixin track {
|
|
72
74
|
transform: translateZ(0);
|
|
73
75
|
width: 100%;
|
|
@@ -77,28 +79,6 @@
|
|
|
77
79
|
z-index: 1;
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
@function get-breakout-media-query($min, $max) {
|
|
81
|
-
@return if(
|
|
82
|
-
$max,
|
|
83
|
-
"(min-width: #{$min}) and (max-width: #{$max})",
|
|
84
|
-
"(min-width: #{$min})"
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
@mixin base-breakout-buttons {
|
|
89
|
-
@each $min, $max in config.$breakout-buttons-breakpoints {
|
|
90
|
-
@media only screen and #{get-breakout-media-query($min, $max)} {
|
|
91
|
-
margin-left: -#{convert.to-rem(60px)};
|
|
92
|
-
margin-right: -#{convert.to-rem(60px)};
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
&.carousel--bleed-right {
|
|
97
|
-
margin-left: 0;
|
|
98
|
-
margin-right: 0;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
82
|
@mixin slide-base {
|
|
103
83
|
display: flex;
|
|
104
84
|
flex-direction: column;
|
|
@@ -122,33 +102,6 @@
|
|
|
122
102
|
}
|
|
123
103
|
}
|
|
124
104
|
|
|
125
|
-
@mixin button-base {
|
|
126
|
-
@include button-mixins.base(button-config.$base);
|
|
127
|
-
@include button-mixins.square-base();
|
|
128
|
-
@include button-mixins.square-size("default");
|
|
129
|
-
@include base.focusring-round();
|
|
130
|
-
|
|
131
|
-
position: absolute;
|
|
132
|
-
top: 50%;
|
|
133
|
-
z-index: 10;
|
|
134
|
-
margin: 0;
|
|
135
|
-
border-radius: 100%;
|
|
136
|
-
|
|
137
|
-
@include breakpoint.get("sm", "down") {
|
|
138
|
-
display: none;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
@mixin button-prev {
|
|
143
|
-
left: 0;
|
|
144
|
-
transform: translateY(-50%);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
@mixin button-next {
|
|
148
|
-
right: 0;
|
|
149
|
-
transform: translateY(-50%);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
105
|
@mixin button-disabled {
|
|
153
106
|
@include generate.css-map(sass-map.get(button-config.$base, "disabled"));
|
|
154
107
|
}
|
|
@@ -156,32 +109,49 @@
|
|
|
156
109
|
@mixin pagination {
|
|
157
110
|
user-select: none;
|
|
158
111
|
display: flex;
|
|
159
|
-
|
|
160
|
-
justify-content: center;
|
|
161
|
-
margin: 0 auto;
|
|
112
|
+
margin: 0;
|
|
162
113
|
padding: 0;
|
|
114
|
+
min-width: 0;
|
|
115
|
+
flex-wrap: wrap;
|
|
116
|
+
align-items: center;
|
|
117
|
+
justify-content: flex-start;
|
|
163
118
|
}
|
|
164
119
|
|
|
165
120
|
@mixin pagination-item {
|
|
121
|
+
appearance: none;
|
|
166
122
|
background: none;
|
|
167
|
-
border: 0;
|
|
168
|
-
padding: 0;
|
|
169
123
|
user-select: none;
|
|
170
124
|
display: block;
|
|
171
125
|
cursor: pointer;
|
|
172
126
|
border-radius: 100%;
|
|
127
|
+
border: 0;
|
|
128
|
+
box-shadow: none;
|
|
129
|
+
padding: 0;
|
|
130
|
+
-webkit-tap-highlight-color: transparent;
|
|
173
131
|
|
|
174
132
|
@include generate.css-map(config.$dot-layout);
|
|
175
133
|
@include base.focusring-round();
|
|
176
|
-
|
|
177
134
|
background-color: sass-map.get(config.$dot, "default");
|
|
178
135
|
|
|
179
136
|
&:hover {
|
|
180
137
|
background-color: sass-map.get(config.$dot, "hover");
|
|
181
138
|
}
|
|
182
139
|
|
|
140
|
+
&:active {
|
|
141
|
+
background-color: sass-map.get(config.$dot, "hover");
|
|
142
|
+
}
|
|
143
|
+
|
|
183
144
|
&.is-active {
|
|
184
|
-
background-color:
|
|
145
|
+
background-color: transparent;
|
|
146
|
+
box-shadow: 0 0 0 2px var(--color-border-accent);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
&.is-active:hover,
|
|
150
|
+
&.is-active:active,
|
|
151
|
+
&.is-active:focus,
|
|
152
|
+
&.is-active:focus-visible {
|
|
153
|
+
background-color: transparent;
|
|
154
|
+
box-shadow: 0 0 0 2px var(--color-border-accent);
|
|
185
155
|
}
|
|
186
156
|
}
|
|
187
157
|
|
|
@@ -13,10 +13,6 @@
|
|
|
13
13
|
@include mixins.bleed-right-variant;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
.container > & {
|
|
17
|
-
@include mixins.base-breakout-buttons;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
16
|
&__viewport-wrapper {
|
|
21
17
|
@include mixins.viewport-wrapper;
|
|
22
18
|
}
|
|
@@ -25,29 +21,20 @@
|
|
|
25
21
|
@include mixins.viewport;
|
|
26
22
|
}
|
|
27
23
|
|
|
28
|
-
&
|
|
29
|
-
@include mixins.
|
|
24
|
+
&__navigation {
|
|
25
|
+
@include mixins.navigation;
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
&
|
|
33
|
-
@include mixins.
|
|
28
|
+
&__controls {
|
|
29
|
+
@include mixins.controls;
|
|
34
30
|
}
|
|
35
31
|
|
|
36
|
-
&
|
|
37
|
-
|
|
38
|
-
@include mixins.button-base;
|
|
39
|
-
|
|
40
|
-
&.is-disabled {
|
|
41
|
-
@include mixins.button-disabled;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
&__prev {
|
|
46
|
-
@include mixins.button-prev;
|
|
32
|
+
&__track {
|
|
33
|
+
@include mixins.track;
|
|
47
34
|
}
|
|
48
35
|
|
|
49
|
-
&
|
|
50
|
-
@include mixins.
|
|
36
|
+
&__slide {
|
|
37
|
+
@include mixins.slide-base;
|
|
51
38
|
}
|
|
52
39
|
|
|
53
40
|
&__pagination {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { render } from "@testing-library/react";
|
|
2
2
|
|
|
3
3
|
import { Carousel } from "../Carousel";
|
|
4
|
+
import { CarouselItem } from "../CarouselItem";
|
|
4
5
|
|
|
5
6
|
//items is required
|
|
6
7
|
describe("rendering Carousel", () => {
|
|
@@ -35,11 +36,11 @@ describe("rendering Carousel", () => {
|
|
|
35
36
|
1,
|
|
36
37
|
);
|
|
37
38
|
});
|
|
38
|
-
it("
|
|
39
|
+
it("does not render scrollbar by default", () => {
|
|
39
40
|
const { container } = render(<Carousel items={[]} />);
|
|
40
41
|
expect(
|
|
41
42
|
container.getElementsByClassName("carousel__scrollbar").length,
|
|
42
|
-
).toBe(
|
|
43
|
+
).toBe(0);
|
|
43
44
|
});
|
|
44
45
|
it("has default class carousel__pagination", () => {
|
|
45
46
|
const { container } = render(<Carousel items={[]} />);
|
|
@@ -47,6 +48,18 @@ describe("rendering Carousel", () => {
|
|
|
47
48
|
container.getElementsByClassName("carousel__pagination").length,
|
|
48
49
|
).toBe(1);
|
|
49
50
|
});
|
|
51
|
+
it("has default class carousel__navigation", () => {
|
|
52
|
+
const { container } = render(<Carousel items={[]} />);
|
|
53
|
+
expect(
|
|
54
|
+
container.getElementsByClassName("carousel__navigation").length,
|
|
55
|
+
).toBe(1);
|
|
56
|
+
});
|
|
57
|
+
it("has default class carousel__controls", () => {
|
|
58
|
+
const { container } = render(<Carousel items={[]} />);
|
|
59
|
+
expect(
|
|
60
|
+
container.getElementsByClassName("carousel__controls").length,
|
|
61
|
+
).toBe(1);
|
|
62
|
+
});
|
|
50
63
|
it("has button with aria-label Naspäť", () => {
|
|
51
64
|
const { container } = render(<Carousel items={[]} />);
|
|
52
65
|
expect(
|
|
@@ -96,18 +109,36 @@ describe("rendering Carousel", () => {
|
|
|
96
109
|
<p key="item2">item2</p>,
|
|
97
110
|
<p key="item3">item3</p>,
|
|
98
111
|
];
|
|
99
|
-
it("
|
|
112
|
+
it("does not render scrollbar element without showScrollbar", () => {
|
|
100
113
|
const { container } = render(<Carousel items={[]} />);
|
|
101
114
|
expect(
|
|
102
115
|
container.getElementsByClassName("carousel__scrollbar").length,
|
|
103
|
-
).toBe(
|
|
116
|
+
).toBe(0);
|
|
104
117
|
});
|
|
105
|
-
it("
|
|
118
|
+
it("does not render scrollbar drag element without showScrollbar", () => {
|
|
106
119
|
const { container } = render(<Carousel items={[]} />);
|
|
120
|
+
expect(
|
|
121
|
+
container.getElementsByClassName("carousel__scrollbar-drag").length,
|
|
122
|
+
).toBe(0);
|
|
123
|
+
});
|
|
124
|
+
it("renders scrollbar when showScrollbar is true", () => {
|
|
125
|
+
const { container } = render(<Carousel items={items} showScrollbar />);
|
|
126
|
+
expect(
|
|
127
|
+
container.getElementsByClassName("carousel__scrollbar").length,
|
|
128
|
+
).toBe(1);
|
|
107
129
|
expect(
|
|
108
130
|
container.getElementsByClassName("carousel__scrollbar-drag").length,
|
|
109
131
|
).toBe(1);
|
|
110
132
|
});
|
|
133
|
+
it("does not render controls row when showScrollbar is true", () => {
|
|
134
|
+
const { container } = render(<Carousel items={items} showScrollbar />);
|
|
135
|
+
expect(
|
|
136
|
+
container.getElementsByClassName("carousel__navigation").length,
|
|
137
|
+
).toBe(0);
|
|
138
|
+
expect(
|
|
139
|
+
container.getElementsByClassName("carousel__controls").length,
|
|
140
|
+
).toBe(0);
|
|
141
|
+
});
|
|
111
142
|
it("pagination container has role tablist", () => {
|
|
112
143
|
const { container } = render(<Carousel items={items} />);
|
|
113
144
|
expect(
|
|
@@ -137,6 +168,47 @@ describe("rendering Carousel", () => {
|
|
|
137
168
|
expect(slides[i].querySelector("p").textContent).toBe(`item${i + 1}`);
|
|
138
169
|
}
|
|
139
170
|
});
|
|
171
|
+
it("uses renderItems callback when provided", () => {
|
|
172
|
+
const data = [{ title: "item1" }, { title: "item2" }, { title: "item3" }];
|
|
173
|
+
const { container } = render(
|
|
174
|
+
<Carousel
|
|
175
|
+
items={data}
|
|
176
|
+
renderItems={(item, index) => <p>{`${index + 1}-${item.title}`}</p>}
|
|
177
|
+
/>,
|
|
178
|
+
);
|
|
179
|
+
const slides = container.getElementsByClassName("carousel__slide");
|
|
180
|
+
for (let i = 0; i < data.length; i++) {
|
|
181
|
+
expect(slides[i].querySelector("p").textContent).toBe(
|
|
182
|
+
`${i + 1}-${data[i].title}`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
it("does not double-wrap CarouselItem elements", () => {
|
|
187
|
+
const items = [
|
|
188
|
+
<CarouselItem key="item1" data-testid="slide-1">
|
|
189
|
+
item1
|
|
190
|
+
</CarouselItem>,
|
|
191
|
+
];
|
|
192
|
+
const { container, getByTestId } = render(<Carousel items={items} />);
|
|
193
|
+
|
|
194
|
+
expect(container.getElementsByClassName("carousel__slide").length).toBe(
|
|
195
|
+
1,
|
|
196
|
+
);
|
|
197
|
+
expect(getByTestId("slide-1").textContent).toBe("item1");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("uses Controls component defaults for built-in navigation", () => {
|
|
201
|
+
const { container } = render(<Carousel items={items} />);
|
|
202
|
+
|
|
203
|
+
expect(
|
|
204
|
+
container.querySelector(
|
|
205
|
+
`.carousel__prev.controls[aria-label="Naspäť"]`,
|
|
206
|
+
),
|
|
207
|
+
).toBeInTheDocument();
|
|
208
|
+
expect(
|
|
209
|
+
container.querySelector(`.carousel__next.controls[aria-label="Ďalej"]`),
|
|
210
|
+
).toBeInTheDocument();
|
|
211
|
+
});
|
|
140
212
|
});
|
|
141
213
|
describe("swiperOptions prop", () => {
|
|
142
214
|
const items = [
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CountdownConfig,
|
|
3
|
+
CountdownLabels,
|
|
4
|
+
CountdownUnit,
|
|
5
|
+
countdownUnits,
|
|
6
|
+
} from "./Countdown.static";
|
|
7
|
+
|
|
8
|
+
const defaultCountdownLabels: Required<CountdownLabels> = {
|
|
9
|
+
days: {
|
|
10
|
+
many: "dni",
|
|
11
|
+
few: "dni",
|
|
12
|
+
one: "deň",
|
|
13
|
+
},
|
|
14
|
+
hours: "hod",
|
|
15
|
+
minutes: "min",
|
|
16
|
+
seconds: "sek",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const defaultCountdownTagLabels: Required<CountdownLabels> = {
|
|
20
|
+
days: "d",
|
|
21
|
+
hours: "h",
|
|
22
|
+
minutes: "m",
|
|
23
|
+
seconds: "s",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const normalizeUnits = (units?: CountdownUnit[]): CountdownUnit[] => {
|
|
27
|
+
if (!units?.length) {
|
|
28
|
+
return [...countdownUnits];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return countdownUnits.filter((unit) => units.includes(unit));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const getLabelText = (
|
|
35
|
+
unit: CountdownUnit,
|
|
36
|
+
labels: CountdownLabels | undefined,
|
|
37
|
+
fallback: Required<CountdownLabels>,
|
|
38
|
+
): string => {
|
|
39
|
+
if (unit === "days") {
|
|
40
|
+
const value = labels?.days ?? fallback.days;
|
|
41
|
+
return typeof value === "string" ? value : value.many;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return labels?.[unit] ?? fallback[unit];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const buildCountdownConfig = (
|
|
48
|
+
targetDate: string,
|
|
49
|
+
units: CountdownUnit[],
|
|
50
|
+
labels?: CountdownLabels,
|
|
51
|
+
): CountdownConfig => ({
|
|
52
|
+
labels,
|
|
53
|
+
targetDate,
|
|
54
|
+
units: normalizeUnits(units),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const getAriaLive = (units: CountdownUnit[]): "off" | "polite" =>
|
|
58
|
+
units.includes("seconds") ? "off" : "polite";
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
buildCountdownConfig,
|
|
62
|
+
defaultCountdownLabels,
|
|
63
|
+
defaultCountdownTagLabels,
|
|
64
|
+
getAriaLive,
|
|
65
|
+
getLabelText,
|
|
66
|
+
normalizeUnits,
|
|
67
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { ATTR_COUNTDOWN_CONFIG, SELECTOR_ANNOUNCEMENT } from "./constants";
|
|
2
|
+
|
|
3
|
+
export const countdownUnits = ["days", "hours", "minutes", "seconds"] as const;
|
|
4
|
+
|
|
5
|
+
export type CountdownUnit = (typeof countdownUnits)[number];
|
|
6
|
+
|
|
7
|
+
export type DayLabels = {
|
|
8
|
+
many: string;
|
|
9
|
+
few: string;
|
|
10
|
+
one: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export interface CountdownLabels {
|
|
14
|
+
days?: string | DayLabels;
|
|
15
|
+
hours?: string;
|
|
16
|
+
minutes?: string;
|
|
17
|
+
seconds?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CountdownConfig {
|
|
21
|
+
targetDate: string;
|
|
22
|
+
units?: CountdownUnit[];
|
|
23
|
+
labels?: CountdownLabels;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CountdownState {
|
|
27
|
+
days: number;
|
|
28
|
+
hours: number;
|
|
29
|
+
minutes: number;
|
|
30
|
+
seconds: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const unitMs: Record<CountdownUnit, number> = {
|
|
34
|
+
days: 24 * 60 * 60 * 1000,
|
|
35
|
+
hours: 60 * 60 * 1000,
|
|
36
|
+
minutes: 60 * 1000,
|
|
37
|
+
seconds: 1000,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const getVisibleUnits = (units?: CountdownUnit[]): CountdownUnit[] => {
|
|
41
|
+
if (!units?.length) {
|
|
42
|
+
return [...countdownUnits];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return countdownUnits.filter((unit) => units.includes(unit));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const normalizeDayLabels = (
|
|
49
|
+
value: string | DayLabels | undefined,
|
|
50
|
+
fallback: DayLabels,
|
|
51
|
+
): DayLabels => {
|
|
52
|
+
if (typeof value === "string" && value.trim()) {
|
|
53
|
+
return {
|
|
54
|
+
many: value,
|
|
55
|
+
few: value,
|
|
56
|
+
one: value,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
value &&
|
|
62
|
+
typeof value === "object" &&
|
|
63
|
+
typeof value.many === "string" &&
|
|
64
|
+
typeof value.few === "string" &&
|
|
65
|
+
typeof value.one === "string"
|
|
66
|
+
) {
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return fallback;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const getTargetDate = (value: string): number => {
|
|
74
|
+
const timestamp = new Date(value).getTime();
|
|
75
|
+
return Number.isNaN(timestamp) ? 0 : timestamp;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const calculateState = (
|
|
79
|
+
targetDate: string,
|
|
80
|
+
units: CountdownUnit[],
|
|
81
|
+
): CountdownState => {
|
|
82
|
+
const target = getTargetDate(targetDate);
|
|
83
|
+
let remaining = Math.max(0, target - Date.now());
|
|
84
|
+
|
|
85
|
+
const values: CountdownState = {
|
|
86
|
+
days: 0,
|
|
87
|
+
hours: 0,
|
|
88
|
+
minutes: 0,
|
|
89
|
+
seconds: 0,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
units.forEach((unit) => {
|
|
93
|
+
const currentValue = Math.floor(remaining / unitMs[unit]);
|
|
94
|
+
values[unit] = currentValue;
|
|
95
|
+
remaining -= currentValue * unitMs[unit];
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return values;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export abstract class BaseCountdownStatic {
|
|
102
|
+
element: HTMLElement;
|
|
103
|
+
config: CountdownConfig;
|
|
104
|
+
liveRegion: HTMLElement | null;
|
|
105
|
+
timerId?: number;
|
|
106
|
+
observer?: IntersectionObserver;
|
|
107
|
+
private instanceKey: string;
|
|
108
|
+
private defaultConfig: CountdownConfig;
|
|
109
|
+
|
|
110
|
+
constructor(
|
|
111
|
+
element: HTMLElement,
|
|
112
|
+
config: Partial<CountdownConfig> | undefined,
|
|
113
|
+
options: {
|
|
114
|
+
defaultConfig: CountdownConfig;
|
|
115
|
+
instanceKey: string;
|
|
116
|
+
},
|
|
117
|
+
) {
|
|
118
|
+
this.element = element;
|
|
119
|
+
this.defaultConfig = options.defaultConfig;
|
|
120
|
+
this.instanceKey = options.instanceKey;
|
|
121
|
+
this.config = { ...options.defaultConfig, ...(config ?? {}) };
|
|
122
|
+
this.liveRegion = null;
|
|
123
|
+
|
|
124
|
+
this.tick = this.tick.bind(this);
|
|
125
|
+
this.start = this.start.bind(this);
|
|
126
|
+
this.stop = this.stop.bind(this);
|
|
127
|
+
|
|
128
|
+
(this.element as any)[this.instanceKey] = this;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
protected readDataConfig(): Partial<CountdownConfig> {
|
|
132
|
+
const attr = this.element.getAttribute(ATTR_COUNTDOWN_CONFIG);
|
|
133
|
+
|
|
134
|
+
if (!attr) {
|
|
135
|
+
return {};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
return JSON.parse(attr);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.warn("Invalid JSON in data-countdown-config attribute", error);
|
|
142
|
+
return {};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
protected getLiveRegion(): HTMLElement | null {
|
|
147
|
+
return this.element.querySelector(SELECTOR_ANNOUNCEMENT);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
protected scheduleNextTick(): void {
|
|
151
|
+
this.stop();
|
|
152
|
+
const nextDelay = 1000 - (Date.now() % 1000) || 1000;
|
|
153
|
+
this.timerId = window.setTimeout(this.tick, nextDelay);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private tick(): void {
|
|
157
|
+
this.render();
|
|
158
|
+
this.scheduleNextTick();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private setupObserver(): void {
|
|
162
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
163
|
+
this.start();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.observer = new IntersectionObserver((entries) => {
|
|
168
|
+
entries.forEach((entry) => {
|
|
169
|
+
if (entry.isIntersecting) {
|
|
170
|
+
this.start();
|
|
171
|
+
} else {
|
|
172
|
+
this.stop();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
this.observer.observe(this.element);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
init(): void {
|
|
181
|
+
const elementConfig = this.readDataConfig();
|
|
182
|
+
|
|
183
|
+
this.config = {
|
|
184
|
+
...this.defaultConfig,
|
|
185
|
+
...elementConfig,
|
|
186
|
+
...this.config,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
this.liveRegion = this.getLiveRegion();
|
|
190
|
+
this.collectElements();
|
|
191
|
+
this.render();
|
|
192
|
+
this.setupObserver();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
start(): void {
|
|
196
|
+
this.render();
|
|
197
|
+
this.scheduleNextTick();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
stop(): void {
|
|
201
|
+
if (this.timerId) {
|
|
202
|
+
window.clearTimeout(this.timerId);
|
|
203
|
+
this.timerId = undefined;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
update(config?: Partial<CountdownConfig>): void {
|
|
208
|
+
this.stop();
|
|
209
|
+
this.observer?.disconnect();
|
|
210
|
+
this.destroyElements();
|
|
211
|
+
this.config = {
|
|
212
|
+
...this.config,
|
|
213
|
+
...(config ?? {}),
|
|
214
|
+
};
|
|
215
|
+
this.init();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
destroy(): void {
|
|
219
|
+
this.stop();
|
|
220
|
+
this.observer?.disconnect();
|
|
221
|
+
this.destroyElements();
|
|
222
|
+
(this.element as any)[this.instanceKey] = null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
protected abstract collectElements(): void;
|
|
226
|
+
protected abstract render(): void;
|
|
227
|
+
protected abstract destroyElements(): void;
|
|
228
|
+
}
|