@orangesk/orange-design-system 2.0.0-beta.47 → 2.0.0-beta.48
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/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 +4 -11
- package/build/components/types/src/components/Carousel/Carousel.static.d.ts +4 -1
- 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 +4 -4
- package/package.json +17 -17
- package/src/components/BlockAction/styles/style.scss +6 -4
- package/src/components/Breadcrumbs/styles/style.scss +2 -1
- package/src/components/Carousel/Carousel.static.ts +29 -1
- package/src/components/Carousel/tests/Carousel.static.test.jsx +50 -0
- 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 +45 -47
- 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
|
@@ -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:
|
|
@@ -39,15 +39,32 @@
|
|
|
39
39
|
border-radius: 0 !important;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
> *:not(:first-child):not(:last-child) .autocomplete__input {
|
|
43
|
+
border-radius: 0 !important;
|
|
44
|
+
}
|
|
45
|
+
|
|
42
46
|
> *:first-child:not(:last-child) {
|
|
43
47
|
border-top-right-radius: 0 !important;
|
|
44
48
|
border-bottom-right-radius: 0 !important;
|
|
45
49
|
}
|
|
46
50
|
|
|
51
|
+
> *:first-child:not(:last-child) .autocomplete__input,
|
|
52
|
+
> *:has(+ .autocomplete[style*="display: none"] + *) .autocomplete__input {
|
|
53
|
+
border-top-right-radius: 0 !important;
|
|
54
|
+
border-bottom-right-radius: 0 !important;
|
|
55
|
+
}
|
|
56
|
+
|
|
47
57
|
> *:last-child:not(:first-child) {
|
|
48
58
|
border-top-left-radius: 0 !important;
|
|
49
59
|
border-bottom-left-radius: 0 !important;
|
|
50
60
|
}
|
|
61
|
+
|
|
62
|
+
> *:last-child:not(:first-child) .autocomplete__input,
|
|
63
|
+
> *:not(:first-child):has(+ .autocomplete[style*="display: none"])
|
|
64
|
+
.autocomplete__input {
|
|
65
|
+
border-top-left-radius: 0 !important;
|
|
66
|
+
border-bottom-left-radius: 0 !important;
|
|
67
|
+
}
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
@mixin item($config: config.$item) {
|
|
@@ -109,6 +109,15 @@ describe("rendering Group", () => {
|
|
|
109
109
|
);
|
|
110
110
|
expect(container.getElementsByClassName("select").length).toBe(1);
|
|
111
111
|
});
|
|
112
|
+
it("renders Autocomplete component (autocomplete class) when control type is set to autocomplete", () => {
|
|
113
|
+
const { container } = render(
|
|
114
|
+
<Group
|
|
115
|
+
data-testid="test-id"
|
|
116
|
+
control={{ type: "autocomplete", options: ["", "apple"] }}
|
|
117
|
+
/>,
|
|
118
|
+
);
|
|
119
|
+
expect(container.getElementsByClassName("autocomplete").length).toBe(1);
|
|
120
|
+
});
|
|
112
121
|
it("addonPrefix Item has class control-group__item--large when size is set to large", () => {
|
|
113
122
|
const { container } = render(
|
|
114
123
|
<Group
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
@include generate.css-map($config, "disabled");
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
&:read-only {
|
|
25
|
+
&:read-only:not(:disabled) {
|
|
26
26
|
@include generate.css-map($config, "readonly");
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -30,6 +30,12 @@
|
|
|
30
30
|
@include generate.css-map($config, "invalid");
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
&::placeholder {
|
|
34
|
+
@include generate.css-map($config, "placeholder");
|
|
35
|
+
|
|
36
|
+
opacity: 1;
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
&.is-valid {
|
|
34
40
|
padding-right: 36px;
|
|
35
41
|
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 15 15'%3E%3Cpath d='M7.5,15 C3.35786438,15 0,11.6421356 0,7.5 C0,3.35786438 3.35786438,0 7.5,0 C11.6421356,0 15,3.35786438 15,7.5 C15,11.6421356 11.6421356,15 7.5,15 Z M3.40550597,6.68901229 C2.99100286,7.0539857 2.95085113,7.6858763 3.31582458,8.1003794 L5.77477826,10.8930318 C6.1397517,11.3075349 6.7716423,11.3476866 7.18614541,10.9827132 C7.20478962,10.9662968 7.22281563,10.9491914 7.24018594,10.9314326 L11.8941565,6.17336831 C12.2803381,5.7785491 12.2733359,5.14542284 11.8785167,4.75924123 C11.4836975,4.37305961 10.8505712,4.38006176 10.4643896,4.77488097 L7.31720124,7.9924615 C6.93101963,8.3872807 6.29789337,8.3942828 5.90307416,8.0081012 C5.88531533,7.9907309 5.86820993,7.9727049 5.85179355,7.9540607 L4.81687313,6.77869369 C4.45189969,6.36419058 3.82000909,6.32403885 3.40550597,6.68901229 Z' fill='%2332c832' /%3E%3C/svg%3E");
|