@orangesk/orange-design-system 3.2.0 → 3.3.1
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/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/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/PromoBanner/style.css +1 -1
- package/build/components/PromoBanner/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 +53 -13
- 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/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 +8 -2
- 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 +4 -2
- 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 +31 -30
- package/src/assets/icons/quote.svg +1 -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/Carousel/Carousel.static.ts +17 -3
- package/src/components/Carousel/Carousel.tsx +28 -24
- 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 +48 -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 +1 -0
- package/src/components/Forms/Autocomplete/styles/style.scss +4 -0
- package/src/components/Forms/Autocomplete/tests/Autocomplete.styles.unit.test.jsx +3 -0
- 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 +31 -7
- package/src/components/PromoBanner/styles/mixins.scss +16 -0
- package/src/components/PromoBanner/styles/style.scss +4 -0
- package/src/components/PromoBanner/tests/PromoBanner.unit.test.jsx +49 -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 +7 -2
- package/src/styles/export/color.js +2 -0
- package/src/styles/tokens/color.scss +2 -0
- package/src/styles/utilities/text.scss +6 -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 {
|
|
@@ -36,11 +36,11 @@ describe("rendering Carousel", () => {
|
|
|
36
36
|
1,
|
|
37
37
|
);
|
|
38
38
|
});
|
|
39
|
-
it("
|
|
39
|
+
it("does not render scrollbar by default", () => {
|
|
40
40
|
const { container } = render(<Carousel items={[]} />);
|
|
41
41
|
expect(
|
|
42
42
|
container.getElementsByClassName("carousel__scrollbar").length,
|
|
43
|
-
).toBe(
|
|
43
|
+
).toBe(0);
|
|
44
44
|
});
|
|
45
45
|
it("has default class carousel__pagination", () => {
|
|
46
46
|
const { container } = render(<Carousel items={[]} />);
|
|
@@ -48,6 +48,18 @@ describe("rendering Carousel", () => {
|
|
|
48
48
|
container.getElementsByClassName("carousel__pagination").length,
|
|
49
49
|
).toBe(1);
|
|
50
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
|
+
});
|
|
51
63
|
it("has button with aria-label Naspäť", () => {
|
|
52
64
|
const { container } = render(<Carousel items={[]} />);
|
|
53
65
|
expect(
|
|
@@ -97,18 +109,36 @@ describe("rendering Carousel", () => {
|
|
|
97
109
|
<p key="item2">item2</p>,
|
|
98
110
|
<p key="item3">item3</p>,
|
|
99
111
|
];
|
|
100
|
-
it("
|
|
112
|
+
it("does not render scrollbar element without showScrollbar", () => {
|
|
101
113
|
const { container } = render(<Carousel items={[]} />);
|
|
102
114
|
expect(
|
|
103
115
|
container.getElementsByClassName("carousel__scrollbar").length,
|
|
104
|
-
).toBe(
|
|
116
|
+
).toBe(0);
|
|
105
117
|
});
|
|
106
|
-
it("
|
|
118
|
+
it("does not render scrollbar drag element without showScrollbar", () => {
|
|
107
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);
|
|
108
129
|
expect(
|
|
109
130
|
container.getElementsByClassName("carousel__scrollbar-drag").length,
|
|
110
131
|
).toBe(1);
|
|
111
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
|
+
});
|
|
112
142
|
it("pagination container has role tablist", () => {
|
|
113
143
|
const { container } = render(<Carousel items={items} />);
|
|
114
144
|
expect(
|
|
@@ -166,6 +196,19 @@ describe("rendering Carousel", () => {
|
|
|
166
196
|
);
|
|
167
197
|
expect(getByTestId("slide-1").textContent).toBe("item1");
|
|
168
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
|
+
});
|
|
169
212
|
});
|
|
170
213
|
describe("swiperOptions prop", () => {
|
|
171
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
|
+
}
|