@orangesk/orange-design-system 3.2.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.
Files changed (118) hide show
  1. package/build/components/Avatar/style.css +2 -0
  2. package/build/components/Avatar/style.css.map +1 -0
  3. package/build/components/Carousel/style.css +1 -1
  4. package/build/components/Carousel/style.css.map +1 -1
  5. package/build/components/Countdown/style.css +2 -0
  6. package/build/components/Countdown/style.css.map +1 -0
  7. package/build/components/DocumentationSidebar/style.css +1 -1
  8. package/build/components/DocumentationSidebar/style.css.map +1 -1
  9. package/build/components/Footer/style.css +1 -1
  10. package/build/components/Footer/style.css.map +1 -1
  11. package/build/components/Megamenu/style.css +1 -1
  12. package/build/components/Megamenu/style.css.map +1 -1
  13. package/build/components/Pill/style.css +1 -1
  14. package/build/components/Pill/style.css.map +1 -1
  15. package/build/components/Preview/style.css +1 -1
  16. package/build/components/Preview/style.css.map +1 -1
  17. package/build/components/StarRating/style.css +2 -0
  18. package/build/components/StarRating/style.css.map +1 -0
  19. package/build/components/index.js +1 -1
  20. package/build/components/index.js.map +1 -1
  21. package/build/components/tsconfig.tsbuildinfo +1 -1
  22. package/build/components/types/index.d.ts +50 -12
  23. package/build/components/types/src/components/Avatar/Avatar.d.ts +7 -0
  24. package/build/components/types/src/components/Avatar/index.d.ts +2 -0
  25. package/build/components/types/src/components/Carousel/constants.d.ts +2 -0
  26. package/build/components/types/src/components/Countdown/Countdown.d.ts +10 -0
  27. package/build/components/types/src/components/Countdown/Countdown.react.d.ts +8 -0
  28. package/build/components/types/src/components/Countdown/Countdown.shared.d.ts +54 -0
  29. package/build/components/types/src/components/Countdown/Countdown.static.d.ts +21 -0
  30. package/build/components/types/src/components/Countdown/CountdownTag.d.ts +9 -0
  31. package/build/components/types/src/components/Countdown/CountdownTag.static.d.ts +16 -0
  32. package/build/components/types/src/components/Countdown/constants.d.ts +28 -0
  33. package/build/components/types/src/components/Countdown/index.d.ts +3 -0
  34. package/build/components/types/src/components/DocumentationSidebar/DocumentationSidebar.d.ts +1 -0
  35. package/build/components/types/src/components/Preview/PreviewGenerator.d.ts +1 -0
  36. package/build/components/types/src/components/PromoBanner/PromoBanner.d.ts +5 -1
  37. package/build/components/types/src/components/StarRating/StarRating.d.ts +9 -0
  38. package/build/components/types/src/components/StarRating/index.d.ts +1 -0
  39. package/build/components/types/src/components/index.d.ts +4 -2
  40. package/build/components/types/src/scripts/index.d.ts +10 -0
  41. package/build/components/types/src/styles/export/color.d.ts +2 -0
  42. package/build/lib/base.css +1 -1
  43. package/build/lib/base.css.map +1 -1
  44. package/build/lib/components.css +1 -1
  45. package/build/lib/components.css.map +1 -1
  46. package/build/lib/footer.css +1 -1
  47. package/build/lib/footer.css.map +1 -1
  48. package/build/lib/megamenu.css +1 -1
  49. package/build/lib/megamenu.css.map +1 -1
  50. package/build/lib/scripts.js +1 -1
  51. package/build/lib/scripts.js.map +1 -1
  52. package/build/lib/style.css +1 -1
  53. package/build/lib/style.css.map +1 -1
  54. package/build/lib/tsconfig.tsbuildinfo +1 -1
  55. package/build/lib/utilities.css +1 -1
  56. package/build/lib/utilities.css.map +1 -1
  57. package/build/sprite.svg +1 -1
  58. package/package.json +29 -28
  59. package/src/assets/icons/quote.svg +1 -1
  60. package/src/assets/icons/star-empty-thin.svg +4 -0
  61. package/src/components/Avatar/Avatar.tsx +36 -0
  62. package/src/components/Avatar/index.ts +2 -0
  63. package/src/components/Avatar/styles/config.scss +11 -0
  64. package/src/components/Avatar/styles/mixins.scss +53 -0
  65. package/src/components/Avatar/styles/style.scss +20 -0
  66. package/src/components/Avatar/tests/Avatar.conformance.test.jsx +23 -0
  67. package/src/components/Avatar/tests/Avatar.unit.test.jsx +45 -0
  68. package/src/components/Carousel/Carousel.static.ts +17 -3
  69. package/src/components/Carousel/Carousel.tsx +28 -24
  70. package/src/components/Carousel/constants.ts +2 -0
  71. package/src/components/Carousel/styles/config.scss +5 -8
  72. package/src/components/Carousel/styles/mixins.scss +48 -78
  73. package/src/components/Carousel/styles/style.scss +8 -21
  74. package/src/components/Carousel/tests/Carousel.unit.test.jsx +48 -5
  75. package/src/components/Countdown/Countdown.react.ts +67 -0
  76. package/src/components/Countdown/Countdown.shared.ts +228 -0
  77. package/src/components/Countdown/Countdown.static.ts +201 -0
  78. package/src/components/Countdown/Countdown.tsx +105 -0
  79. package/src/components/Countdown/CountdownTag.static.ts +140 -0
  80. package/src/components/Countdown/CountdownTag.tsx +99 -0
  81. package/src/components/Countdown/constants.ts +36 -0
  82. package/src/components/Countdown/index.ts +3 -0
  83. package/src/components/Countdown/styles/config.scss +17 -0
  84. package/src/components/Countdown/styles/mixins.scss +193 -0
  85. package/src/components/Countdown/styles/style.scss +82 -0
  86. package/src/components/{Testimonial/tests/Testimonial.conformance.test.jsx → Countdown/tests/Countdown.conformance.test.jsx} +7 -2
  87. package/src/components/Countdown/tests/Countdown.static.test.jsx +117 -0
  88. package/src/components/Countdown/tests/Countdown.unit.test.jsx +101 -0
  89. package/src/components/Countdown/tests/CountdownTag.conformance.test.jsx +21 -0
  90. package/src/components/Countdown/tests/CountdownTag.static.test.jsx +30 -0
  91. package/src/components/Countdown/tests/CountdownTag.unit.test.jsx +67 -0
  92. package/src/components/DocumentationSidebar/DocumentationSidebar.tsx +14 -1
  93. package/src/components/DocumentationSidebar/styles/style.scss +11 -0
  94. package/src/components/Forms/Autocomplete/Autocomplete.static.ts +14 -1
  95. package/src/components/Pill/styles/style.scss +2 -1
  96. package/src/components/Preview/PreviewGenerator.tsx +137 -103
  97. package/src/components/Preview/styles/style.scss +15 -9
  98. package/src/components/PromoBanner/PromoBanner.tsx +24 -7
  99. package/src/components/PromoBanner/tests/PromoBanner.unit.test.jsx +31 -0
  100. package/src/components/StarRating/StarRating.tsx +52 -0
  101. package/src/components/StarRating/index.ts +1 -0
  102. package/src/components/StarRating/styles/style.scss +28 -0
  103. package/src/components/StarRating/tests/StarRating.unit.test.jsx +62 -0
  104. package/src/components/index.ts +7 -2
  105. package/src/styles/export/color.js +2 -0
  106. package/src/styles/tokens/color.scss +2 -0
  107. package/src/styles/utilities/text.scss +6 -0
  108. package/build/components/Testimonial/style.css +0 -2
  109. package/build/components/Testimonial/style.css.map +0 -1
  110. package/build/components/types/src/components/Testimonial/Testimonial.d.ts +0 -10
  111. package/build/components/types/src/components/Testimonial/index.d.ts +0 -1
  112. package/build/search-index.json +0 -430
  113. package/src/components/Testimonial/Testimonial.tsx +0 -38
  114. package/src/components/Testimonial/index.ts +0 -1
  115. package/src/components/Testimonial/styles/config.scss +0 -42
  116. package/src/components/Testimonial/styles/mixins.scss +0 -44
  117. package/src/components/Testimonial/styles/style.scss +0 -16
  118. package/src/components/Testimonial/tests/Testimonial.unit.test.jsx +0 -43
