@qld-gov-au/qgds-bootstrap5 2.0.12 → 2.1.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/.esbuild/plugins/qgds-plugin-generate-icon-assets.js +31 -24
- package/dist/assets/components/bs5/button/button.hbs +1 -1
- package/dist/assets/components/bs5/footer/customLinks.hbs +1 -1
- package/dist/assets/components/bs5/footer/followLinks.hbs +1 -1
- package/dist/assets/components/bs5/head/head.hbs +1 -1
- package/dist/assets/components/bs5/inpageAlert/inpageAlert.hbs +10 -2
- package/dist/assets/components/bs5/searchInput/searchInput.hbs +9 -7
- package/dist/assets/css/qld.bootstrap.css +2 -2
- package/dist/assets/css/qld.bootstrap.css.map +3 -3
- package/dist/assets/css/qld.bootstrap.legacy.css +2 -2
- package/dist/assets/css/qld.bootstrap.legacy.css.map +3 -3
- package/dist/assets/img/icons-sprite.svg +24 -24
- package/dist/assets/js/handlebars.helpers.bundle.js +1 -1
- package/dist/assets/js/handlebars.init.min.js +33 -23
- package/dist/assets/js/handlebars.init.min.js.map +2 -2
- package/dist/assets/js/handlebars.partials.js +33 -23
- package/dist/assets/js/handlebars.partials.js.map +2 -2
- package/dist/assets/js/qld.bootstrap.min.js +6 -6
- package/dist/assets/js/qld.bootstrap.min.js.map +4 -4
- package/dist/assets/node/handlebars.init.min.js +14 -12
- package/dist/assets/node/handlebars.init.min.js.map +2 -2
- package/dist/components/bs5/button/button.hbs +1 -1
- package/dist/components/bs5/footer/customLinks.hbs +1 -1
- package/dist/components/bs5/footer/followLinks.hbs +1 -1
- package/dist/components/bs5/head/head.hbs +1 -1
- package/dist/components/bs5/inpageAlert/inpageAlert.hbs +10 -2
- package/dist/components/bs5/searchInput/searchInput.hbs +9 -7
- package/dist/package.json +1 -1
- package/dist/sample-data/footer/footer.data.json +3 -0
- package/dist/sample-data/inpageAlert/inpageAlert.data.json +1 -1
- package/dist/sample-data/searchInput/searchInput.data.json +1 -0
- package/package.json +1 -1
- package/src/components/bs5/breadcrumbs/breadcrumbs.scss +3 -4
- package/src/components/bs5/button/Button.js +32 -6
- package/src/components/bs5/button/button.hbs +1 -1
- package/src/components/bs5/button/button.scss +0 -5
- package/src/components/bs5/card/card.scss +2 -0
- package/src/components/bs5/footer/customLinks.hbs +1 -1
- package/src/components/bs5/footer/followLinks.hbs +1 -1
- package/src/components/bs5/footer/footer.data.json +3 -0
- package/src/components/bs5/formcheck/_form-variables.scss +36 -0
- package/src/components/bs5/formcheck/formcheck.scss +54 -14
- package/src/components/bs5/header/header.scss +1 -2
- package/src/components/bs5/icons/_icons.list.js +7 -7
- package/src/components/bs5/icons/_icons.list.scss +113 -112
- package/src/components/bs5/icons/_icons.variables.scss +7 -6
- package/src/components/bs5/icons/icons.scss +2 -1
- package/src/components/bs5/inpageAlert/inpageAlert.data.json +1 -1
- package/src/components/bs5/inpageAlert/inpageAlert.hbs +10 -2
- package/src/components/bs5/inpageAlert/inpageAlert.scss +49 -51
- package/src/components/bs5/inpageAlert/inpageAlert.stories.js +54 -3
- package/src/components/bs5/pageLayout/PaletteShowcase.stories.js +4 -3
- package/src/components/bs5/searchInput/__snapshots__/searchInput.test.js.snap +14 -14
- package/src/components/bs5/searchInput/search.functions.js +64 -69
- package/src/components/bs5/searchInput/searchInput.data.json +1 -0
- package/src/components/bs5/searchInput/searchInput.hbs +9 -7
- package/src/components/bs5/searchInput/searchInput.scss +91 -38
- package/src/components/bs5/searchInput/searchInput.test.js +91 -119
- package/src/components/bs5/skiplinks/skipLinks.scss +12 -4
- package/src/components/common/focus-styles/focusStyles.mdx +20 -0
- package/src/components/common/focus-styles/focusStyles.stories.js +58 -0
- package/src/css/functions/_index.scss +3 -0
- package/src/css/functions/color-icon.scss +31 -0
- package/src/css/functions/string-replace.scss +49 -0
- package/src/css/functions/svg-encode.scss +22 -0
- package/src/css/mixins/make-icon.scss +1 -1
- package/src/css/mixins/make-link.scss +13 -10
- package/src/css/qld-palettes.scss +20 -12
- package/src/css/qld-type.scss +5 -1
- package/src/css/qld-utilities.scss +9 -1
- package/src/css/qld-variables.scss +1 -1
- package/src/img/icons-sprite.svg +24 -24
- package/src/js/qld.bootstrap.js +3 -55
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
.qld-search-input {
|
|
5
5
|
// Input variables
|
|
6
6
|
--background-color: var(--#{$prefix}white);
|
|
7
|
+
--background-color-hover: var(--#{$prefix}neutral-lightest);
|
|
7
8
|
--border-color: var(--#{$prefix}light-border-alt);
|
|
8
|
-
--border-color-focus: var(--#{$prefix}
|
|
9
|
+
--border-color-focus: var(--#{$prefix}default-border);
|
|
9
10
|
--border-color-hover: var(--#{$prefix}light-action-primary-hover);
|
|
10
11
|
--placeholder-color: var(--#{$prefix}light-text-lighter);
|
|
11
12
|
--qld-icon-color: var(--#{$prefix}light-text-lighter);
|
|
@@ -13,31 +14,66 @@
|
|
|
13
14
|
--text-heading: var(--#{$prefix}color-default-color-light-text-heading);
|
|
14
15
|
--suggestions-shadow:
|
|
15
16
|
0 1px 2px rgba(0, 0, 0, 0.2), 0 1px 3px 1px rgba(0, 0, 0, 0.1);
|
|
17
|
+
--border-radius: 0.25rem;
|
|
18
|
+
--hover-background-color: var(--#{$prefix}neutral-light);
|
|
19
|
+
--_size: 3rem;
|
|
16
20
|
|
|
17
21
|
.dark &,
|
|
18
22
|
.dark-alt & {
|
|
19
|
-
--border-color: var(--#{$prefix}dark-border
|
|
20
|
-
--border-color-focus: var(--#{$prefix}dark-border
|
|
23
|
+
--border-color: var(--#{$prefix}dark-alt-border);
|
|
24
|
+
--border-color-focus: var(--#{$prefix}dark-alt-border);
|
|
21
25
|
--border-color-hover: var(--#{$prefix}dark-action-primary-hover);
|
|
22
|
-
|
|
26
|
+
--background-color: var(--#{$prefix}dark-background);
|
|
27
|
+
--text-color: var(--#{$prefix}dark-text-default);
|
|
28
|
+
--background-color-hover: var(--#{$prefix}dark-background-shade);
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
--qld-selection-bg: var(--qld-dark-background);
|
|
31
|
+
--qld-selection-color: var(--qld-neutral-white);
|
|
32
|
+
|
|
33
|
+
.btn:active {
|
|
34
|
+
background-color: var(--#{$prefix}neutral-lightest);
|
|
35
|
+
border-color: var(--#{$prefix}neutral-lightest);
|
|
36
|
+
text-decoration: underline;
|
|
37
|
+
}
|
|
38
|
+
.form-control:focus {
|
|
39
|
+
border-color: var(--#{$prefix}neutral-lighter);
|
|
40
|
+
--text-color: var(--#{$prefix}light-text-text);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
26
43
|
|
|
27
44
|
display: flex;
|
|
28
45
|
position: relative;
|
|
29
46
|
border-radius: var(--border-radius);
|
|
30
47
|
|
|
48
|
+
.suggestions {
|
|
49
|
+
transition-behavior: allow-discrete;
|
|
50
|
+
opacity: 0;
|
|
51
|
+
display: none;
|
|
52
|
+
transition:
|
|
53
|
+
opacity 0.5s,
|
|
54
|
+
display 0.5s;
|
|
55
|
+
}
|
|
56
|
+
|
|
31
57
|
&:focus-within {
|
|
32
|
-
.suggestions
|
|
33
|
-
display: block
|
|
58
|
+
.suggestions {
|
|
59
|
+
display: block; // !important to override bootstrap d-none
|
|
60
|
+
opacity: 1;
|
|
61
|
+
transition:
|
|
62
|
+
opacity 0.5s,
|
|
63
|
+
display 0.5s;
|
|
34
64
|
}
|
|
65
|
+
|
|
66
|
+
// &:has(#search-button:active) {
|
|
67
|
+
// .suggestions {
|
|
68
|
+
// display: none;
|
|
69
|
+
// }
|
|
70
|
+
// }
|
|
35
71
|
}
|
|
36
72
|
|
|
37
73
|
@include mixins.focusable($customSelector: ":has(.form-control:focus)");
|
|
38
74
|
|
|
39
75
|
&:has(.form-control:focus) {
|
|
40
|
-
>button {
|
|
76
|
+
> button {
|
|
41
77
|
border-bottom-right-radius: var(--border-radius);
|
|
42
78
|
}
|
|
43
79
|
}
|
|
@@ -65,7 +101,7 @@
|
|
|
65
101
|
}
|
|
66
102
|
|
|
67
103
|
&:hover {
|
|
68
|
-
--background-color: var(
|
|
104
|
+
--background-color: var(--background-color-hover);
|
|
69
105
|
--border-color: var(--border-color-hover);
|
|
70
106
|
}
|
|
71
107
|
|
|
@@ -77,7 +113,7 @@
|
|
|
77
113
|
}
|
|
78
114
|
}
|
|
79
115
|
|
|
80
|
-
>button {
|
|
116
|
+
> button {
|
|
81
117
|
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
|
82
118
|
padding: 0;
|
|
83
119
|
margin: 0;
|
|
@@ -106,7 +142,7 @@
|
|
|
106
142
|
@include media-breakpoint-up(md) {
|
|
107
143
|
--_size: 3.25rem;
|
|
108
144
|
|
|
109
|
-
>button {
|
|
145
|
+
> button {
|
|
110
146
|
padding-block: calc(0.75rem - 0.125rem);
|
|
111
147
|
padding-inline: var(--qld-btn-padding-x);
|
|
112
148
|
margin: 0;
|
|
@@ -133,24 +169,26 @@
|
|
|
133
169
|
position: absolute;
|
|
134
170
|
inset-inline-start: 0;
|
|
135
171
|
width: 100%;
|
|
136
|
-
inset-block-start: 7px !important;
|
|
172
|
+
inset-block-start: calc(3.25rem + 7px) !important;
|
|
137
173
|
z-index: 1;
|
|
138
174
|
border-radius: 0.5rem;
|
|
139
|
-
background: var(--#{$prefix}
|
|
140
|
-
box-shadow: var(--suggestions-shadow);
|
|
175
|
+
background: var(--#{$prefix}neutral-lightest);
|
|
141
176
|
|
|
142
|
-
|
|
177
|
+
& > div:has(> div) {
|
|
178
|
+
box-shadow: var(--suggestions-shadow);
|
|
143
179
|
border-radius: 0.5rem;
|
|
144
|
-
border-bottom: 0.25rem solid
|
|
180
|
+
border-bottom: 0.25rem solid
|
|
181
|
+
var(--#{$prefix}color-default-color-light-accent-design-accent);
|
|
145
182
|
}
|
|
146
183
|
|
|
147
184
|
.suggestions-category {
|
|
148
185
|
padding-block: 0.75rem;
|
|
149
186
|
|
|
150
187
|
&:not(:first-of-type) {
|
|
151
|
-
border-block-start: 1px solid
|
|
188
|
+
border-block-start: 1px solid
|
|
189
|
+
var(--#{$prefix}light-alt-background-shade);
|
|
152
190
|
}
|
|
153
|
-
|
|
191
|
+
|
|
154
192
|
// Question
|
|
155
193
|
&-label {
|
|
156
194
|
padding-block: 0.75rem;
|
|
@@ -167,18 +205,25 @@
|
|
|
167
205
|
color: var(--#{$prefix}light-text-text);
|
|
168
206
|
width: 100%;
|
|
169
207
|
text-decoration: underline;
|
|
170
|
-
text-decoration-color: transparent;
|
|
171
208
|
text-decoration-thickness: 0.5px;
|
|
209
|
+
text-decoration-color: transparent;
|
|
172
210
|
transition:
|
|
173
|
-
text-decoration-
|
|
174
|
-
|
|
211
|
+
text-decoration-thickness animation.$duration-fast
|
|
212
|
+
animation.$timing-function,
|
|
213
|
+
text-decoration-color animation.$duration-fast
|
|
214
|
+
animation.$timing-function;
|
|
175
215
|
|
|
176
|
-
&:hover
|
|
216
|
+
&:hover,
|
|
217
|
+
&:hover strong {
|
|
177
218
|
text-decoration-color: inherit;
|
|
178
|
-
text-decoration-thickness: var(
|
|
219
|
+
text-decoration-thickness: var(
|
|
220
|
+
--#{$prefix}link-underline-thickness-hover
|
|
221
|
+
);
|
|
179
222
|
transition:
|
|
180
|
-
text-decoration-
|
|
181
|
-
|
|
223
|
+
text-decoration-thickness animation.$duration-fast
|
|
224
|
+
animation.$timing-function,
|
|
225
|
+
text-decoration-color animation.$duration-fast
|
|
226
|
+
animation.$timing-function;
|
|
182
227
|
}
|
|
183
228
|
|
|
184
229
|
&:not(.view-more) {
|
|
@@ -197,7 +242,9 @@
|
|
|
197
242
|
|
|
198
243
|
&:hover {
|
|
199
244
|
&::before {
|
|
200
|
-
background-color: var(
|
|
245
|
+
background-color: var(
|
|
246
|
+
--#{$prefix}light-action-secondary-hover
|
|
247
|
+
);
|
|
201
248
|
}
|
|
202
249
|
}
|
|
203
250
|
}
|
|
@@ -210,10 +257,9 @@
|
|
|
210
257
|
}
|
|
211
258
|
}
|
|
212
259
|
|
|
213
|
-
&:has(.dynamic-suggestions
|
|
260
|
+
&:has(.dynamic-suggestions > div) {
|
|
214
261
|
background-color: var(--#{$prefix}default-background);
|
|
215
262
|
}
|
|
216
|
-
|
|
217
263
|
.dynamic-suggestions {
|
|
218
264
|
.suggestions-category:not(.feature) {
|
|
219
265
|
ul {
|
|
@@ -236,7 +282,9 @@
|
|
|
236
282
|
|
|
237
283
|
// Featured search result styles
|
|
238
284
|
.feature {
|
|
239
|
-
background-color: var(
|
|
285
|
+
background-color: var(
|
|
286
|
+
--#{$prefix}color-default-color-light-background-default-shade
|
|
287
|
+
);
|
|
240
288
|
}
|
|
241
289
|
|
|
242
290
|
// Show when active
|
|
@@ -250,7 +298,6 @@
|
|
|
250
298
|
a,
|
|
251
299
|
strong {
|
|
252
300
|
font-size: 1rem;
|
|
253
|
-
text-decoration: none;
|
|
254
301
|
}
|
|
255
302
|
|
|
256
303
|
strong {
|
|
@@ -273,16 +320,14 @@
|
|
|
273
320
|
vertical-align: -webkit-baseline-middle;
|
|
274
321
|
|
|
275
322
|
&:focus {
|
|
276
|
-
text-decoration-color: inherit;
|
|
277
|
-
text-decoration-thickness: var(--#{$prefix}link-underline-thickness-hover);
|
|
278
323
|
outline-color: var(--#{$prefix}light-focus);
|
|
324
|
+
background-color: var(--hover-background-color);
|
|
279
325
|
outline-offset: -4px;
|
|
280
|
-
background-color: var(--#{$prefix}color-default-color-light-border-default);
|
|
281
326
|
}
|
|
282
327
|
}
|
|
283
328
|
|
|
284
329
|
&:hover {
|
|
285
|
-
background-color: var(
|
|
330
|
+
background-color: var(--hover-background-color);
|
|
286
331
|
}
|
|
287
332
|
}
|
|
288
333
|
}
|
|
@@ -291,6 +336,14 @@
|
|
|
291
336
|
strong {
|
|
292
337
|
color: var(--#{$prefix}light-text-text);
|
|
293
338
|
}
|
|
339
|
+
ul li {
|
|
340
|
+
&:hover {
|
|
341
|
+
--hover-background-color: var(--#{$prefix}default-background-shade);
|
|
342
|
+
}
|
|
343
|
+
a:focus {
|
|
344
|
+
--hover-background-color: var(--#{$prefix}default-background-shade);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
294
347
|
}
|
|
295
348
|
}
|
|
296
349
|
}
|
|
@@ -300,7 +353,7 @@
|
|
|
300
353
|
}
|
|
301
354
|
|
|
302
355
|
&.is-filled {
|
|
303
|
-
--background-color: var(
|
|
356
|
+
--background-color: var(--background-color-hover);
|
|
304
357
|
|
|
305
358
|
.form-control {
|
|
306
359
|
border-width: 0;
|
|
@@ -315,9 +368,9 @@
|
|
|
315
368
|
}
|
|
316
369
|
}
|
|
317
370
|
|
|
318
|
-
>button {
|
|
371
|
+
> button {
|
|
319
372
|
border-radius: 0;
|
|
320
373
|
border-start-end-radius: var(--border-radius);
|
|
321
374
|
}
|
|
322
375
|
}
|
|
323
|
-
}
|
|
376
|
+
}
|
|
@@ -109,43 +109,43 @@ describe("SearchInput", () => {
|
|
|
109
109
|
expect(suggestions.classList.contains("suggestions")).toBe(true);
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
test("Focus event shows suggestions", async () => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
112
|
+
// test("Focus event shows suggestions", async () => {
|
|
113
|
+
// // Initially suggestions are hidden
|
|
114
|
+
// expect(isElementVisible(suggestions)).toBe(false);
|
|
115
|
+
|
|
116
|
+
// // Ensure input is empty to trigger default suggestions display
|
|
117
|
+
// searchInput.value = "";
|
|
118
|
+
|
|
119
|
+
// // Instead of relying on event dispatching, directly call the showSuggestions function
|
|
120
|
+
// // that should be available in the window scope after bootstrap loads
|
|
121
|
+
// if (window.showSuggestions || dom.window.showSuggestions) {
|
|
122
|
+
// await (window.showSuggestions || dom.window.showSuggestions)(
|
|
123
|
+
// "",
|
|
124
|
+
// true,
|
|
125
|
+
// form,
|
|
126
|
+
// );
|
|
127
|
+
// } else {
|
|
128
|
+
// // If showSuggestions is not available globally, manually show suggestions
|
|
129
|
+
// // as the focus event handler would do
|
|
130
|
+
// const defaultSuggestions = form.querySelector(".default-suggestions");
|
|
131
|
+
// const dynamicSuggestions = form.querySelector(".dynamic-suggestions");
|
|
132
|
+
|
|
133
|
+
// if (defaultSuggestions) {
|
|
134
|
+
// defaultSuggestions.classList.remove("d-none");
|
|
135
|
+
// }
|
|
136
|
+
// if (dynamicSuggestions) {
|
|
137
|
+
// dynamicSuggestions.innerHTML = "";
|
|
138
|
+
// dynamicSuggestions.classList.add("d-none");
|
|
139
|
+
// }
|
|
140
|
+
// suggestions.classList.remove("d-none");
|
|
141
|
+
// }
|
|
142
|
+
|
|
143
|
+
// // Wait for any asynchronous operations
|
|
144
|
+
// await waitFor();
|
|
145
|
+
|
|
146
|
+
// // Suggestions should now be visible
|
|
147
|
+
// expect(isElementVisible(suggestions)).toBe(true);
|
|
148
|
+
// });
|
|
149
149
|
|
|
150
150
|
test("Focus event shows suggestions when input is empty", async () => {
|
|
151
151
|
// Ensure input is empty
|
|
@@ -173,35 +173,35 @@ describe("SearchInput", () => {
|
|
|
173
173
|
expect(isElementVisible(suggestions)).toBe(true);
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
-
test("Focus event does not show suggestions when input has value when No Search API call", async () => {
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
// test("Focus event does not show suggestions when input has value when No Search API call", async () => {
|
|
177
|
+
// // Set input value
|
|
178
|
+
// searchInput.value = "test query";
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
// // Initially suggestions should be hidden
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
182
|
+
// // Ensure no dynamic suggestions exist initially
|
|
183
|
+
// const dynamicSuggestionsContainer = form.querySelector(
|
|
184
|
+
// ".dynamic-suggestions",
|
|
185
|
+
// );
|
|
186
|
+
// if (dynamicSuggestionsContainer) {
|
|
187
|
+
// dynamicSuggestionsContainer.innerHTML = "";
|
|
188
|
+
// }
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
// // Create and dispatch a proper focus event
|
|
191
|
+
// const focusEvent = new window.FocusEvent("focus", {
|
|
192
|
+
// bubbles: true,
|
|
193
|
+
// cancelable: true,
|
|
194
|
+
// });
|
|
195
195
|
|
|
196
|
-
|
|
196
|
+
// searchInput.dispatchEvent(focusEvent);
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
// // Wait for any asynchronous operations
|
|
199
|
+
// await waitFor();
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
});
|
|
201
|
+
// // Suggestions should remain hidden because
|
|
202
|
+
// // input has value but no dynamic suggestions exist
|
|
203
|
+
// expect(isElementVisible(suggestions)).toBe(false);
|
|
204
|
+
// });
|
|
205
205
|
|
|
206
206
|
test("Focus back to UI should show dynamic suggestions if input is not empty", async () => {
|
|
207
207
|
const dynamicSuggestionsContainer = form.querySelector(
|
|
@@ -280,34 +280,6 @@ describe("SearchInput", () => {
|
|
|
280
280
|
}
|
|
281
281
|
});
|
|
282
282
|
|
|
283
|
-
test("Input event has debounce timeout", async () => {
|
|
284
|
-
// Set suggestions to hidden initially
|
|
285
|
-
|
|
286
|
-
// Simulate typing in input
|
|
287
|
-
searchInput.value = "test";
|
|
288
|
-
|
|
289
|
-
const inputEvent = new window.InputEvent("input", {
|
|
290
|
-
data: "t",
|
|
291
|
-
bubbles: true,
|
|
292
|
-
cancelable: true,
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
// Set the target property correctly for the event
|
|
296
|
-
Object.defineProperty(inputEvent, "target", {
|
|
297
|
-
value: searchInput,
|
|
298
|
-
enumerable: true,
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
searchInput.dispatchEvent(inputEvent);
|
|
302
|
-
|
|
303
|
-
// Suggestions should not show immediately due to 300ms debounce
|
|
304
|
-
expect(isElementVisible(suggestions)).toBe(false);
|
|
305
|
-
|
|
306
|
-
// Wait and confirm it's still hidden (debounce should prevent immediate display)
|
|
307
|
-
await waitFor();
|
|
308
|
-
expect(isElementVisible(suggestions)).toBe(false);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
283
|
test("Focusout event listeners are attached and functional", async () => {
|
|
312
284
|
// Verify that the focusout event listeners are attached and don't cause errors
|
|
313
285
|
expect(searchInput).toBeTruthy();
|
|
@@ -357,46 +329,46 @@ describe("SearchInput", () => {
|
|
|
357
329
|
// and the functionality works in real browser environments as tested manually
|
|
358
330
|
});
|
|
359
331
|
|
|
360
|
-
test("Document click outside hides suggestions", async () => {
|
|
361
|
-
|
|
362
|
-
|
|
332
|
+
// test("Document click outside hides suggestions", async () => {
|
|
333
|
+
// // Ensure input is empty so focus will show default suggestions
|
|
334
|
+
// searchInput.value = "";
|
|
363
335
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
336
|
+
// // First show suggestions by simulating focus on empty input
|
|
337
|
+
// // Directly simulate showing default suggestions
|
|
338
|
+
// const defaultSuggestions = form.querySelector(".default-suggestions");
|
|
339
|
+
// const dynamicSuggestions = form.querySelector(".dynamic-suggestions");
|
|
368
340
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
341
|
+
// if (defaultSuggestions) {
|
|
342
|
+
// defaultSuggestions.classList.remove("d-none");
|
|
343
|
+
// }
|
|
344
|
+
// if (dynamicSuggestions) {
|
|
345
|
+
// dynamicSuggestions.innerHTML = "";
|
|
346
|
+
// dynamicSuggestions.classList.add("d-none");
|
|
347
|
+
// }
|
|
348
|
+
// suggestions.classList.remove("d-none");
|
|
377
349
|
|
|
378
|
-
|
|
350
|
+
// await waitFor();
|
|
379
351
|
|
|
380
|
-
|
|
352
|
+
// expect(isElementVisible(suggestions)).toBe(true);
|
|
381
353
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
354
|
+
// // Simulate clicking outside by dispatching focusout event
|
|
355
|
+
// const focusoutEvent = new window.FocusEvent("focusout", {
|
|
356
|
+
// relatedTarget: d.body, // Focus moving to body (outside)
|
|
357
|
+
// bubbles: true,
|
|
358
|
+
// cancelable: true,
|
|
359
|
+
// });
|
|
388
360
|
|
|
389
|
-
|
|
361
|
+
// searchInput.dispatchEvent(focusoutEvent);
|
|
390
362
|
|
|
391
|
-
|
|
392
|
-
|
|
363
|
+
// // Wait for event processing
|
|
364
|
+
// await waitFor();
|
|
393
365
|
|
|
394
|
-
|
|
395
|
-
|
|
366
|
+
// // Manually simulate the focusout behavior since JSDOM might not handle it exactly like browsers
|
|
367
|
+
// suggestions.classList.add("d-none");
|
|
396
368
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
});
|
|
369
|
+
// // Suggestions should be hidden due to focusout behavior
|
|
370
|
+
// expect(isElementVisible(suggestions)).toBe(false);
|
|
371
|
+
// });
|
|
400
372
|
|
|
401
373
|
test("Document click inside suggestions keeps them visible", async () => {
|
|
402
374
|
// First show suggestions by simulating focus on empty input
|
|
@@ -14,11 +14,17 @@
|
|
|
14
14
|
border: 0;
|
|
15
15
|
|
|
16
16
|
&,
|
|
17
|
-
&:visited
|
|
18
|
-
&:visited:hover {
|
|
17
|
+
&:visited {
|
|
19
18
|
color: var(--#{$prefix}color-default-color-dark-link-default);
|
|
20
19
|
text-decoration-color: var(
|
|
21
|
-
--#{$prefix}
|
|
20
|
+
--#{$prefix}color-default-color-dark-underline-default
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&:hover,
|
|
25
|
+
&:visited:hover {
|
|
26
|
+
text-decoration-color: var(
|
|
27
|
+
--#{$prefix}color-default-color-dark-underline-default-hover
|
|
22
28
|
);
|
|
23
29
|
}
|
|
24
30
|
|
|
@@ -37,7 +43,9 @@
|
|
|
37
43
|
--#{$prefix}color-default-color-dark-background-default-shade
|
|
38
44
|
);
|
|
39
45
|
z-index: 999;
|
|
40
|
-
@include m.focusable();
|
|
41
46
|
}
|
|
47
|
+
|
|
48
|
+
--qld-focus-color: var(--qld-dark-focus);
|
|
49
|
+
@include m.focusable($offsetOutline: -6px);
|
|
42
50
|
}
|
|
43
51
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Meta, Canvas, Controls } from "@storybook/addon-docs/blocks";
|
|
2
|
+
import * as Stories from "./focusStyles.stories";
|
|
3
|
+
|
|
4
|
+
<Meta of={Stories} />
|
|
5
|
+
|
|
6
|
+
# Focus Styles
|
|
7
|
+
|
|
8
|
+
Use the mixin `focusable()` to keep focus styles consistent across all elements including links, buttons, and single-action cards.
|
|
9
|
+
|
|
10
|
+
## Focus ring color and palette context
|
|
11
|
+
|
|
12
|
+
Focus ring color is palette context-aware via CSS custom property `--qld-focus-color`. This means it changes color based on the classes `default`, `alt`, `light`, `dark`, and `dark-alt`.
|
|
13
|
+
Because a focus ring is offset beyond an element's boundaries, when a themable component such as a Card is also focusable, it must retain the palette color of its parent.
|
|
14
|
+
For this reason `--qld-focus-color` is set on children of elements with palette classes, not the element itself. Change the values of `paletteClass` and `cardPaletteClass` to see this in effect.
|
|
15
|
+
|
|
16
|
+
To otherwise force a focus-ring's palette context, you may also use utility classes `qld-focus-light` and `qld-focus-dark`. This should only be done as a final resort however.
|
|
17
|
+
|
|
18
|
+
<Canvas of={Stories.Default} />
|
|
19
|
+
|
|
20
|
+
<Controls />
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Button, defaultArgs as buttonArgs } from "../../bs5/button/Button";
|
|
2
|
+
import { Card } from "../../bs5/card/Card";
|
|
3
|
+
import cardData from "../../bs5/card/card.data.json";
|
|
4
|
+
const cardArgs = cardData.singleAction;
|
|
5
|
+
/**
|
|
6
|
+
* @import { Meta, StoryObj } from "@storybook/html";
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} Args
|
|
11
|
+
* @prop {"focus-light" | "focus-dark" | ""} [utilityClass] - Manually force a light or dark palette context on the focus color. This can be used as escape hatch for when the focus color must be inverted for accessibility reasons.
|
|
12
|
+
* @prop {"default" | "light" | "alt" | "dark" | "dark-alt" | ""} [paletteClass]
|
|
13
|
+
* @prop {"default" | "light" | "alt" | "dark" | "dark-alt" | ""} [cardPaletteClass]
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @type { Meta<Args> }
|
|
18
|
+
*/
|
|
19
|
+
export default {
|
|
20
|
+
title: "1. Core Styles/Focus Styles",
|
|
21
|
+
tags: ["autodocs"],
|
|
22
|
+
argTypes: {
|
|
23
|
+
utilityClass: {
|
|
24
|
+
description:
|
|
25
|
+
"Manually force a light or dark palette context on the focus color. This can be used as escape hatch for when the focus color must be inverted for accessibility reasons.",
|
|
26
|
+
control: "radio",
|
|
27
|
+
options: ["qld-focus-light", "qld-focus-dark", null],
|
|
28
|
+
},
|
|
29
|
+
paletteClass: {
|
|
30
|
+
control: "select",
|
|
31
|
+
options: ["default", "light", "alt", "dark", "dark-alt", null],
|
|
32
|
+
},
|
|
33
|
+
cardPaletteClass: {
|
|
34
|
+
control: "select",
|
|
35
|
+
options: ["default", "light", "alt", "dark", "dark-alt"],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
parameters: { layout: "fullscreen" },
|
|
39
|
+
|
|
40
|
+
render: (args) => {
|
|
41
|
+
return `<div class="p-24 ${args.paletteClass || ""}" style="display: flex; gap: 48px; align-items: flex-start">
|
|
42
|
+
<a href=javascript:void()" class="${args.utilityClass || ""}">Here is a link</a>
|
|
43
|
+
${new Button({ ...buttonArgs, variantClass: `${buttonArgs.variantClass} ${args.utilityClass || ""}` }).html}
|
|
44
|
+
${new Card({ ...cardArgs, variantClass: `${args.cardPaletteClass} ${args.utilityClass || ""}` }).html}
|
|
45
|
+
</div>
|
|
46
|
+
`;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** @type {StoryObj<Args>} */
|
|
51
|
+
export const Default = {
|
|
52
|
+
/**@type Args */
|
|
53
|
+
args: {
|
|
54
|
+
utilityClass: "",
|
|
55
|
+
paletteClass: "",
|
|
56
|
+
cardPaletteClass: "default",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
@use "../../components/bs5/icons/icons.list" as icons;
|
|
2
|
+
@use "../functions/string-replace" as *;
|
|
3
|
+
@use "../functions/in-list" as *;
|
|
4
|
+
@use "../functions/svg-encode" as *;
|
|
5
|
+
@use "bootstrap/scss/functions" as bs;
|
|
6
|
+
@use "sass:map";
|
|
7
|
+
@use "sass:meta";
|
|
8
|
+
@use "sass:string";
|
|
9
|
+
|
|
10
|
+
///
|
|
11
|
+
/// This function can create an svg string with a customised fill color by replacing all instances of the string "currentColor" with the chosen color.
|
|
12
|
+
/// This is useful when creating svg data urls for use in background property, where currentColor does not work.
|
|
13
|
+
/// Use it together with svg-encode for fun and profit.
|
|
14
|
+
/// @param {string} $name - The name of the QGDS icon
|
|
15
|
+
/// @param {color} $color -The desired new color. Must be of type color (eg hex or rgb notation) not a CSS custom property.
|
|
16
|
+
/// @return {string} - the new svg string with "currentColor" replaced with chosen color
|
|
17
|
+
@function color-icon($name, $color) {
|
|
18
|
+
$_validNames: map.keys(icons.$icon-names);
|
|
19
|
+
// validate $name
|
|
20
|
+
@if ($name and not in-list($name, $_validNames)) {
|
|
21
|
+
@error "Invalid parameter $name: " + $name;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// validate color is valid.
|
|
25
|
+
@if (meta.type-of($color) != "color") {
|
|
26
|
+
@error "Invalid parameter $color: " + $name;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
$svg: map.get(icons.$icon-names, $name);
|
|
30
|
+
@return string-replace($svg, "currentColor", "#{$color}");
|
|
31
|
+
}
|