@orangesk/orange-design-system 2.0.0-beta.47 → 2.0.0-beta.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/BlockAction/style.css +1 -1
- package/build/components/BlockAction/style.css.map +1 -1
- package/build/components/Breadcrumbs/style.css +1 -1
- package/build/components/Breadcrumbs/style.css.map +1 -1
- package/build/components/Card/style.css +1 -1
- package/build/components/Card/style.css.map +1 -1
- package/build/components/DocumentationSidebar/style.css +1 -1
- package/build/components/DocumentationSidebar/style.css.map +1 -1
- package/build/components/Icon/style.css +1 -1
- package/build/components/Icon/style.css.map +1 -1
- package/build/components/Loader/style.css +1 -1
- package/build/components/Loader/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/Pagination/style.css +1 -1
- package/build/components/Pagination/style.css.map +1 -1
- package/build/components/PromotionCard/style.css +1 -1
- package/build/components/PromotionCard/style.css.map +1 -1
- package/build/components/Section/style.css +1 -1
- package/build/components/Section/style.css.map +1 -1
- package/build/components/SocialButton/style.css +1 -1
- package/build/components/SocialButton/style.css.map +1 -1
- package/build/components/Stepbar/style.css +1 -1
- package/build/components/Stepbar/style.css.map +1 -1
- package/build/components/Table/style.css +1 -1
- package/build/components/Table/style.css.map +1 -1
- package/build/components/Tag/style.css +1 -1
- package/build/components/Tag/style.css.map +1 -1
- package/build/components/Tile/style.css +1 -1
- package/build/components/Tile/style.css.map +1 -1
- 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 +5 -11
- package/build/components/types/src/components/Carousel/Carousel.static.d.ts +4 -1
- package/build/components/types/src/components/CarouselHero/CarouselHero.d.ts +1 -0
- package/build/components/types/src/components/Forms/Autocomplete/Autocomplete.static.d.ts +11 -0
- package/build/components/types/src/components/Forms/Group/Group.d.ts +1 -1
- package/build/components/types/src/components/Tile/Tile.d.ts +3 -11
- 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/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/search-index.json +5 -5
- package/package.json +21 -21
- package/src/components/BlockAction/styles/style.scss +6 -4
- package/src/components/Breadcrumbs/styles/style.scss +2 -1
- package/src/components/Card/styles/config.scss +1 -1
- package/src/components/Carousel/Carousel.static.ts +29 -1
- package/src/components/Carousel/tests/Carousel.static.test.jsx +50 -0
- package/src/components/CarouselHero/CarouselHero.static.ts +7 -0
- package/src/components/CarouselHero/CarouselHero.tsx +61 -3
- package/src/components/CarouselHero/tests/CarouselHero.unit.test.jsx +31 -1
- package/src/components/DocumentationSidebar/DocumentationSidebar.tsx +21 -34
- package/src/components/DocumentationSidebar/styles/style.scss +0 -6
- package/src/components/Forms/Autocomplete/Autocomplete.static.ts +190 -14
- package/src/components/Forms/Autocomplete/styles/style.scss +61 -8
- package/src/components/Forms/Autocomplete/tests/Autocomplete.static.test.ts +187 -0
- package/src/components/Forms/DatePicker/styles/style.scss +1 -2
- package/src/components/Forms/Group/Group.tsx +4 -1
- package/src/components/Forms/Group/styles/config.scss +1 -1
- package/src/components/Forms/Group/styles/mixins.scss +17 -0
- package/src/components/Forms/Group/tests/Group.unit.test.jsx +9 -0
- package/src/components/Forms/TextArea/styles/config.scss +1 -0
- package/src/components/Forms/TextArea/styles/mixins.scss +7 -1
- package/src/components/Forms/TextInput/styles/config.scss +3 -1
- package/src/components/Forms/TextInput/styles/mixins.scss +7 -1
- package/src/components/Forms/TextInput/styles/style.scss +17 -12
- package/src/components/Forms/styles/config.scss +3 -2
- package/src/components/Icon/styles/style.scss +2 -1
- package/src/components/Loader/styles/style.scss +0 -2
- package/src/components/Pagination/styles/mixins.scss +8 -6
- package/src/components/Pagination/styles/style.scss +0 -4
- package/src/components/Preview/PreviewGenerator.tsx +15 -2
- package/src/components/PromotionCard/styles/mixins.scss +2 -1
- package/src/components/Section/styles/mixins.scss +27 -5
- package/src/components/SocialButton/styles/config.scss +2 -2
- package/src/components/Stepbar/styles/style.scss +4 -2
- package/src/components/Table/styles/mixins.scss +6 -3
- package/src/components/Tag/styles/config.scss +1 -1
- package/src/components/Tag/styles/style.scss +22 -5
- package/src/components/Tile/Tile.tsx +18 -42
- package/src/components/Tile/styles/mixins.scss +55 -45
- package/src/components/Tile/styles/style.scss +5 -17
- package/src/components/Tile/tests/Tile.unit.test.jsx +9 -78
- package/src/styles/tokens/color-vars.scss +0 -2
- package/src/styles/utilities/color.scss +0 -153
- package/src/components/Tile/CHANGELOG.md +0 -28
- package/src/components/Tile/styles/config.scss +0 -7
|
@@ -381,7 +381,7 @@ export const DocumentationSidebar: React.FC<DocumentationSidebarProps> = (
|
|
|
381
381
|
>
|
|
382
382
|
<Image src="/logo.svg" alt="logo" />
|
|
383
383
|
|
|
384
|
-
<div className=
|
|
384
|
+
<div className="mb">
|
|
385
385
|
<TextInput
|
|
386
386
|
id="search-documentation"
|
|
387
387
|
htmlType="search"
|
|
@@ -393,42 +393,29 @@ export const DocumentationSidebar: React.FC<DocumentationSidebarProps> = (
|
|
|
393
393
|
aria-label="Search documentation"
|
|
394
394
|
ref={searchInputRef}
|
|
395
395
|
/>
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
className={`${CLASS_ROOT}__search-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
396
|
+
|
|
397
|
+
{searchResults.length > 0 && (
|
|
398
|
+
<div className={`${CLASS_ROOT}__search-results`}>
|
|
399
|
+
<ul className={`${CLASS_ROOT}__search-list`}>
|
|
400
|
+
{searchResults.map((result, index) => (
|
|
401
|
+
<li key={result.id}>
|
|
402
|
+
<Link
|
|
403
|
+
href={result.href}
|
|
404
|
+
className={cx(`${CLASS_ROOT}__search-result-link`, {
|
|
405
|
+
[`${CLASS_ROOT}__search-result-link--active`]:
|
|
406
|
+
index === activeSearchIndex,
|
|
407
|
+
})}
|
|
408
|
+
onClick={() => handleSelect(result.href)}
|
|
409
|
+
>
|
|
410
|
+
{result.label}
|
|
411
|
+
</Link>
|
|
412
|
+
</li>
|
|
413
|
+
))}
|
|
414
|
+
</ul>
|
|
415
|
+
</div>
|
|
408
416
|
)}
|
|
409
417
|
</div>
|
|
410
418
|
|
|
411
|
-
{searchResults.length > 0 && (
|
|
412
|
-
<div className={`${CLASS_ROOT}__search-results`}>
|
|
413
|
-
<ul className={`${CLASS_ROOT}__search-list`}>
|
|
414
|
-
{searchResults.map((result, index) => (
|
|
415
|
-
<li key={result.id}>
|
|
416
|
-
<Link
|
|
417
|
-
href={result.href}
|
|
418
|
-
className={cx(`${CLASS_ROOT}__search-result-link`, {
|
|
419
|
-
[`${CLASS_ROOT}__search-result-link--active`]:
|
|
420
|
-
index === activeSearchIndex,
|
|
421
|
-
})}
|
|
422
|
-
onClick={() => handleSelect(result.href)}
|
|
423
|
-
>
|
|
424
|
-
{result.label}
|
|
425
|
-
</Link>
|
|
426
|
-
</li>
|
|
427
|
-
))}
|
|
428
|
-
</ul>
|
|
429
|
-
</div>
|
|
430
|
-
)}
|
|
431
|
-
|
|
432
419
|
<nav
|
|
433
420
|
className={`${CLASS_ROOT}__nav`}
|
|
434
421
|
style={{ display: searchResults.length > 0 ? "none" : "block" }}
|
|
@@ -6,6 +6,8 @@ export interface AutocompleteConfig {
|
|
|
6
6
|
optionClassName: string;
|
|
7
7
|
customInputClassName: string;
|
|
8
8
|
displayMenu: string;
|
|
9
|
+
onConfirm?: (value: unknown) => void;
|
|
10
|
+
tClearButton: () => string;
|
|
9
11
|
tAssistiveHint: () => string;
|
|
10
12
|
}
|
|
11
13
|
|
|
@@ -16,25 +18,39 @@ export const defaultConfig: AutocompleteConfig = {
|
|
|
16
18
|
optionClassName: "",
|
|
17
19
|
customInputClassName: "",
|
|
18
20
|
displayMenu: "overlay",
|
|
21
|
+
tClearButton: () => "Vymazať text",
|
|
19
22
|
tAssistiveHint: () =>
|
|
20
23
|
"Pre prezeranie výsledkov automatického dopĺňania použite šípky nahor a nadol a pre výber konkrétneho výsledku použite tlačidlo Enter. Pri dotykových zariadeniach použite dotyk alebo potiahnutie prstom do strany.",
|
|
21
24
|
};
|
|
22
25
|
|
|
26
|
+
const addClassToken = (className: string, token: string) => {
|
|
27
|
+
const classTokens = className.split(/\s+/).filter(Boolean);
|
|
28
|
+
|
|
29
|
+
if (classTokens.includes(token)) {
|
|
30
|
+
return className;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return [...classTokens, token].join(" ");
|
|
34
|
+
};
|
|
35
|
+
|
|
23
36
|
export default class Autocomplete {
|
|
24
37
|
private element: HTMLElement;
|
|
25
38
|
private config: AutocompleteConfig;
|
|
26
39
|
private instance: any;
|
|
40
|
+
private teardownClearButton: (() => void) | null;
|
|
27
41
|
|
|
28
42
|
constructor(element: HTMLElement, config?: Partial<AutocompleteConfig>) {
|
|
29
43
|
this.element = element;
|
|
30
44
|
this.config = { ...defaultConfig, ...config };
|
|
31
45
|
this.onPageshow = this.onPageshow.bind(this);
|
|
46
|
+
this.onClearButtonClick = this.onClearButtonClick.bind(this);
|
|
32
47
|
this.instance = null;
|
|
48
|
+
this.teardownClearButton = null;
|
|
33
49
|
|
|
34
50
|
(this.element as any).ODS_Autocomplete = this;
|
|
35
51
|
|
|
36
52
|
// Initialize asynchronously to handle dynamic imports
|
|
37
|
-
this.init().catch(
|
|
53
|
+
this.init().catch(() => undefined);
|
|
38
54
|
|
|
39
55
|
return this;
|
|
40
56
|
}
|
|
@@ -55,11 +71,19 @@ export default class Autocomplete {
|
|
|
55
71
|
const customInputClassTokens = customInputClassName
|
|
56
72
|
.split(/\s+/)
|
|
57
73
|
.filter(Boolean);
|
|
74
|
+
const wrapperClassTokens = this.config.wrapperClassName
|
|
75
|
+
.split(/\s+/)
|
|
76
|
+
.filter(Boolean);
|
|
58
77
|
|
|
59
|
-
const hasSearchIcon =
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
|
|
78
|
+
const hasSearchIcon =
|
|
79
|
+
customInputClassTokens.includes("input--search-icon") ||
|
|
80
|
+
wrapperClassTokens.includes("autocomplete__wrapper--search-icon");
|
|
81
|
+
const hasSearchIconWithPlaceholder =
|
|
82
|
+
customInputClassTokens.includes("input--search-icon-with-placeholder") ||
|
|
83
|
+
wrapperClassTokens.includes(
|
|
84
|
+
"autocomplete__wrapper--search-icon-with-placeholder",
|
|
85
|
+
);
|
|
86
|
+
const isInvalid = this.element.classList.contains("is-invalid");
|
|
63
87
|
|
|
64
88
|
this.config.customInputClassName = customInputClassTokens
|
|
65
89
|
.filter(
|
|
@@ -69,22 +93,25 @@ export default class Autocomplete {
|
|
|
69
93
|
)
|
|
70
94
|
.join(" ");
|
|
71
95
|
|
|
96
|
+
if (isInvalid) {
|
|
97
|
+
this.config.customInputClassName = addClassToken(
|
|
98
|
+
this.config.customInputClassName,
|
|
99
|
+
"is-invalid",
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
72
103
|
if (hasSearchIcon) {
|
|
73
|
-
this.config.wrapperClassName =
|
|
104
|
+
this.config.wrapperClassName = addClassToken(
|
|
74
105
|
this.config.wrapperClassName,
|
|
75
106
|
"autocomplete__wrapper--search-icon",
|
|
76
|
-
|
|
77
|
-
.filter(Boolean)
|
|
78
|
-
.join(" ");
|
|
107
|
+
);
|
|
79
108
|
}
|
|
80
109
|
|
|
81
110
|
if (hasSearchIconWithPlaceholder) {
|
|
82
|
-
this.config.wrapperClassName =
|
|
111
|
+
this.config.wrapperClassName = addClassToken(
|
|
83
112
|
this.config.wrapperClassName,
|
|
84
113
|
"autocomplete__wrapper--search-icon-with-placeholder",
|
|
85
|
-
|
|
86
|
-
.filter(Boolean)
|
|
87
|
-
.join(" ");
|
|
114
|
+
);
|
|
88
115
|
}
|
|
89
116
|
|
|
90
117
|
// Dynamic import to avoid SSR issues
|
|
@@ -104,9 +131,156 @@ export default class Autocomplete {
|
|
|
104
131
|
});
|
|
105
132
|
}
|
|
106
133
|
|
|
134
|
+
this.initEnhancedControls(hasSearchIconWithPlaceholder);
|
|
135
|
+
|
|
107
136
|
window.addEventListener("pageshow", this.onPageshow);
|
|
108
137
|
}
|
|
109
138
|
|
|
139
|
+
private getWrapper(): HTMLElement | null {
|
|
140
|
+
if (this.element.tagName === "SELECT") {
|
|
141
|
+
const rootElement = this.element.previousElementSibling;
|
|
142
|
+
if (!(rootElement instanceof HTMLElement)) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (rootElement.classList.contains("autocomplete__wrapper")) {
|
|
147
|
+
return rootElement;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return rootElement.querySelector(".autocomplete__wrapper");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return this.element.querySelector(".autocomplete__wrapper");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private getInput(): HTMLInputElement | null {
|
|
157
|
+
return this.getWrapper()?.querySelector(".autocomplete__input") ?? null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private initEnhancedControls(
|
|
161
|
+
hasSearchIconWithPlaceholder: boolean,
|
|
162
|
+
retries = 10,
|
|
163
|
+
): void {
|
|
164
|
+
if (!this.getInput()) {
|
|
165
|
+
if (retries > 0) {
|
|
166
|
+
window.setTimeout(
|
|
167
|
+
() =>
|
|
168
|
+
this.initEnhancedControls(
|
|
169
|
+
hasSearchIconWithPlaceholder,
|
|
170
|
+
retries - 1,
|
|
171
|
+
),
|
|
172
|
+
0,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.syncInputState();
|
|
180
|
+
|
|
181
|
+
if (hasSearchIconWithPlaceholder) {
|
|
182
|
+
this.initClearButton();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private syncInputState(): void {
|
|
187
|
+
const input = this.getInput();
|
|
188
|
+
if (!input) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (this.element.classList.contains("is-invalid")) {
|
|
193
|
+
input.classList.add("is-invalid");
|
|
194
|
+
input.setAttribute("aria-invalid", "true");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (this.element instanceof HTMLSelectElement && this.element.disabled) {
|
|
198
|
+
input.disabled = true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private initClearButton(): void {
|
|
203
|
+
this.teardownClearButton?.();
|
|
204
|
+
|
|
205
|
+
const wrapper = this.getWrapper();
|
|
206
|
+
const input = this.getInput();
|
|
207
|
+
|
|
208
|
+
if (!wrapper || !input) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const clearButton = document.createElement("button");
|
|
213
|
+
clearButton.type = "button";
|
|
214
|
+
clearButton.className = "autocomplete__clear";
|
|
215
|
+
clearButton.setAttribute("aria-label", this.config.tClearButton());
|
|
216
|
+
|
|
217
|
+
const updateClearButton = () => {
|
|
218
|
+
clearButton.hidden = input.disabled || input.value.length === 0;
|
|
219
|
+
};
|
|
220
|
+
const scheduleUpdateClearButton = () => {
|
|
221
|
+
window.setTimeout(updateClearButton, 0);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
clearButton.addEventListener("mousedown", this.preventDefault);
|
|
225
|
+
clearButton.addEventListener("keydown", this.stopPropagation);
|
|
226
|
+
clearButton.addEventListener("click", this.onClearButtonClick);
|
|
227
|
+
input.addEventListener("input", updateClearButton);
|
|
228
|
+
input.addEventListener("change", updateClearButton);
|
|
229
|
+
wrapper.addEventListener("click", scheduleUpdateClearButton);
|
|
230
|
+
wrapper.addEventListener("keyup", scheduleUpdateClearButton);
|
|
231
|
+
|
|
232
|
+
updateClearButton();
|
|
233
|
+
wrapper.appendChild(clearButton);
|
|
234
|
+
|
|
235
|
+
this.teardownClearButton = () => {
|
|
236
|
+
clearButton.removeEventListener("mousedown", this.preventDefault);
|
|
237
|
+
clearButton.removeEventListener("keydown", this.stopPropagation);
|
|
238
|
+
clearButton.removeEventListener("click", this.onClearButtonClick);
|
|
239
|
+
input.removeEventListener("input", updateClearButton);
|
|
240
|
+
input.removeEventListener("change", updateClearButton);
|
|
241
|
+
wrapper.removeEventListener("click", scheduleUpdateClearButton);
|
|
242
|
+
wrapper.removeEventListener("keyup", scheduleUpdateClearButton);
|
|
243
|
+
clearButton.remove();
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private preventDefault(event: Event): void {
|
|
248
|
+
event.preventDefault();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private stopPropagation(event: Event): void {
|
|
252
|
+
event.stopPropagation();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private onClearButtonClick(): void {
|
|
256
|
+
const input = this.getInput();
|
|
257
|
+
|
|
258
|
+
if (!input) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
input.value = "";
|
|
263
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
264
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
265
|
+
|
|
266
|
+
if (this.element instanceof HTMLSelectElement) {
|
|
267
|
+
const emptyOption = Array.from(this.element.options).find(
|
|
268
|
+
(option) => option.value === "",
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
if (emptyOption) {
|
|
272
|
+
emptyOption.selected = true;
|
|
273
|
+
} else {
|
|
274
|
+
this.element.selectedIndex = -1;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.element.dispatchEvent(new Event("change", { bubbles: true }));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
this.config.onConfirm?.("");
|
|
281
|
+
input.focus();
|
|
282
|
+
}
|
|
283
|
+
|
|
110
284
|
private onPageshow(e: PageTransitionEvent): void {
|
|
111
285
|
if (e.persisted && window) {
|
|
112
286
|
window.location.reload();
|
|
@@ -114,13 +288,15 @@ export default class Autocomplete {
|
|
|
114
288
|
}
|
|
115
289
|
|
|
116
290
|
public destroy(): void {
|
|
291
|
+
this.teardownClearButton?.();
|
|
292
|
+
this.teardownClearButton = null;
|
|
117
293
|
window.removeEventListener("pageshow", this.onPageshow);
|
|
118
294
|
(this.element as any).ODS_Autocomplete = null;
|
|
119
295
|
}
|
|
120
296
|
|
|
121
297
|
public update(): void {
|
|
122
298
|
this.destroy();
|
|
123
|
-
this.init().catch(
|
|
299
|
+
this.init().catch(() => undefined);
|
|
124
300
|
}
|
|
125
301
|
|
|
126
302
|
static getInstance(el: HTMLElement): Autocomplete | null {
|
|
@@ -26,16 +26,32 @@
|
|
|
26
26
|
content: "";
|
|
27
27
|
position: absolute;
|
|
28
28
|
top: 50%;
|
|
29
|
-
left: convert.to-rem(
|
|
29
|
+
left: convert.to-rem(10px);
|
|
30
30
|
width: convert.to-rem(20px);
|
|
31
31
|
height: convert.to-rem(20px);
|
|
32
32
|
transform: translateY(-50%);
|
|
33
|
-
background-
|
|
34
|
-
|
|
33
|
+
background-color: var(--color-icon-brand);
|
|
34
|
+
mask-image: textConfig.$mask-image-search-icon;
|
|
35
|
+
mask-position: center;
|
|
36
|
+
mask-repeat: no-repeat;
|
|
37
|
+
mask-size: contain;
|
|
35
38
|
pointer-events: none;
|
|
36
39
|
z-index: 3;
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
&:has(.autocomplete__input:not(:placeholder-shown))::before {
|
|
43
|
+
background-color: var(--color-icon-default);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&:has(.autocomplete__input:disabled)::before {
|
|
47
|
+
background-color: var(--color-icon-disabled);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&:has(.autocomplete__input.is-invalid)::before,
|
|
51
|
+
&:has(.autocomplete__input[aria-invalid="true"])::before {
|
|
52
|
+
background-color: var(--color-icon-negative);
|
|
53
|
+
}
|
|
54
|
+
|
|
39
55
|
.autocomplete__input {
|
|
40
56
|
background-image: none !important;
|
|
41
57
|
padding-left: convert.to-rem(40px);
|
|
@@ -48,17 +64,54 @@
|
|
|
48
64
|
}
|
|
49
65
|
}
|
|
50
66
|
|
|
51
|
-
&--search-icon {
|
|
52
|
-
|
|
53
|
-
|
|
67
|
+
&--search-icon-with-placeholder {
|
|
68
|
+
.autocomplete__input {
|
|
69
|
+
padding-right: convert.to-rem(44px);
|
|
54
70
|
}
|
|
55
71
|
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
.autocomplete__hint {
|
|
73
|
+
padding-right: convert.to-rem(44px);
|
|
58
74
|
}
|
|
59
75
|
}
|
|
60
76
|
}
|
|
61
77
|
|
|
78
|
+
&__clear {
|
|
79
|
+
position: absolute;
|
|
80
|
+
top: 50%;
|
|
81
|
+
right: 0;
|
|
82
|
+
z-index: 4;
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
width: convert.to-rem(44px);
|
|
87
|
+
height: convert.to-rem(36px);
|
|
88
|
+
padding: 0;
|
|
89
|
+
background: transparent;
|
|
90
|
+
border: 0;
|
|
91
|
+
transform: translateY(-50%);
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
|
|
94
|
+
&::before {
|
|
95
|
+
content: "";
|
|
96
|
+
width: convert.to-rem(24px);
|
|
97
|
+
height: convert.to-rem(24px);
|
|
98
|
+
background-color: var(--color-icon-default);
|
|
99
|
+
mask-image: url('data:image/svg+xml,<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18 20L10 12L18 4L20 6L14 12L20 18L18 20Z" fill="%23000"/><path d="M6 4L14 12L6 20L4 18L10 12L4 6L6 4Z" fill="%23000"/></svg>');
|
|
100
|
+
mask-position: center;
|
|
101
|
+
mask-repeat: no-repeat;
|
|
102
|
+
mask-size: contain;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
&:focus-visible {
|
|
106
|
+
outline: 2px solid var(--color-border-contrast);
|
|
107
|
+
outline-offset: -2px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
&[hidden] {
|
|
111
|
+
display: none;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
62
115
|
&__input {
|
|
63
116
|
@include textMixins.base();
|
|
64
117
|
@include textMixins.states();
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { fireEvent, waitFor } from "@testing-library/dom";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
import AutocompleteStatic from "../Autocomplete.static";
|
|
5
|
+
|
|
6
|
+
const renderAutocomplete = (options: any) => {
|
|
7
|
+
const wrapper = document.createElement("div");
|
|
8
|
+
wrapper.className = ["autocomplete__wrapper", options.wrapperClassName]
|
|
9
|
+
.filter(Boolean)
|
|
10
|
+
.join(" ");
|
|
11
|
+
|
|
12
|
+
const input = document.createElement("input");
|
|
13
|
+
input.className = ["autocomplete__input", options.customInputClassName]
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.join(" ");
|
|
16
|
+
input.value = options.defaultValue ?? "";
|
|
17
|
+
|
|
18
|
+
wrapper.appendChild(input);
|
|
19
|
+
options.element.appendChild(wrapper);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const renderAutocompleteAsync = (options: any) => {
|
|
23
|
+
window.setTimeout(() => renderAutocomplete(options), 0);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const accessibleAutocompleteMock: any = vi.fn(renderAutocompleteAsync);
|
|
27
|
+
|
|
28
|
+
accessibleAutocompleteMock.enhanceSelectElement = vi.fn((options: any) => {
|
|
29
|
+
const element = document.createElement("div");
|
|
30
|
+
const selectedOption =
|
|
31
|
+
options.selectElement.options[options.selectElement.selectedIndex];
|
|
32
|
+
|
|
33
|
+
options.selectElement.parentNode.insertBefore(element, options.selectElement);
|
|
34
|
+
|
|
35
|
+
renderAutocompleteAsync({
|
|
36
|
+
...options,
|
|
37
|
+
element,
|
|
38
|
+
defaultValue: selectedOption?.textContent ?? "",
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
vi.mock("@orangesk/accessible-autocomplete", () => ({
|
|
43
|
+
default: accessibleAutocompleteMock,
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
describe("Autocomplete.static", () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
document.body.innerHTML = "";
|
|
49
|
+
vi.clearAllMocks();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("moves search icon classes from input config to wrapper config", async () => {
|
|
53
|
+
const select = document.createElement("select");
|
|
54
|
+
document.body.appendChild(select);
|
|
55
|
+
|
|
56
|
+
new AutocompleteStatic(select, {
|
|
57
|
+
customInputClassName: "input--search-icon-with-placeholder custom-class",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await waitFor(() => {
|
|
61
|
+
expect(
|
|
62
|
+
accessibleAutocompleteMock.enhanceSelectElement,
|
|
63
|
+
).toHaveBeenCalled();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const config =
|
|
67
|
+
accessibleAutocompleteMock.enhanceSelectElement.mock.calls[0][0];
|
|
68
|
+
|
|
69
|
+
expect(config.wrapperClassName).toContain(
|
|
70
|
+
"autocomplete__wrapper--search-icon-with-placeholder",
|
|
71
|
+
);
|
|
72
|
+
expect(config.customInputClassName).toBe("custom-class");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("passes invalid state to generated input", async () => {
|
|
76
|
+
const select = document.createElement("select");
|
|
77
|
+
select.className = "autocomplete is-invalid";
|
|
78
|
+
document.body.appendChild(select);
|
|
79
|
+
|
|
80
|
+
new AutocompleteStatic(select, {
|
|
81
|
+
customInputClassName: "input--search-icon-with-placeholder",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await waitFor(() => {
|
|
85
|
+
expect(document.querySelector(".autocomplete__input")).toHaveClass(
|
|
86
|
+
"is-invalid",
|
|
87
|
+
);
|
|
88
|
+
expect(document.querySelector(".autocomplete__input")).toHaveAttribute(
|
|
89
|
+
"aria-invalid",
|
|
90
|
+
"true",
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("passes disabled state to generated input and hides clear button", async () => {
|
|
96
|
+
const select = document.createElement("select");
|
|
97
|
+
const appleOption = new Option("apple", "apple", true, true);
|
|
98
|
+
|
|
99
|
+
select.append(appleOption);
|
|
100
|
+
select.disabled = true;
|
|
101
|
+
document.body.appendChild(select);
|
|
102
|
+
|
|
103
|
+
new AutocompleteStatic(select, {
|
|
104
|
+
customInputClassName: "input--search-icon-with-placeholder",
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await waitFor(() => {
|
|
108
|
+
expect(document.querySelector(".autocomplete__input")).toBeDisabled();
|
|
109
|
+
expect(document.querySelector(".autocomplete__clear")).not.toBeVisible();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("clears input and select value from search icon autocomplete", async () => {
|
|
114
|
+
const select = document.createElement("select");
|
|
115
|
+
const emptyOption = new Option("", "");
|
|
116
|
+
const appleOption = new Option("apple", "apple", true, true);
|
|
117
|
+
|
|
118
|
+
select.append(emptyOption, appleOption);
|
|
119
|
+
document.body.appendChild(select);
|
|
120
|
+
|
|
121
|
+
const onConfirm = vi.fn();
|
|
122
|
+
|
|
123
|
+
new AutocompleteStatic(select, {
|
|
124
|
+
customInputClassName: "input--search-icon-with-placeholder",
|
|
125
|
+
onConfirm,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await waitFor(() => {
|
|
129
|
+
expect(document.querySelector(".autocomplete__clear")).toBeVisible();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(
|
|
133
|
+
document
|
|
134
|
+
.querySelector(".autocomplete__wrapper")
|
|
135
|
+
?.contains(document.querySelector(".autocomplete__clear")),
|
|
136
|
+
).toBe(true);
|
|
137
|
+
|
|
138
|
+
const input = document.querySelector<HTMLInputElement>(
|
|
139
|
+
".autocomplete__input",
|
|
140
|
+
);
|
|
141
|
+
const clearButton = document.querySelector<HTMLButtonElement>(
|
|
142
|
+
".autocomplete__clear",
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
expect(input?.value).toBe("apple");
|
|
146
|
+
|
|
147
|
+
fireEvent.click(clearButton as HTMLButtonElement);
|
|
148
|
+
|
|
149
|
+
expect(input?.value).toBe("");
|
|
150
|
+
expect(select.value).toBe("");
|
|
151
|
+
expect(clearButton).not.toBeVisible();
|
|
152
|
+
expect(onConfirm).toHaveBeenCalledWith("");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("keeps clear button keyboard events from reaching autocomplete wrapper", async () => {
|
|
156
|
+
const select = document.createElement("select");
|
|
157
|
+
const emptyOption = new Option("", "");
|
|
158
|
+
const appleOption = new Option("apple", "apple", true, true);
|
|
159
|
+
|
|
160
|
+
select.append(emptyOption, appleOption);
|
|
161
|
+
document.body.appendChild(select);
|
|
162
|
+
|
|
163
|
+
new AutocompleteStatic(select, {
|
|
164
|
+
customInputClassName: "input--search-icon-with-placeholder",
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
await waitFor(() => {
|
|
168
|
+
expect(document.querySelector(".autocomplete__clear")).toBeVisible();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const wrapper = document.querySelector(".autocomplete__wrapper");
|
|
172
|
+
const clearButton = document.querySelector<HTMLButtonElement>(
|
|
173
|
+
".autocomplete__clear",
|
|
174
|
+
);
|
|
175
|
+
const onWrapperKeydown = vi.fn();
|
|
176
|
+
|
|
177
|
+
wrapper?.addEventListener("keydown", onWrapperKeydown);
|
|
178
|
+
|
|
179
|
+
fireEvent.keyDown(clearButton as HTMLButtonElement, {
|
|
180
|
+
key: " ",
|
|
181
|
+
code: "Space",
|
|
182
|
+
keyCode: 32,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(onWrapperKeydown).not.toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
border-color: var(--color-surface-moderate);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
.is-dark .daypickr__select {
|
|
75
75
|
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' fill='white' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20 10L12 18L4 10L6 8L12 14L18 8L20 10Z'/%3E%3C/svg%3E%0A");
|
|
76
76
|
}
|
|
@@ -109,7 +109,6 @@
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
.bg-black .daypickr__button--pagination,
|
|
113
112
|
.is-dark .daypickr__button--pagination {
|
|
114
113
|
&-prev::before {
|
|
115
114
|
background-image: url("data:image/svg+xml,%3Csvg width='20' height='20' fill='white' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14 20L6 12L14 4L16 6L10 12L16 18L14 20Z'/%3E%3C/svg%3E%0A");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import cx from "classnames";
|
|
2
2
|
import React, { HTMLAttributes, ReactNode } from "react";
|
|
3
|
+
import { Autocomplete } from "../Autocomplete/Autocomplete";
|
|
3
4
|
import { Select } from "../Select/Select";
|
|
4
5
|
import { TextInput } from "../TextInput/TextInput";
|
|
5
6
|
|
|
@@ -8,7 +9,7 @@ import { Item } from "./Item";
|
|
|
8
9
|
const CLASS_ROOT = "control-group";
|
|
9
10
|
|
|
10
11
|
interface ControlShape {
|
|
11
|
-
type: "text" | "select";
|
|
12
|
+
type: "autocomplete" | "text" | "select";
|
|
12
13
|
[key: string]: any;
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -35,6 +36,8 @@ const Group: React.FC<GroupProps> = ({
|
|
|
35
36
|
|
|
36
37
|
const ControlElement = (props: any) => {
|
|
37
38
|
switch (type) {
|
|
39
|
+
case "autocomplete":
|
|
40
|
+
return <Autocomplete {...props} />;
|
|
38
41
|
case "select":
|
|
39
42
|
return <Select {...props} />;
|
|
40
43
|
default:
|