@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
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseCountdownStatic,
|
|
3
|
+
CountdownConfig,
|
|
4
|
+
CountdownLabels,
|
|
5
|
+
CountdownUnit,
|
|
6
|
+
calculateState,
|
|
7
|
+
countdownUnits,
|
|
8
|
+
DayLabels,
|
|
9
|
+
getVisibleUnits,
|
|
10
|
+
normalizeDayLabels,
|
|
11
|
+
} from "./Countdown.shared";
|
|
12
|
+
import {
|
|
13
|
+
ATTR_COUNTDOWN_UNIT,
|
|
14
|
+
CLASS_FLIP,
|
|
15
|
+
SELECTOR_LABEL,
|
|
16
|
+
SELECTOR_UNIT,
|
|
17
|
+
SELECTOR_VALUE_LAYERS,
|
|
18
|
+
} from "./constants";
|
|
19
|
+
|
|
20
|
+
interface CountdownLeaf {
|
|
21
|
+
element: HTMLElement;
|
|
22
|
+
unit: CountdownUnit;
|
|
23
|
+
labels: HTMLElement[];
|
|
24
|
+
isFlipping: boolean;
|
|
25
|
+
flipTimeoutId?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const defaultDayLabels: DayLabels = {
|
|
29
|
+
many: "dni",
|
|
30
|
+
few: "dni",
|
|
31
|
+
one: "deň",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const defaultLabels: Required<CountdownLabels> = {
|
|
35
|
+
days: defaultDayLabels,
|
|
36
|
+
hours: "hod",
|
|
37
|
+
minutes: "min",
|
|
38
|
+
seconds: "sek",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const defaultConfig: CountdownConfig = {
|
|
42
|
+
targetDate: "",
|
|
43
|
+
units: [...countdownUnits],
|
|
44
|
+
labels: defaultLabels,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const FLIP_DURATION_MS = 600;
|
|
48
|
+
|
|
49
|
+
const pad = (value: number): string =>
|
|
50
|
+
String(Math.max(0, value)).padStart(2, "0");
|
|
51
|
+
|
|
52
|
+
const getUnitLabel = (
|
|
53
|
+
unit: CountdownUnit,
|
|
54
|
+
value: number,
|
|
55
|
+
labels: Required<CountdownLabels>,
|
|
56
|
+
): string => {
|
|
57
|
+
if (unit !== "days") {
|
|
58
|
+
return labels[unit];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const { many, few, one } = normalizeDayLabels(labels.days, defaultDayLabels);
|
|
62
|
+
|
|
63
|
+
if (value === 1) return one;
|
|
64
|
+
if (value >= 2 && value <= 4) return few;
|
|
65
|
+
return many;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default class CountdownStatic extends BaseCountdownStatic {
|
|
69
|
+
leaves!: Map<CountdownUnit, CountdownLeaf>;
|
|
70
|
+
|
|
71
|
+
constructor(element: HTMLElement, config?: Partial<CountdownConfig>) {
|
|
72
|
+
super(element, config, {
|
|
73
|
+
defaultConfig,
|
|
74
|
+
instanceKey: "ODS_Countdown",
|
|
75
|
+
});
|
|
76
|
+
this.leaves = new Map();
|
|
77
|
+
this.init();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private getLabels(): Required<CountdownLabels> {
|
|
81
|
+
return {
|
|
82
|
+
...defaultLabels,
|
|
83
|
+
...(this.config.labels ?? {}),
|
|
84
|
+
days: normalizeDayLabels(this.config.labels?.days, defaultDayLabels),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected collectElements(): void {
|
|
89
|
+
this.leaves.clear();
|
|
90
|
+
|
|
91
|
+
const visibleUnits = getVisibleUnits(this.config.units);
|
|
92
|
+
|
|
93
|
+
Array.from(
|
|
94
|
+
this.element.querySelectorAll<HTMLElement>(SELECTOR_UNIT),
|
|
95
|
+
).forEach((leaf) => {
|
|
96
|
+
const unit = leaf.getAttribute(
|
|
97
|
+
ATTR_COUNTDOWN_UNIT,
|
|
98
|
+
) as CountdownUnit | null;
|
|
99
|
+
|
|
100
|
+
if (!unit || !visibleUnits.includes(unit)) return;
|
|
101
|
+
|
|
102
|
+
this.leaves.set(unit, {
|
|
103
|
+
element: leaf,
|
|
104
|
+
unit,
|
|
105
|
+
labels: Array.from(leaf.querySelectorAll(SELECTOR_VALUE_LAYERS)),
|
|
106
|
+
isFlipping: false,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private setLeafValue(leaf: CountdownLeaf, value: string): void {
|
|
112
|
+
leaf.labels.forEach((label) => {
|
|
113
|
+
label.replaceChildren(document.createTextNode(value));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private flipLeaf(leaf: CountdownLeaf, value: string): void {
|
|
118
|
+
const [topLabel, frontLabel, backLabel, bottomLabel] = leaf.labels;
|
|
119
|
+
|
|
120
|
+
if (!topLabel || !frontLabel || !backLabel || !bottomLabel) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!topLabel.textContent) {
|
|
125
|
+
this.setLeafValue(leaf, value);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (leaf.isFlipping || topLabel.textContent === value) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (leaf.flipTimeoutId) {
|
|
134
|
+
window.clearTimeout(leaf.flipTimeoutId);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
leaf.isFlipping = true;
|
|
138
|
+
topLabel.replaceChildren(document.createTextNode(value));
|
|
139
|
+
backLabel.replaceChildren(document.createTextNode(value));
|
|
140
|
+
leaf.element.classList.add(CLASS_FLIP);
|
|
141
|
+
|
|
142
|
+
leaf.flipTimeoutId = window.setTimeout(() => {
|
|
143
|
+
frontLabel.replaceChildren(document.createTextNode(value));
|
|
144
|
+
bottomLabel.replaceChildren(document.createTextNode(value));
|
|
145
|
+
leaf.element.classList.remove(CLASS_FLIP);
|
|
146
|
+
leaf.isFlipping = false;
|
|
147
|
+
leaf.flipTimeoutId = undefined;
|
|
148
|
+
}, FLIP_DURATION_MS);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
protected render(): void {
|
|
152
|
+
const units = getVisibleUnits(this.config.units);
|
|
153
|
+
const labels = this.getLabels();
|
|
154
|
+
const state = calculateState(this.config.targetDate, units);
|
|
155
|
+
|
|
156
|
+
units.forEach((unit) => {
|
|
157
|
+
const leaf = this.leaves.get(unit);
|
|
158
|
+
|
|
159
|
+
if (!leaf) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const value = state[unit];
|
|
164
|
+
const label = getUnitLabel(unit, value, labels);
|
|
165
|
+
|
|
166
|
+
this.flipLeaf(leaf, pad(value));
|
|
167
|
+
const labelElement = leaf.element.querySelector(SELECTOR_LABEL);
|
|
168
|
+
if (labelElement) {
|
|
169
|
+
labelElement.textContent = label;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (this.liveRegion) {
|
|
174
|
+
const announcement = units
|
|
175
|
+
.map(
|
|
176
|
+
(unit) => `${state[unit]} ${getUnitLabel(unit, state[unit], labels)}`,
|
|
177
|
+
)
|
|
178
|
+
.join(", ");
|
|
179
|
+
this.liveRegion.textContent = announcement;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
protected destroyElements(): void {
|
|
184
|
+
this.leaves.forEach((leaf) => {
|
|
185
|
+
if (leaf.flipTimeoutId) {
|
|
186
|
+
window.clearTimeout(leaf.flipTimeoutId);
|
|
187
|
+
leaf.flipTimeoutId = undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
leaf.isFlipping = false;
|
|
191
|
+
leaf.element.classList.remove(CLASS_FLIP);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static getInstance(el: HTMLElement): CountdownStatic | null {
|
|
196
|
+
return el && (el as any).ODS_Countdown ? (el as any).ODS_Countdown : null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export type { CountdownConfig, CountdownLabels, CountdownUnit };
|
|
201
|
+
export { countdownUnits };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import cx from "classnames";
|
|
4
|
+
import React from "react";
|
|
5
|
+
|
|
6
|
+
import { useStatic } from "../../utils/hooks";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
buildCountdownConfig,
|
|
10
|
+
defaultCountdownLabels,
|
|
11
|
+
getAriaLive,
|
|
12
|
+
getLabelText,
|
|
13
|
+
normalizeUnits,
|
|
14
|
+
} from "./Countdown.react";
|
|
15
|
+
import CountdownStatic, {
|
|
16
|
+
CountdownConfig,
|
|
17
|
+
CountdownLabels,
|
|
18
|
+
CountdownUnit,
|
|
19
|
+
countdownUnits,
|
|
20
|
+
} from "./Countdown.static";
|
|
21
|
+
import {
|
|
22
|
+
ATTR_COUNTDOWN,
|
|
23
|
+
ATTR_COUNTDOWN_CONFIG,
|
|
24
|
+
ATTR_COUNTDOWN_UNIT,
|
|
25
|
+
CLASS_ANNOUNCEMENT,
|
|
26
|
+
CLASS_BOTTOM,
|
|
27
|
+
CLASS_CARD,
|
|
28
|
+
CLASS_LABEL,
|
|
29
|
+
CLASS_LEAF_BACK,
|
|
30
|
+
CLASS_LEAF_FRONT,
|
|
31
|
+
CLASS_ROOT,
|
|
32
|
+
CLASS_TOP,
|
|
33
|
+
CLASS_UNIT,
|
|
34
|
+
CLASS_VALUE,
|
|
35
|
+
} from "./constants";
|
|
36
|
+
|
|
37
|
+
export interface CountdownProps
|
|
38
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
39
|
+
labels?: CountdownLabels;
|
|
40
|
+
targetDate: string;
|
|
41
|
+
units?: CountdownUnit[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const Countdown: React.FC<CountdownProps> = ({
|
|
45
|
+
className,
|
|
46
|
+
labels,
|
|
47
|
+
targetDate,
|
|
48
|
+
units = [...countdownUnits],
|
|
49
|
+
...other
|
|
50
|
+
}) => {
|
|
51
|
+
const normalizedUnits = normalizeUnits(units);
|
|
52
|
+
const config: CountdownConfig = buildCountdownConfig(
|
|
53
|
+
targetDate,
|
|
54
|
+
normalizedUnits,
|
|
55
|
+
labels,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const [elementRef] = useStatic(CountdownStatic, config);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
className={cx(CLASS_ROOT, className)}
|
|
63
|
+
{...{ [ATTR_COUNTDOWN]: "true" }}
|
|
64
|
+
{...{ [ATTR_COUNTDOWN_CONFIG]: JSON.stringify(config) }}
|
|
65
|
+
ref={elementRef}
|
|
66
|
+
role="timer"
|
|
67
|
+
aria-live={getAriaLive(normalizedUnits)}
|
|
68
|
+
aria-atomic="true"
|
|
69
|
+
{...other}
|
|
70
|
+
>
|
|
71
|
+
<span className={`sr-only ${CLASS_ANNOUNCEMENT}`} />
|
|
72
|
+
{normalizedUnits.map((unit) => (
|
|
73
|
+
<div
|
|
74
|
+
key={unit}
|
|
75
|
+
className={CLASS_UNIT}
|
|
76
|
+
{...{ [ATTR_COUNTDOWN_UNIT]: unit }}
|
|
77
|
+
aria-hidden="true"
|
|
78
|
+
>
|
|
79
|
+
<div className={CLASS_CARD}>
|
|
80
|
+
<div className={CLASS_TOP}>
|
|
81
|
+
<span className={CLASS_VALUE}>00</span>
|
|
82
|
+
</div>
|
|
83
|
+
<div className={CLASS_LEAF_FRONT}>
|
|
84
|
+
<span className={CLASS_VALUE}>00</span>
|
|
85
|
+
</div>
|
|
86
|
+
<div className={CLASS_LEAF_BACK}>
|
|
87
|
+
<span className={CLASS_VALUE}>00</span>
|
|
88
|
+
</div>
|
|
89
|
+
<div className={CLASS_BOTTOM}>
|
|
90
|
+
<span className={CLASS_VALUE}>00</span>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
<span className={CLASS_LABEL}>
|
|
94
|
+
{getLabelText(unit, labels, defaultCountdownLabels)}
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
))}
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
Countdown.displayName = "Countdown";
|
|
103
|
+
|
|
104
|
+
export type { CountdownLabels, CountdownUnit };
|
|
105
|
+
export { Countdown, countdownUnits };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseCountdownStatic,
|
|
3
|
+
CountdownConfig,
|
|
4
|
+
CountdownLabels,
|
|
5
|
+
CountdownUnit,
|
|
6
|
+
calculateState,
|
|
7
|
+
DayLabels,
|
|
8
|
+
getVisibleUnits,
|
|
9
|
+
normalizeDayLabels,
|
|
10
|
+
} from "./Countdown.shared";
|
|
11
|
+
import {
|
|
12
|
+
ATTR_COUNTDOWN_UNIT,
|
|
13
|
+
SELECTOR_TAG_LABEL,
|
|
14
|
+
SELECTOR_TAG_UNIT,
|
|
15
|
+
SELECTOR_TAG_VALUE,
|
|
16
|
+
} from "./constants";
|
|
17
|
+
|
|
18
|
+
interface CountdownTagUnit {
|
|
19
|
+
element: HTMLElement;
|
|
20
|
+
unit: CountdownUnit;
|
|
21
|
+
valueElement: HTMLElement | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const defaultDayLabels: DayLabels = {
|
|
25
|
+
many: "d",
|
|
26
|
+
few: "d",
|
|
27
|
+
one: "d",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const defaultLabels: Required<CountdownLabels> = {
|
|
31
|
+
days: defaultDayLabels,
|
|
32
|
+
hours: "h",
|
|
33
|
+
minutes: "m",
|
|
34
|
+
seconds: "s",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const defaultConfig: CountdownConfig = {
|
|
38
|
+
targetDate: "",
|
|
39
|
+
units: ["days", "hours", "minutes", "seconds"],
|
|
40
|
+
labels: defaultLabels,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const getUnitLabel = (
|
|
44
|
+
unit: CountdownUnit,
|
|
45
|
+
labels: Required<CountdownLabels>,
|
|
46
|
+
): string => {
|
|
47
|
+
if (unit === "days") {
|
|
48
|
+
return normalizeDayLabels(labels.days, defaultDayLabels).many;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return labels[unit];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const formatValue = (value: number): string =>
|
|
55
|
+
value < 100
|
|
56
|
+
? String(Math.max(0, value)).padStart(2, "0")
|
|
57
|
+
: String(Math.max(0, value));
|
|
58
|
+
|
|
59
|
+
export default class CountdownTagStatic extends BaseCountdownStatic {
|
|
60
|
+
units!: Map<CountdownUnit, CountdownTagUnit>;
|
|
61
|
+
|
|
62
|
+
constructor(element: HTMLElement, config?: Partial<CountdownConfig>) {
|
|
63
|
+
super(element, config, {
|
|
64
|
+
defaultConfig,
|
|
65
|
+
instanceKey: "ODS_CountdownTag",
|
|
66
|
+
});
|
|
67
|
+
this.units = new Map();
|
|
68
|
+
this.init();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private getLabels(): Required<CountdownLabels> {
|
|
72
|
+
return {
|
|
73
|
+
...defaultLabels,
|
|
74
|
+
...(this.config.labels ?? {}),
|
|
75
|
+
days: normalizeDayLabels(this.config.labels?.days, defaultDayLabels),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected collectElements(): void {
|
|
80
|
+
this.units.clear();
|
|
81
|
+
|
|
82
|
+
const visibleUnits = getVisibleUnits(this.config.units);
|
|
83
|
+
|
|
84
|
+
Array.from(
|
|
85
|
+
this.element.querySelectorAll<HTMLElement>(SELECTOR_TAG_UNIT),
|
|
86
|
+
).forEach((unitElement) => {
|
|
87
|
+
const unit = unitElement.getAttribute(
|
|
88
|
+
ATTR_COUNTDOWN_UNIT,
|
|
89
|
+
) as CountdownUnit | null;
|
|
90
|
+
|
|
91
|
+
if (!unit || !visibleUnits.includes(unit)) return;
|
|
92
|
+
|
|
93
|
+
this.units.set(unit, {
|
|
94
|
+
element: unitElement,
|
|
95
|
+
unit,
|
|
96
|
+
valueElement: unitElement.querySelector(SELECTOR_TAG_VALUE),
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
protected render(): void {
|
|
102
|
+
const visibleUnits = getVisibleUnits(this.config.units);
|
|
103
|
+
const labels = this.getLabels();
|
|
104
|
+
const state = calculateState(this.config.targetDate, visibleUnits);
|
|
105
|
+
|
|
106
|
+
visibleUnits.forEach((unit) => {
|
|
107
|
+
const currentUnit = this.units.get(unit);
|
|
108
|
+
|
|
109
|
+
if (!currentUnit) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const value = state[unit];
|
|
114
|
+
|
|
115
|
+
if (currentUnit.valueElement) {
|
|
116
|
+
currentUnit.valueElement.textContent = formatValue(value);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const labelElement =
|
|
120
|
+
currentUnit.element.querySelector(SELECTOR_TAG_LABEL);
|
|
121
|
+
if (labelElement) {
|
|
122
|
+
labelElement.textContent = getUnitLabel(unit, labels);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (this.liveRegion) {
|
|
127
|
+
this.liveRegion.textContent = visibleUnits
|
|
128
|
+
.map((unit) => `${state[unit]} ${getUnitLabel(unit, labels)}`)
|
|
129
|
+
.join(", ");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
protected destroyElements(): void {}
|
|
134
|
+
|
|
135
|
+
static getInstance(el: HTMLElement): CountdownTagStatic | null {
|
|
136
|
+
return el && (el as any).ODS_CountdownTag
|
|
137
|
+
? (el as any).ODS_CountdownTag
|
|
138
|
+
: null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import cx from "classnames";
|
|
4
|
+
import React from "react";
|
|
5
|
+
|
|
6
|
+
import { useStatic } from "../../utils/hooks";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
buildCountdownConfig,
|
|
10
|
+
defaultCountdownTagLabels,
|
|
11
|
+
getAriaLive,
|
|
12
|
+
getLabelText,
|
|
13
|
+
normalizeUnits,
|
|
14
|
+
} from "./Countdown.react";
|
|
15
|
+
import {
|
|
16
|
+
CountdownConfig,
|
|
17
|
+
CountdownLabels,
|
|
18
|
+
CountdownUnit,
|
|
19
|
+
} from "./Countdown.static";
|
|
20
|
+
import CountdownTagStatic from "./CountdownTag.static";
|
|
21
|
+
import {
|
|
22
|
+
ATTR_COUNTDOWN_CONFIG,
|
|
23
|
+
ATTR_COUNTDOWN_TAG,
|
|
24
|
+
ATTR_COUNTDOWN_TAG_VALUE,
|
|
25
|
+
ATTR_COUNTDOWN_UNIT,
|
|
26
|
+
CLASS_ANNOUNCEMENT,
|
|
27
|
+
CLASS_TAG_LABEL,
|
|
28
|
+
CLASS_TAG_PART,
|
|
29
|
+
CLASS_TAG_ROOT,
|
|
30
|
+
CLASS_TAG_SEPARATOR,
|
|
31
|
+
CLASS_TAG_VALUE,
|
|
32
|
+
} from "./constants";
|
|
33
|
+
|
|
34
|
+
export interface CountdownTagProps
|
|
35
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
36
|
+
labels?: CountdownLabels;
|
|
37
|
+
targetDate: string;
|
|
38
|
+
units?: CountdownUnit[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const CountdownTag: React.FC<CountdownTagProps> = ({
|
|
42
|
+
className,
|
|
43
|
+
labels,
|
|
44
|
+
targetDate,
|
|
45
|
+
units = ["days", "hours", "minutes", "seconds"],
|
|
46
|
+
...other
|
|
47
|
+
}) => {
|
|
48
|
+
const normalizedUnits = normalizeUnits(units);
|
|
49
|
+
const config: CountdownConfig = buildCountdownConfig(
|
|
50
|
+
targetDate,
|
|
51
|
+
normalizedUnits,
|
|
52
|
+
labels,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const [elementRef] = useStatic(CountdownTagStatic, config);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
className={cx(CLASS_TAG_ROOT, className)}
|
|
60
|
+
{...{ [ATTR_COUNTDOWN_TAG]: "true" }}
|
|
61
|
+
{...{ [ATTR_COUNTDOWN_CONFIG]: JSON.stringify(config) }}
|
|
62
|
+
ref={elementRef}
|
|
63
|
+
role="timer"
|
|
64
|
+
aria-live={getAriaLive(normalizedUnits)}
|
|
65
|
+
aria-atomic="true"
|
|
66
|
+
{...other}
|
|
67
|
+
>
|
|
68
|
+
<span className={`sr-only ${CLASS_ANNOUNCEMENT}`} />
|
|
69
|
+
{normalizedUnits.map((unit, index) => (
|
|
70
|
+
<React.Fragment key={unit}>
|
|
71
|
+
<span
|
|
72
|
+
className={CLASS_TAG_PART}
|
|
73
|
+
{...{ [ATTR_COUNTDOWN_UNIT]: unit }}
|
|
74
|
+
aria-hidden="true"
|
|
75
|
+
>
|
|
76
|
+
<span
|
|
77
|
+
className={CLASS_TAG_VALUE}
|
|
78
|
+
{...{ [ATTR_COUNTDOWN_TAG_VALUE]: "true" }}
|
|
79
|
+
>
|
|
80
|
+
00
|
|
81
|
+
</span>
|
|
82
|
+
<span className={CLASS_TAG_LABEL}>
|
|
83
|
+
{getLabelText(unit, labels, defaultCountdownTagLabels)}
|
|
84
|
+
</span>
|
|
85
|
+
</span>
|
|
86
|
+
{index < normalizedUnits.length - 1 && (
|
|
87
|
+
<span className={CLASS_TAG_SEPARATOR} aria-hidden="true">
|
|
88
|
+
:
|
|
89
|
+
</span>
|
|
90
|
+
)}
|
|
91
|
+
</React.Fragment>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
CountdownTag.displayName = "CountdownTag";
|
|
98
|
+
|
|
99
|
+
export { CountdownTag };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const CLASS_ROOT = "countdown";
|
|
2
|
+
|
|
3
|
+
export const CLASS_ANNOUNCEMENT = `${CLASS_ROOT}__announcement`;
|
|
4
|
+
export const CLASS_UNIT = `${CLASS_ROOT}__unit`;
|
|
5
|
+
export const CLASS_CARD = `${CLASS_ROOT}__card`;
|
|
6
|
+
export const CLASS_TOP = `${CLASS_ROOT}__top`;
|
|
7
|
+
export const CLASS_LEAF_FRONT = `${CLASS_ROOT}__leaf-front`;
|
|
8
|
+
export const CLASS_LEAF_BACK = `${CLASS_ROOT}__leaf-back`;
|
|
9
|
+
export const CLASS_BOTTOM = `${CLASS_ROOT}__bottom`;
|
|
10
|
+
export const CLASS_VALUE = `${CLASS_ROOT}__value`;
|
|
11
|
+
export const CLASS_LABEL = `${CLASS_ROOT}__label`;
|
|
12
|
+
export const CLASS_FLIP = "is-flipping";
|
|
13
|
+
export const CLASS_TAG_ROOT = "countdown-tag";
|
|
14
|
+
export const CLASS_TAG_PART = `${CLASS_TAG_ROOT}__part`;
|
|
15
|
+
export const CLASS_TAG_VALUE = `${CLASS_TAG_ROOT}__value`;
|
|
16
|
+
export const CLASS_TAG_LABEL = `${CLASS_TAG_ROOT}__label`;
|
|
17
|
+
export const CLASS_TAG_SEPARATOR = `${CLASS_TAG_ROOT}__separator`;
|
|
18
|
+
|
|
19
|
+
export const ATTR_COUNTDOWN = "data-countdown";
|
|
20
|
+
export const ATTR_COUNTDOWN_CONFIG = "data-countdown-config";
|
|
21
|
+
export const ATTR_COUNTDOWN_UNIT = "data-countdown-unit";
|
|
22
|
+
export const ATTR_COUNTDOWN_TAG = "data-countdown-tag";
|
|
23
|
+
export const ATTR_COUNTDOWN_TAG_VALUE = "data-countdown-tag-value";
|
|
24
|
+
|
|
25
|
+
export const SELECTOR_ANNOUNCEMENT = `.${CLASS_ANNOUNCEMENT}`;
|
|
26
|
+
export const SELECTOR_UNIT = `.${CLASS_UNIT}`;
|
|
27
|
+
export const SELECTOR_LABEL = `.${CLASS_LABEL}`;
|
|
28
|
+
export const SELECTOR_TAG_UNIT = `[${ATTR_COUNTDOWN_UNIT}]`;
|
|
29
|
+
export const SELECTOR_TAG_LABEL = `.${CLASS_TAG_LABEL}`;
|
|
30
|
+
export const SELECTOR_TAG_VALUE = `[${ATTR_COUNTDOWN_TAG_VALUE}]`;
|
|
31
|
+
export const SELECTOR_VALUE_LAYERS = [
|
|
32
|
+
`.${CLASS_TOP} .${CLASS_VALUE}`,
|
|
33
|
+
`.${CLASS_LEAF_FRONT} .${CLASS_VALUE}`,
|
|
34
|
+
`.${CLASS_LEAF_BACK} .${CLASS_VALUE}`,
|
|
35
|
+
`.${CLASS_BOTTOM} .${CLASS_VALUE}`,
|
|
36
|
+
].join(", ");
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
@use "../../../styles/tools/convert";
|
|
2
|
+
|
|
3
|
+
$unit-width: convert.to-rem(67.5px);
|
|
4
|
+
$unit-height: convert.to-rem(82px);
|
|
5
|
+
$unit-half-height: convert.to-rem(41px);
|
|
6
|
+
$unit-radius: convert.to-rem(10px);
|
|
7
|
+
$tag-gap: convert.to-rem(4px);
|
|
8
|
+
$tag-min-height: convert.to-rem(40px);
|
|
9
|
+
$tag-padding-y: convert.to-rem(9px);
|
|
10
|
+
$tag-padding-x: convert.to-rem(15px);
|
|
11
|
+
$tag-part-gap: convert.to-rem(3px);
|
|
12
|
+
$tag-value-font-size: convert.to-rem(16px);
|
|
13
|
+
$tag-value-line-height: convert.to-rem(18px);
|
|
14
|
+
$tag-value-letter-spacing: convert.to-rem(-0.2px);
|
|
15
|
+
$tag-label-font-size: convert.to-rem(12px);
|
|
16
|
+
$tag-label-line-height: convert.to-rem(14px);
|
|
17
|
+
$tag-separator-offset-y: convert.to-rem(-1px);
|