@@ -0,0 +1,193 @@
1
+ @use "../../../styles/tokens/breakpoint";
2
+ @use "../../../styles/tokens/space";
3
+ @use "../../../styles/tokens/color";
4
+ @use "../../../styles/tools/convert";
5
+ @use "./config";
6
+
7
+ @mixin root {
8
+ display: flex;
9
+ flex-wrap: wrap;
10
+ gap: space.get("small");
11
+ margin-bottom: space.get("large");
12
+ }
13
+
14
+ @mixin unit {
15
+ display: flex;
16
+ flex-direction: column;
17
+ align-items: center;
18
+ gap: space.get("xsmall");
19
+ perspective: convert.to-rem(300px);
20
+ perspective-origin: center center;
21
+ transform: translateZ(0);
22
+ }
23
+
24
+ @mixin card {
25
+ position: relative;
26
+ width: config.$unit-width;
27
+ height: config.$unit-height;
28
+ overflow: visible;
29
+ border-radius: config.$unit-radius;
30
+ }
31
+
32
+ @mixin layer {
33
+ position: relative;
34
+ display: block;
35
+ width: config.$unit-width;
36
+ height: config.$unit-half-height;
37
+ overflow: hidden;
38
+ background-color: var(--color-surface-secondary);
39
+ color: color.$white;
40
+ backface-visibility: hidden;
41
+ }
42
+
43
+ @mixin flipped-unit {
44
+ .countdown__leaf-front {
45
+ transform: rotateX(-90deg) translateZ(0);
46
+ transition:
47
+ transform 0.2s ease-in,
48
+ background-color 0.2s ease-in,
49
+ color 0.2s ease-in;
50
+ transition-delay: 0s;
51
+ background-color: var(--color-surface-moderate);
52
+ }
53
+
54
+ .countdown__leaf-back {
55
+ transform: rotateX(0deg) translateZ(0);
56
+ transition:
57
+ transform 0.2s ease-in,
58
+ background-color 0.2s ease-in,
59
+ color 0.2s ease-in;
60
+ transition-delay: 0.2s;
61
+ }
62
+
63
+ .countdown__bottom::before {
64
+ transition: height 0.2s ease-in-out;
65
+ transition-delay: 0.15s;
66
+ height: 100%;
67
+ }
68
+ }
69
+
70
+ @mixin top-half {
71
+ border-top-left-radius: inherit;
72
+ border-top-right-radius: inherit;
73
+
74
+ span {
75
+ top: 0;
76
+ }
77
+ }
78
+
79
+ @mixin bottom-half {
80
+ border-bottom-left-radius: inherit;
81
+ border-bottom-right-radius: inherit;
82
+
83
+ span {
84
+ bottom: 0;
85
+ }
86
+ }
87
+
88
+ @mixin animated-half {
89
+ position: absolute;
90
+ left: 0;
91
+ width: calc(100% - 0.5px);
92
+ z-index: 10;
93
+ transform-style: preserve-3d;
94
+ }
95
+
96
+ @mixin leaf-front {
97
+ top: 0;
98
+ transform-origin: bottom center;
99
+ transform: rotateX(0deg) translateZ(0);
100
+ transition-delay: 0.2s;
101
+ }
102
+
103
+ @mixin leaf-back {
104
+ top: config.$unit-half-height;
105
+ transform-origin: top center;
106
+ transform: rotateX(90deg) translateZ(0);
107
+ transition-delay: 0s;
108
+ }
109
+
110
+ @mixin bottom-shadow {
111
+ &::before {
112
+ content: "";
113
+ display: block;
114
+ position: absolute;
115
+ top: 0;
116
+ left: 0;
117
+ width: 100%;
118
+ height: 0;
119
+ background: linear-gradient(
120
+ 180deg,
121
+ rgba(0, 0, 0, 0.8) 0%,
122
+ rgba(0, 0, 0, 0) 100%
123
+ );
124
+ filter: blur(10px);
125
+ }
126
+ }
127
+
128
+ @mixin value-position {
129
+ position: absolute;
130
+ left: 50%;
131
+ width: 100%;
132
+ height: config.$unit-height;
133
+ text-align: center;
134
+ transform: translateX(-50%);
135
+ }
136
+
137
+ @mixin value {
138
+ display: block;
139
+ font-size: convert.to-rem(32px);
140
+ font-weight: 700;
141
+ line-height: config.$unit-height;
142
+ letter-spacing: convert.to-rem(-0.6px);
143
+ }
144
+
145
+ @mixin label {
146
+ display: block;
147
+ text-align: center;
148
+ font-size: convert.to-rem(18px);
149
+ line-height: convert.to-rem(26px);
150
+ color: var(--color-text-default);
151
+ }
152
+
153
+ @mixin tag-root {
154
+ display: flex;
155
+ align-items: center;
156
+ gap: config.$tag-gap;
157
+ width: fit-content;
158
+ max-width: 100%;
159
+ min-height: config.$tag-min-height;
160
+ padding: config.$tag-padding-y config.$tag-padding-x;
161
+ border-radius: 9999px;
162
+ background-color: var(--tag-black);
163
+ color: var(--tag-text-light);
164
+ margin-bottom: space.get("large");
165
+ }
166
+
167
+ @mixin tag-part {
168
+ display: inline-flex;
169
+ align-items: baseline;
170
+ gap: config.$tag-part-gap;
171
+ }
172
+
173
+ @mixin tag-value {
174
+ font-size: config.$tag-value-font-size;
175
+ line-height: config.$tag-value-line-height;
176
+ font-weight: 700;
177
+ letter-spacing: config.$tag-value-letter-spacing;
178
+ }
179
+
180
+ @mixin tag-label {
181
+ font-size: config.$tag-label-font-size;
182
+ line-height: config.$tag-label-line-height;
183
+ font-weight: 700;
184
+ color: var(--color-text-disabled);
185
+ }
186
+
187
+ @mixin tag-separator {
188
+ font-size: config.$tag-value-font-size;
189
+ line-height: config.$tag-value-line-height;
190
+ font-weight: 700;
191
+ color: var(--color-text-disabled);
192
+ transform: translateY(config.$tag-separator-offset-y);
193
+ }
@@ -0,0 +1,82 @@
1
+ @use "./mixins";
2
+
3
+ @layer components {
4
+ .countdown {
5
+ @include mixins.root;
6
+
7
+ &__unit {
8
+ @include mixins.unit;
9
+
10
+ &.is-flipping {
11
+ @include mixins.flipped-unit;
12
+ }
13
+ }
14
+
15
+ &__card {
16
+ @include mixins.card;
17
+ }
18
+
19
+ &__top,
20
+ &__bottom,
21
+ &__leaf-front,
22
+ &__leaf-back {
23
+ @include mixins.layer;
24
+ }
25
+
26
+ &__top,
27
+ &__leaf-front {
28
+ @include mixins.top-half;
29
+ }
30
+
31
+ &__bottom,
32
+ &__leaf-back {
33
+ @include mixins.bottom-half;
34
+ }
35
+
36
+ &__leaf-front,
37
+ &__leaf-back {
38
+ @include mixins.animated-half;
39
+ }
40
+
41
+ &__leaf-front {
42
+ @include mixins.leaf-front;
43
+ }
44
+
45
+ &__leaf-back {
46
+ @include mixins.leaf-back;
47
+ }
48
+
49
+ &__bottom {
50
+ @include mixins.bottom-shadow;
51
+ }
52
+
53
+ &__value {
54
+ @include mixins.value-position;
55
+ @include mixins.value;
56
+ }
57
+
58
+ &__label {
59
+ @include mixins.label;
60
+ }
61
+ }
62
+
63
+ .countdown-tag {
64
+ @include mixins.tag-root;
65
+
66
+ &__part {
67
+ @include mixins.tag-part;
68
+ }
69
+
70
+ &__value {
71
+ @include mixins.tag-value;
72
+ }
73
+
74
+ &__label {
75
+ @include mixins.tag-label;
76
+ }
77
+
78
+ &__separator {
79
+ @include mixins.tag-separator;
80
+ }
81
+ }
82
+ }
@@ -1,9 +1,14 @@
1
1
  import { render } from "@testing-library/react";
2
2
  import { axe } from "vitest-axe";
3
3
 
4
- import { Testimonial } from "../";
4
+ import { Countdown } from "../";
5
5
 
6
- const example = <Testimonial />;
6
+ const example = (
7
+ <Countdown
8
+ targetDate="2027-01-01T00:00:00.000Z"
9
+ units={["days", "hours", "minutes"]}
10
+ />
11
+ );
7
12
 
8
13
  it("is valid html", () => {
9
14
  const { container } = render(example);
@@ -0,0 +1,117 @@
1
+ import { render } from "@testing-library/react";
2
+ import { vi } from "vitest";
3
+
4
+ import { Countdown } from "../Countdown";
5
+ import CountdownStatic from "../Countdown.static";
6
+
7
+ describe("Countdown static behavior", () => {
8
+ it("warns when data-countdown-config JSON is invalid", () => {
9
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
10
+
11
+ const element = document.createElement("div");
12
+ element.className = "countdown";
13
+ element.setAttribute("data-countdown-config", "{invalid");
14
+
15
+ new CountdownStatic(element);
16
+
17
+ expect(warnSpy).toHaveBeenCalled();
18
+
19
+ warnSpy.mockRestore();
20
+ });
21
+
22
+ it("updates visible units from current time", () => {
23
+ vi.useFakeTimers();
24
+ vi.setSystemTime(new Date("2026-07-24T00:00:00.000Z"));
25
+
26
+ const { container } = render(
27
+ <Countdown
28
+ targetDate="2026-07-25T01:02:03.000Z"
29
+ units={["days", "hours", "seconds"]}
30
+ />,
31
+ );
32
+
33
+ const countdownElement = container.querySelector("[data-countdown]");
34
+ const countdown = CountdownStatic.getInstance(countdownElement);
35
+
36
+ countdown?.start();
37
+
38
+ const values = Array.from(
39
+ container.querySelectorAll(".countdown__top .countdown__value"),
40
+ ).map((element) => element.textContent);
41
+
42
+ expect(values).toEqual(["01", "01", "123"]);
43
+
44
+ countdown?.destroy();
45
+ vi.useRealTimers();
46
+ });
47
+
48
+ it("switches day label based on remaining value", () => {
49
+ vi.useFakeTimers();
50
+ vi.setSystemTime(new Date("2026-07-24T00:00:00.000Z"));
51
+
52
+ const { container } = render(
53
+ <Countdown targetDate="2026-07-25T00:00:00.000Z" units={["days"]} />,
54
+ );
55
+
56
+ const announcement = container.querySelector(".countdown__announcement");
57
+
58
+ expect(announcement).toHaveTextContent("1 deň");
59
+
60
+ vi.useRealTimers();
61
+ });
62
+
63
+ it("adds flipping state class when value changes", () => {
64
+ vi.useFakeTimers();
65
+ vi.setSystemTime(new Date("2026-07-24T00:00:00.000Z"));
66
+
67
+ const { container } = render(
68
+ <Countdown targetDate="2026-07-24T00:00:02.000Z" units={["seconds"]} />,
69
+ );
70
+
71
+ const unit = container.querySelector(".countdown__unit");
72
+
73
+ vi.advanceTimersByTime(1000);
74
+
75
+ expect(unit).toHaveClass("is-flipping");
76
+
77
+ vi.advanceTimersByTime(650);
78
+
79
+ expect(unit).not.toHaveClass("is-flipping");
80
+ vi.useRealTimers();
81
+ });
82
+
83
+ it("clears pending flip timeout before update re-init", () => {
84
+ vi.useFakeTimers();
85
+ vi.setSystemTime(new Date("2026-07-24T00:00:00.000Z"));
86
+
87
+ const { container } = render(
88
+ <Countdown targetDate="2026-07-24T00:00:02.000Z" units={["seconds"]} />,
89
+ );
90
+
91
+ const countdownElement = container.querySelector("[data-countdown]");
92
+ const countdown = CountdownStatic.getInstance(countdownElement);
93
+
94
+ vi.advanceTimersByTime(1000);
95
+
96
+ expect(container.querySelector(".countdown__unit")).toHaveClass(
97
+ "is-flipping",
98
+ );
99
+
100
+ countdown?.update({
101
+ targetDate: "2026-07-24T00:00:59.000Z",
102
+ units: ["seconds"],
103
+ });
104
+
105
+ vi.advanceTimersByTime(650);
106
+
107
+ expect(
108
+ container.querySelector(".countdown__top .countdown__value")?.textContent,
109
+ ).toBe("58");
110
+ expect(container.querySelector(".countdown__unit")).not.toHaveClass(
111
+ "is-flipping",
112
+ );
113
+
114
+ countdown?.destroy();
115
+ vi.useRealTimers();
116
+ });
117
+ });
@@ -0,0 +1,101 @@
1
+ import { render } from "@testing-library/react";
2
+
3
+ import { Countdown } from "../Countdown";
4
+
5
+ describe("rendering Countdown", () => {
6
+ it("has default class countdown", () => {
7
+ const { getByTestId } = render(
8
+ <Countdown data-testid="test-id" targetDate="2027-01-01T00:00:00.000Z" />,
9
+ );
10
+
11
+ expect(getByTestId("test-id")).toHaveClass("countdown");
12
+ });
13
+
14
+ it("has additional class when className is set", () => {
15
+ const { getByTestId } = render(
16
+ <Countdown
17
+ data-testid="test-id"
18
+ targetDate="2027-01-01T00:00:00.000Z"
19
+ className="test-class"
20
+ />,
21
+ );
22
+
23
+ expect(getByTestId("test-id")).toHaveClass("countdown");
24
+ expect(getByTestId("test-id")).toHaveClass("test-class");
25
+ });
26
+
27
+ it("renders selected units only", () => {
28
+ const { container } = render(
29
+ <Countdown
30
+ targetDate="2027-01-01T00:00:00.000Z"
31
+ units={["days", "hours"]}
32
+ />,
33
+ );
34
+
35
+ const labels = Array.from(
36
+ container.querySelectorAll(".countdown__label"),
37
+ ).map((element) => element.textContent);
38
+
39
+ expect(container.querySelectorAll(".countdown__unit")).toHaveLength(2);
40
+ expect(labels).toEqual(["dni", "hod"]);
41
+ });
42
+
43
+ it("renders custom labels", () => {
44
+ const { getByText } = render(
45
+ <Countdown
46
+ targetDate="2027-01-01T00:00:00.000Z"
47
+ units={["days", "hours", "seconds"]}
48
+ labels={{
49
+ days: { many: "days", few: "days", one: "day" },
50
+ hours: "hrs",
51
+ seconds: "sec",
52
+ }}
53
+ />,
54
+ );
55
+
56
+ expect(getByText("days")).toBeInTheDocument();
57
+ expect(getByText("hrs")).toBeInTheDocument();
58
+ expect(getByText("sec")).toBeInTheDocument();
59
+ });
60
+
61
+ it("renders root-level data attributes for vanilla use", () => {
62
+ const { getByTestId } = render(
63
+ <Countdown
64
+ data-testid="test-id"
65
+ targetDate="2027-01-01T00:00:00.000Z"
66
+ units={["days", "hours"]}
67
+ labels={{ days: { many: "dni", few: "dni", one: "deň" }, hours: "hod" }}
68
+ />,
69
+ );
70
+
71
+ expect(getByTestId("test-id")).toHaveAttribute("data-countdown", "true");
72
+ expect(
73
+ JSON.parse(getByTestId("test-id").getAttribute("data-countdown-config")),
74
+ ).toEqual({
75
+ targetDate: "2027-01-01T00:00:00.000Z",
76
+ units: ["days", "hours"],
77
+ labels: { days: { many: "dni", few: "dni", one: "deň" }, hours: "hod" },
78
+ });
79
+ });
80
+
81
+ it("normalizes duplicate and out-of-order units", () => {
82
+ const { container, getByTestId } = render(
83
+ <Countdown
84
+ data-testid="test-id"
85
+ targetDate="2027-01-01T00:00:00.000Z"
86
+ units={["seconds", "days", "seconds", "hours"]}
87
+ />,
88
+ );
89
+
90
+ const labels = Array.from(
91
+ container.querySelectorAll(".countdown__label"),
92
+ ).map((element) => element.textContent);
93
+
94
+ expect(labels).toEqual(["dni", "hod", "sek"]);
95
+ expect(
96
+ JSON.parse(getByTestId("test-id").getAttribute("data-countdown-config")),
97
+ ).toMatchObject({
98
+ units: ["days", "hours", "seconds"],
99
+ });
100
+ });
101
+ });
@@ -0,0 +1,21 @@
1
+ import { render } from "@testing-library/react";
2
+ import { axe } from "vitest-axe";
3
+
4
+ import { CountdownTag } from "../";
5
+
6
+ const example = (
7
+ <CountdownTag
8
+ targetDate="2027-01-01T00:00:00.000Z"
9
+ units={["days", "hours", "minutes"]}
10
+ />
11
+ );
12
+
13
+ it("CountdownTag is valid html", () => {
14
+ const { container } = render(example);
15
+ expect(container).toHTMLValidate();
16
+ });
17
+
18
+ it("CountdownTag is accessible", async () => {
19
+ const { container } = render(example);
20
+ expect(await axe(container)).toHaveNoViolations();
21
+ });
@@ -0,0 +1,30 @@
1
+ import { render } from "@testing-library/react";
2
+ import { vi } from "vitest";
3
+ import { CountdownTag } from "../CountdownTag";
4
+ import CountdownTagStatic from "../CountdownTag.static";
5
+
6
+ describe("CountdownTag static behavior", () => {
7
+ it("updates visible value from current time", () => {
8
+ vi.useFakeTimers();
9
+ vi.setSystemTime(new Date("2026-07-24T00:00:00.000Z"));
10
+
11
+ const { container } = render(
12
+ <CountdownTag
13
+ targetDate="2026-07-24T00:00:59.000Z"
14
+ units={["seconds"]}
15
+ />,
16
+ );
17
+
18
+ const element = container.querySelector("[data-countdown-tag]");
19
+ const countdownTag = CountdownTagStatic.getInstance(element);
20
+
21
+ countdownTag?.start();
22
+
23
+ expect(
24
+ container.querySelector("[data-countdown-tag-value]")?.textContent,
25
+ ).toBe("59");
26
+
27
+ countdownTag?.destroy();
28
+ vi.useRealTimers();
29
+ });
30
+ });
@@ -0,0 +1,67 @@
1
+ import { render } from "@testing-library/react";
2
+
3
+ import { CountdownTag } from "../CountdownTag";
4
+
5
+ describe("rendering CountdownTag", () => {
6
+ it("has default class countdown-tag", () => {
7
+ const { getByTestId } = render(
8
+ <CountdownTag
9
+ data-testid="test-id"
10
+ targetDate="2027-01-01T00:00:00.000Z"
11
+ />,
12
+ );
13
+
14
+ expect(getByTestId("test-id")).toHaveClass("countdown-tag");
15
+ });
16
+
17
+ it("renders default labels including seconds", () => {
18
+ const { container } = render(
19
+ <CountdownTag targetDate="2027-01-01T00:00:00.000Z" />,
20
+ );
21
+
22
+ const labels = Array.from(
23
+ container.querySelectorAll(".countdown-tag__label"),
24
+ ).map((element) => element.textContent);
25
+
26
+ expect(labels).toEqual(["d", "h", "m", "s"]);
27
+ });
28
+
29
+ it("renders custom labels", () => {
30
+ const { getByText } = render(
31
+ <CountdownTag
32
+ targetDate="2027-01-01T00:00:00.000Z"
33
+ units={["days", "hours", "seconds"]}
34
+ labels={{
35
+ days: "dni",
36
+ hours: "hod",
37
+ seconds: "sek",
38
+ }}
39
+ />,
40
+ );
41
+
42
+ expect(getByText("dni")).toBeInTheDocument();
43
+ expect(getByText("hod")).toBeInTheDocument();
44
+ expect(getByText("sek")).toBeInTheDocument();
45
+ });
46
+
47
+ it("normalizes duplicate and out-of-order units", () => {
48
+ const { container, getByTestId } = render(
49
+ <CountdownTag
50
+ data-testid="test-id"
51
+ targetDate="2027-01-01T00:00:00.000Z"
52
+ units={["seconds", "days", "seconds", "hours"]}
53
+ />,
54
+ );
55
+
56
+ const labels = Array.from(
57
+ container.querySelectorAll(".countdown-tag__label"),
58
+ ).map((element) => element.textContent);
59
+
60
+ expect(labels).toEqual(["d", "h", "s"]);
61
+ expect(
62
+ JSON.parse(getByTestId("test-id").getAttribute("data-countdown-config")),
63
+ ).toMatchObject({
64
+ units: ["days", "hours", "seconds"],
65
+ });
66
+ });
67
+ });
@@ -9,11 +9,13 @@ import packageJson from "../../../package.json";
9
9
  import { TextInput } from "../Forms/TextInput";
10
10
  import { Icon } from "../Icon";
11
11
  import { Image } from "../Image";
12
+ import { Tag } from "../Tag";
12
13
 
13
14
  export interface DocumentationSidebarItem {
14
15
  label: string;
15
16
  href?: string;
16
17
  target?: React.HTMLAttributeAnchorTarget;
18
+ tag?: string;
17
19
  items?: DocumentationSidebarItem[];
18
20
  content?: string; // Optional: MDX content for searching
19
21
  }
@@ -201,7 +203,18 @@ const SidebarItem: React.FC<{
201
203
  })}
202
204
  onClick={() => item.href && onSelect(item.href)}
203
205
  >
204
- {item.label}
206
+ <span className={`${CLASS_ROOT}__link-content`}>
207
+ <span>{item.label}</span>
208
+ {item.tag ? (
209
+ <Tag
210
+ color="warning"
211
+ size="small"
212
+ className={`${CLASS_ROOT}__tag`}
213
+ >
214
+ {item.tag}
215
+ </Tag>
216
+ ) : null}
217
+ </span>
205
218
  </Link>
206
219
  )}
207
220
  </div>
@@ -178,6 +178,17 @@
178
178
  }
179
179
  }
180
180
 
181
+ &__link-content {
182
+ display: inline-flex;
183
+ align-items: center;
184
+ gap: convert.to-rem(8px);
185
+ flex-wrap: wrap;
186
+ }
187
+
188
+ &__tag {
189
+ vertical-align: middle;
190
+ }
191
+
181
192
  &__content {
182
193
  padding: 16px;
183
194
  background: #fff;