@qld-gov-au/qgds-bootstrap5 2.0.10 → 2.0.12

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 (113) hide show
  1. package/.storybook/main.mjs +2 -2
  2. package/.storybook/preview.js +5 -2
  3. package/dist/assets/components/bs5/banner/banner.hbs +3 -6
  4. package/dist/assets/components/bs5/card/card.hbs +2 -2
  5. package/dist/assets/components/bs5/dateinput/dateinput.hbs +27 -27
  6. package/dist/assets/components/bs5/formcheck/formcheck.hbs +10 -2
  7. package/dist/assets/components/bs5/head/head.hbs +1 -1
  8. package/dist/assets/components/bs5/searchInput/searchInput.hbs +31 -29
  9. package/dist/assets/components/bs5/select/select.hbs +19 -19
  10. package/dist/assets/components/bs5/tag/tag.hbs +1 -1
  11. package/dist/assets/components/bs5/textarea/textarea.hbs +17 -17
  12. package/dist/assets/components/bs5/textbox/textbox.hbs +17 -18
  13. package/dist/assets/css/qld.bootstrap.css +2 -2
  14. package/dist/assets/css/qld.bootstrap.css.map +3 -3
  15. package/dist/assets/css/qld.bootstrap.legacy.css +2 -2
  16. package/dist/assets/css/qld.bootstrap.legacy.css.map +3 -3
  17. package/dist/assets/js/handlebars.helpers.bundle.js +2 -2
  18. package/dist/assets/js/handlebars.helpers.bundle.js.map +2 -2
  19. package/dist/assets/js/handlebars.init.min.js +141 -135
  20. package/dist/assets/js/handlebars.init.min.js.map +2 -2
  21. package/dist/assets/js/handlebars.partials.js +141 -135
  22. package/dist/assets/js/handlebars.partials.js.map +2 -2
  23. package/dist/assets/js/qld.bootstrap.min.js +9 -10
  24. package/dist/assets/js/qld.bootstrap.min.js.map +4 -4
  25. package/dist/assets/node/handlebars.init.min.js +59 -11
  26. package/dist/assets/node/handlebars.init.min.js.map +2 -2
  27. package/dist/components/bs5/banner/banner.hbs +3 -6
  28. package/dist/components/bs5/card/card.hbs +2 -2
  29. package/dist/components/bs5/dateinput/dateinput.hbs +27 -27
  30. package/dist/components/bs5/formcheck/formcheck.hbs +10 -2
  31. package/dist/components/bs5/head/head.hbs +1 -1
  32. package/dist/components/bs5/searchInput/searchInput.hbs +31 -29
  33. package/dist/components/bs5/select/select.hbs +19 -19
  34. package/dist/components/bs5/tag/tag.hbs +1 -1
  35. package/dist/components/bs5/textarea/textarea.hbs +17 -17
  36. package/dist/components/bs5/textbox/textbox.hbs +17 -18
  37. package/dist/package.json +1 -1
  38. package/dist/sample-data/card/card.data.json +4 -1
  39. package/dist/sample-data/dateinput/dateinput.data.json +14 -12
  40. package/dist/sample-data/formcheck/stories/checkbox/checkbox.data.json +4 -5
  41. package/dist/sample-data/formcheck/stories/radio/radio.data.json +4 -4
  42. package/dist/sample-data/searchInput/searchInput.data.json +19 -10
  43. package/dist/sample-data/select/select.data.json +12 -10
  44. package/dist/sample-data/tag/tag.data.json +149 -143
  45. package/dist/sample-data/textarea/textarea.data.json +14 -11
  46. package/dist/sample-data/textbox/textbox.data.json +13 -10
  47. package/package.json +1 -1
  48. package/src/components/bs5/banner/banner.hbs +3 -6
  49. package/src/components/bs5/banner/banner.scss +10 -7
  50. package/src/components/bs5/banner/banner.stories.js +2 -5
  51. package/src/components/bs5/button/button.scss +4 -11
  52. package/src/components/bs5/button/button.stories.js +17 -15
  53. package/src/components/bs5/card/Card.js +31 -2
  54. package/src/components/bs5/card/Card.mdx +4 -0
  55. package/src/components/bs5/card/card--icon-list-footer.stories.js +2 -24
  56. package/src/components/bs5/card/card--multi-action.stories.js +9 -28
  57. package/src/components/bs5/card/card--no-action.stories.js +5 -27
  58. package/src/components/bs5/card/card--single-action.stories.js +4 -33
  59. package/src/components/bs5/card/card.data.json +4 -1
  60. package/src/components/bs5/card/card.hbs +2 -2
  61. package/src/components/bs5/dateinput/Dateinput.js +26 -11
  62. package/src/components/bs5/dateinput/dateinput.data.json +14 -12
  63. package/src/components/bs5/dateinput/dateinput.hbs +27 -27
  64. package/src/components/bs5/footer/footer_formio.scss +5 -5
  65. package/src/components/bs5/formcheck/Formcheck.js +57 -6
  66. package/src/components/bs5/formcheck/_form-variables.scss +131 -0
  67. package/src/components/bs5/formcheck/formcheck.hbs +10 -2
  68. package/src/components/bs5/formcheck/formcheck.scss +229 -66
  69. package/src/components/bs5/formcheck/stories/bootstrap-validation/bootstrap-validation.stories.js +304 -0
  70. package/src/components/bs5/formcheck/stories/checkbox/checkbox.data.json +4 -5
  71. package/src/components/bs5/formcheck/stories/checkbox/checkbox.stories.js +19 -111
  72. package/src/components/bs5/formcheck/stories/radio/radio.data.json +4 -4
  73. package/src/components/bs5/formcheck/stories/radio/radio.stories.js +30 -122
  74. package/src/components/bs5/inpageAlert/inpageAlert.scss +1 -1
  75. package/src/components/bs5/modal/modal.scss +106 -99
  76. package/src/components/bs5/navbar/navbar.functions.js +122 -19
  77. package/src/components/bs5/pageLayout/{ThemeShowcase.stories.js → PaletteShowcase.stories.js} +36 -35
  78. package/src/components/bs5/searchInput/__snapshots__/searchInput.test.js.snap +24 -28
  79. package/src/components/bs5/searchInput/search.functions.js +93 -76
  80. package/src/components/bs5/searchInput/searchInput.data.json +19 -10
  81. package/src/components/bs5/searchInput/searchInput.hbs +31 -29
  82. package/src/components/bs5/searchInput/searchInput.scss +140 -196
  83. package/src/components/bs5/searchInput/searchInput.stories.js +35 -13
  84. package/src/components/bs5/searchInput/searchInput.test.js +5 -1
  85. package/src/components/bs5/select/Select.js +13 -5
  86. package/src/components/bs5/select/Select.stories.js +27 -83
  87. package/src/components/bs5/select/select.data.json +12 -10
  88. package/src/components/bs5/select/select.hbs +19 -19
  89. package/src/components/bs5/tag/tag--status.stories.js +1 -0
  90. package/src/components/bs5/tag/tag.data.json +149 -143
  91. package/src/components/bs5/tag/tag.hbs +1 -1
  92. package/src/components/bs5/tag/tag.scss +2 -5
  93. package/src/components/bs5/tag/tag.stories.js +1 -0
  94. package/src/components/bs5/textarea/Textarea.js +13 -5
  95. package/src/components/bs5/textarea/Textarea.stories.js +29 -55
  96. package/src/components/bs5/textarea/textarea.data.json +14 -11
  97. package/src/components/bs5/textarea/textarea.hbs +17 -17
  98. package/src/components/bs5/textbox/Textbox.js +16 -5
  99. package/src/components/bs5/textbox/Textbox.stories.js +26 -51
  100. package/src/components/bs5/textbox/textInput.scss +12 -232
  101. package/src/components/bs5/textbox/textbox.data.json +13 -10
  102. package/src/components/bs5/textbox/textbox.hbs +17 -18
  103. package/src/components/bs5/typography/typography.stories.js +1 -1
  104. package/src/css/functions/_index.scss +2 -0
  105. package/src/css/functions/remify.scss +32 -0
  106. package/src/css/functions/snap-line-height.scss +7 -0
  107. package/src/css/main.scss +1 -1
  108. package/src/css/mixins/focusable.scss +4 -1
  109. package/src/css/mixins/make-icon.scss +1 -1
  110. package/src/css/{qld-theme.scss → qld-palettes.scss} +30 -23
  111. package/src/js/handlebars.helpers.js +9 -1
  112. package/src/js/utils.js +142 -0
  113. package/src/components/bs5/formcheck/_formcheck.stories.bak.js +0 -432
@@ -1,5 +1,5 @@
1
1
  // FormcheckRadio.stories.js
2
- import { Formcheck } from "../../Formcheck.js";
2
+ import { Formcheck, argTypes } from "../../Formcheck.js";
3
3
  import defaultdata from "./radio.data.json";
4
4
 
5
5
  export default {
@@ -11,169 +11,77 @@ export default {
11
11
  globals: { backgrounds: { value: "default" } },
12
12
  parameters: { backgrounds: { disable: false } },
13
13
  //https://storybook.js.org/docs/api/arg-types
14
- argTypes: {},
14
+ argTypes,
15
15
  };
16
16
 
17
- // Default story for Formcheck component
18
17
  export const Default = {
19
18
  args: { ...defaultdata },
20
- parameters: {
21
- controls: { include: `listitems` },
22
- },
23
19
  globals: { backgrounds: { value: "default" } },
24
20
  };
25
21
 
26
22
  export const RadioDark = {
27
23
  args: { ...defaultdata },
28
24
  globals: { backgrounds: { value: "dark" } },
29
- decorators: [
30
- (Story) => {
31
- return `
32
- <div class="dark">
33
- ${Story()}
34
- </div>
35
- `;
36
- },
37
- ],
25
+ render: (args) => `<div class="dark">${new Formcheck(args).html}</div>`,
38
26
  };
39
27
 
40
28
  export const RadioSmall = {
41
- args: { ...defaultdata, id: "radioSmall" },
42
- decorators: [
43
- (Story) => {
44
- return `
45
- <div class="small">
46
- ${Story()}
47
- </div>
48
- `;
49
- },
50
- ],
29
+ args: { ...defaultdata, id: "checkboxSmall" },
30
+ render: (args) => `<div class="small">${new Formcheck(args).html}</div>`,
51
31
  };
52
32
 
53
33
  export const RadioSmallDark = {
54
- args: { ...defaultdata, id: "radioSmallDark" },
34
+ args: { ...defaultdata, id: "checkboxSmallDark" },
55
35
  globals: { backgrounds: { value: "dark" } },
56
- decorators: [
57
- (Story) => {
58
- return `
59
- <div class="dark"><div class="small">
60
- ${Story()}
61
- </div></div>
62
- `;
63
- },
64
- ],
36
+ render: (args) => `<div class="dark small">${new Formcheck(args).html}</div>`,
65
37
  };
66
38
 
67
39
  export const RadioValid = {
68
- args: { ...defaultdata, id: "radioValid" },
69
- render: (args) => {
70
- return `
71
- <div class="valid">${new Formcheck(args).html}</div>
72
- `;
73
- },
40
+ args: { ...defaultdata, id: "checkboxValid" },
41
+ render: (args) => `${new Formcheck({ ...args, isValid: true }).html}`,
74
42
  };
75
43
 
76
44
  export const RadioValidSmall = {
77
- args: { ...defaultdata, id: "radioValidSmall" },
78
- render: (args) => {
79
- return `
80
- <div class="small"><div class="valid">${new Formcheck(args).html}</div></div>
81
- `;
82
- },
45
+ args: { ...defaultdata, id: "checkboxValidSmall" },
46
+ render: (args) =>
47
+ `<div class="small">${new Formcheck({ ...args, isValid: true }).html}</div>`,
83
48
  };
84
49
 
85
50
  export const RadioValidDark = {
86
- args: { ...defaultdata, id: "radioValidDark" },
87
- render: (args) => {
88
- return `
89
- <div class="dark">
90
- <div class="valid">${new Formcheck(args).html}</div></div>
91
- `;
92
- },
51
+ args: { ...defaultdata, id: "checkboxValidDark" },
52
+ render: (args) =>
53
+ `<div class="dark">${new Formcheck({ ...args, isValid: true }).html}</div>`,
93
54
  globals: { backgrounds: { value: "dark" } },
94
- decorators: [
95
- (Story) => {
96
- return `
97
- <div class="dark"><div class="valid">
98
- ${Story()}
99
- </div></div>
100
- `;
101
- },
102
- ],
103
55
  };
104
56
 
105
57
  export const RadioValidSmallDark = {
106
- args: { ...defaultdata, id: "radioValidSmallDark" },
107
- render: (args) => {
108
- return `
109
- <div class="dark">
110
- <div class="valid">${new Formcheck(args).html}</div></div>
111
- `;
112
- },
58
+ args: { ...defaultdata, id: "checkboxValidSmallDark" },
59
+ render: (args) =>
60
+ `<div class="dark small">${new Formcheck({ ...args, isValid: true }).html}</div>`,
113
61
  globals: { backgrounds: { value: "dark" } },
114
- decorators: [
115
- (Story) => {
116
- return `
117
- <div class="dark"><div class="small"><div class="valid">
118
- ${Story()}
119
- </div></div></div>
120
- `;
121
- },
122
- ],
123
62
  };
124
63
 
125
64
  export const RadioInvalid = {
126
- args: { ...defaultdata, id: "radioInvalid" },
127
- render: (args) => {
128
- return `
129
- <div class="invalid">${new Formcheck(args).html}</div>
130
- `;
131
- },
65
+ args: { ...defaultdata, id: "checkboxInvalid" },
66
+ render: (args) => `${new Formcheck({ ...args, isValid: false }).html}`,
132
67
  };
133
68
 
134
69
  export const RadioInvalidSmall = {
135
- args: { ...defaultdata, id: "radioInvalidSmall" },
136
- render: (args) => {
137
- return `
138
- <div class="small"><div class="invalid">${new Formcheck(args).html}</div></div>
139
- `;
140
- },
70
+ args: { ...defaultdata, id: "checkboxInvalidSmall" },
71
+ render: (args) =>
72
+ `<div class="small">${new Formcheck({ ...args, isValid: false }).html}</div>`,
141
73
  };
74
+
142
75
  export const RadioInvalidDark = {
143
- args: { ...defaultdata, id: "radioInvalidDark" },
144
- render: (args) => {
145
- return `
146
- <div class="invalid"><div class="dark">${new Formcheck(args).html}</div></div>
147
- `;
148
- },
76
+ args: { ...defaultdata, id: "checkboxInvalidDark" },
77
+ render: (args) =>
78
+ `<div class="dark">${new Formcheck({ ...args, isValid: false }).html}</div>`,
149
79
  globals: { backgrounds: { value: "dark" } },
150
- decorators: [
151
- (Story) => {
152
- return `
153
- <div class="dark">
154
- ${Story()}
155
- </div>
156
- `;
157
- },
158
- ],
159
80
  };
160
81
 
161
82
  export const RadioInvalidSmallDark = {
162
- args: { ...defaultdata, id: "radioInvalidSmallDark" },
163
- render: (args) => {
164
- return `
165
- <div class="dark">
166
- <div class="invalid">${new Formcheck(args).html}</div></div>
167
- `;
168
- },
83
+ args: { ...defaultdata, id: "checkboxInvalidSmallDark" },
84
+ render: (args) =>
85
+ `<div class="dark small">${new Formcheck({ ...args, isValid: false }).html}</div>`,
169
86
  globals: { backgrounds: { value: "dark" } },
170
- decorators: [
171
- (Story) => {
172
- return `
173
- <div class="dark"><div class="small"><div class="invalid">
174
- ${Story()}
175
- </div></div></div>
176
- `;
177
- },
178
- ],
179
87
  };
@@ -5,7 +5,7 @@
5
5
  //To account for this, we redefine the global vars at the .alert scope
6
6
 
7
7
  .alert {
8
- @include light-theme-properties();
8
+ @extend %qld-palette-default;
9
9
  --qld-headings-color: var(--#{$prefix}light-text-text);
10
10
  --qld-alert-color: var(--#{$prefix}light-text-text);
11
11
  --qld-alert-bg: #fff;
@@ -4,117 +4,124 @@
4
4
 
5
5
  // QGDS specific modal variables.
6
6
  .modal {
7
- --#{$prefix}modal-zindex: #{$zindex-modal};
8
- --#{$prefix}modal-width: #{$modal-md};
9
-
10
- --#{$prefix}modal-margin: #{$modal-dialog-margin};
11
- --#{$prefix}modal-color: #{$modal-content-color};
12
- --#{$prefix}modal-bg: #{$modal-content-bg};
13
- --#{$prefix}modal-padding: 1.5rem 2rem;
14
-
15
- --#{$prefix}modal-border-color: var(--#{$prefix}light-grey);
16
- --#{$prefix}modal-border-width: 1px;
17
- --#{$prefix}modal-border-radius: 0.75rem;
18
-
19
- --#{$prefix}modal-box-shadow: #{$modal-content-box-shadow-xs};
20
- --#{$prefix}modal-inner-border-radius: #{$modal-content-inner-border-radius};
21
-
22
- --#{$prefix}modal-header-padding: 2rem 2rem 0;
23
- --#{$prefix}modal-header-border-width: 0px;
24
-
25
- --#{$prefix}modal-title-line-height: 2rem;
26
-
27
- --#{$prefix}modal-footer-gap: 0;
28
- --#{$prefix}modal-footer-bg: none;
29
- --#{$prefix}modal-footer-border-color: none;
30
- --#{$prefix}modal-footer-border-width: none;
31
-
32
- .btn-close {
33
- --#{$prefix}btn-close-icon: "data:image/svg+xml,%3Csvg width='13' height='13' viewBox='0 0 13 13' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.0547 18.5938C17.8203 18.8281 17.3906 18.8281 17.1562 18.5938L12 13.3984L6.80469 18.5938C6.57031 18.8281 6.14062 18.8281 5.90625 18.5938C5.67188 18.3594 5.67188 17.9297 5.90625 17.6953L11.1016 12.5L5.90625 7.34375C5.67188 7.10938 5.67188 6.67969 5.90625 6.44531C6.14062 6.21094 6.57031 6.21094 6.80469 6.44531L12 11.6406L17.1562 6.44531C17.3906 6.21094 17.8203 6.21094 18.0547 6.44531C18.2891 6.67969 18.2891 7.10938 18.0547 7.34375L12.8594 12.5L18.0547 17.6953C18.2891 17.9297 18.2891 18.3594 18.0547 18.5938Z' /%3E%3C/svg%3E%0A";
34
- --#{$prefix}btn-close-color: var(--#{$prefix}color-default-color-light-text-lighter);
35
- --#{$prefix}btn-close-width: 40px;
36
- --#{$prefix}btn-border-radius: 50%;
37
-
38
- //Hover state
39
- --#{$prefix}btn-close-hover-color: var(--#{$prefix}color-default-color-light-text-heading);
40
- --#{$prefix}btn-close-hover-bg: var(--#{$prefix}core-default-color-neutral-lightest);
41
- --#{$prefix}btn-close-hover-bg-size: 15px;
42
-
43
- //Focus state
44
- --#{$prefix}btn-close-focus-color: var(--#{$prefix}color-default-color-light-text-heading);
45
- --#{$prefix}btn-close-focus-shadow: none;
46
- --#{$prefix}btn-close-focus-border: var(--#{$prefix}light-blue) solid 3px;
47
- }
7
+ --#{$prefix}modal-zindex: #{$zindex-modal};
8
+ --#{$prefix}modal-width: #{$modal-md};
9
+
10
+ --#{$prefix}modal-margin: #{$modal-dialog-margin};
11
+ --#{$prefix}modal-color: #{$modal-content-color};
12
+ --#{$prefix}modal-bg: #{$modal-content-bg};
13
+ --#{$prefix}modal-padding: 1.5rem 2rem;
14
+
15
+ --#{$prefix}modal-border-color: var(--#{$prefix}light-grey);
16
+ --#{$prefix}modal-border-width: 1px;
17
+ --#{$prefix}modal-border-radius: 0.75rem;
18
+
19
+ --#{$prefix}modal-box-shadow: #{$modal-content-box-shadow-xs};
20
+ --#{$prefix}modal-inner-border-radius: #{$modal-content-inner-border-radius};
21
+
22
+ --#{$prefix}modal-header-padding: 2rem 2rem 0;
23
+ --#{$prefix}modal-header-border-width: 0px;
24
+
25
+ --#{$prefix}modal-title-line-height: 2rem;
26
+
27
+ --#{$prefix}modal-footer-gap: 0;
28
+ --#{$prefix}modal-footer-bg: none;
29
+ --#{$prefix}modal-footer-border-color: none;
30
+ --#{$prefix}modal-footer-border-width: none;
31
+
32
+ .btn-close {
33
+ --#{$prefix}btn-close-icon: "data:image/svg+xml,%3Csvg width='13' height='13' viewBox='0 0 13 13' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.0547 18.5938C17.8203 18.8281 17.3906 18.8281 17.1562 18.5938L12 13.3984L6.80469 18.5938C6.57031 18.8281 6.14062 18.8281 5.90625 18.5938C5.67188 18.3594 5.67188 17.9297 5.90625 17.6953L11.1016 12.5L5.90625 7.34375C5.67188 7.10938 5.67188 6.67969 5.90625 6.44531C6.14062 6.21094 6.57031 6.21094 6.80469 6.44531L12 11.6406L17.1562 6.44531C17.3906 6.21094 17.8203 6.21094 18.0547 6.44531C18.2891 6.67969 18.2891 7.10938 18.0547 7.34375L12.8594 12.5L18.0547 17.6953C18.2891 17.9297 18.2891 18.3594 18.0547 18.5938Z' /%3E%3C/svg%3E%0A";
34
+ --#{$prefix}btn-close-color: var(
35
+ --#{$prefix}color-default-color-light-text-lighter
36
+ );
37
+ --#{$prefix}btn-close-width: 40px;
38
+ --#{$prefix}btn-border-radius: 50%;
39
+
40
+ //Hover state
41
+ --#{$prefix}btn-close-hover-color: var(
42
+ --#{$prefix}color-default-color-light-text-heading
43
+ );
44
+ --#{$prefix}btn-close-hover-bg: var(
45
+ --#{$prefix}core-default-color-neutral-lightest
46
+ );
47
+ --#{$prefix}btn-close-hover-bg-size: 15px;
48
+
49
+ //Focus state
50
+ --#{$prefix}btn-close-focus-color: var(
51
+ --#{$prefix}color-default-color-light-text-heading
52
+ );
53
+ --#{$prefix}btn-close-focus-shadow: none;
54
+ --#{$prefix}btn-close-focus-border: var(--#{$prefix}focus-color) solid 3px;
55
+ }
48
56
  }
49
57
 
50
58
  // General styling rules.
51
59
  .modal {
52
- .btn-close {
53
- width: var(--#{$prefix}btn-close-width);
54
- height: var(--#{$prefix}btn-close-width);
55
- border-radius: var(--#{$prefix}btn-border-radius);
56
- background-size: 12px;
57
- padding: 0;
58
- flex-shrink: 0; //Flex, do not shrink this perfectly round button.
59
-
60
- &:hover {
61
- width: var(--#{$prefix}btn-close-width);
62
- height: var(--#{$prefix}btn-close-width);
63
- color: var(--#{$prefix}btn-close-hover-color);
64
- background-size: var(--#{$prefix}btn-close-hover-bg-size);
65
- background-color: var(--#{$prefix}btn-close-hover-bg);
66
- }
67
- &:focus {
68
- width: var(--#{$prefix}btn-close-width);
69
- height: var(--#{$prefix}btn-close-width);
70
- color: var(--#{$prefix}btn-close-focus-color);
71
- background-size: var(--#{$prefix}btn-close-hover-bg-size);
72
- background-color: var(--#{$prefix}btn-close-hover-bg);
73
- outline: var(--#{$prefix}btn-close-focus-border);
74
- outline-offset: 2px;
75
- }
76
- }
60
+ .btn-close {
61
+ width: var(--#{$prefix}btn-close-width);
62
+ height: var(--#{$prefix}btn-close-width);
63
+ border-radius: var(--#{$prefix}btn-border-radius);
64
+ background-size: 12px;
65
+ padding: 0;
66
+ flex-shrink: 0; //Flex, do not shrink this perfectly round button.
77
67
 
78
- .modal-header {
79
- .modal-title {
80
- line-height: var(--#{$prefix}modal-title-line-height);
81
- font-size: 1.5rem;
82
- }
68
+ &:hover {
69
+ width: var(--#{$prefix}btn-close-width);
70
+ height: var(--#{$prefix}btn-close-width);
71
+ color: var(--#{$prefix}btn-close-hover-color);
72
+ background-size: var(--#{$prefix}btn-close-hover-bg-size);
73
+ background-color: var(--#{$prefix}btn-close-hover-bg);
74
+ }
75
+ &:focus {
76
+ width: var(--#{$prefix}btn-close-width);
77
+ height: var(--#{$prefix}btn-close-width);
78
+ color: var(--#{$prefix}btn-close-focus-color);
79
+ background-size: var(--#{$prefix}btn-close-hover-bg-size);
80
+ background-color: var(--#{$prefix}btn-close-hover-bg);
81
+ outline: var(--#{$prefix}btn-close-focus-border);
82
+ outline-offset: 2px;
83
83
  }
84
+ }
84
85
 
85
- .modal-footer {
86
- padding: 0 2rem 2rem;
87
- flex-direction: row;
88
- flex-wrap: nowrap;
89
- justify-content: flex-end;
90
-
91
- .btn {
92
- margin: 0 0 0 1.5rem;
93
- }
86
+ .modal-header {
87
+ .modal-title {
88
+ line-height: var(--#{$prefix}modal-title-line-height);
89
+ font-size: 1.5rem;
94
90
  }
91
+ }
95
92
 
96
- //Rescope for smaller modals
97
- .modal-dialog {
98
- &.modal-sm {
99
- .modal-footer {
100
- flex-wrap: wrap;
101
- .btn {
102
- width: 100%;
103
- margin: 1rem 0;
104
- }
105
- }
106
- }
93
+ .modal-footer {
94
+ padding: 0 2rem 2rem;
95
+ flex-direction: row;
96
+ flex-wrap: nowrap;
97
+ justify-content: flex-end;
98
+
99
+ .btn {
100
+ margin: 0 0 0 1.5rem;
107
101
  }
102
+ }
108
103
 
109
- //Rescope for smaller viewports
110
- @include media-breakpoint-down(md) {
111
- .modal-footer {
112
- flex-wrap: wrap;
113
- .btn {
114
- width: 100%;
115
- margin: 1rem 0;
116
- }
104
+ //Rescope for smaller modals
105
+ .modal-dialog {
106
+ &.modal-sm {
107
+ .modal-footer {
108
+ flex-wrap: wrap;
109
+ .btn {
110
+ width: 100%;
111
+ margin: 1rem 0;
117
112
  }
113
+ }
118
114
  }
115
+ }
119
116
 
117
+ //Rescope for smaller viewports
118
+ @include media-breakpoint-down(md) {
119
+ .modal-footer {
120
+ flex-wrap: wrap;
121
+ .btn {
122
+ width: 100%;
123
+ margin: 1rem 0;
124
+ }
125
+ }
126
+ }
120
127
  }
@@ -1,3 +1,5 @@
1
+ import { createFocusTrap } from "../../../js/utils.js";
2
+
1
3
  export function initializeNavbar() {
2
4
  const navbarCollapse = document.getElementById("main-nav");
3
5
  const overlay = document.getElementById("overlay");
@@ -8,13 +10,112 @@ export function initializeNavbar() {
8
10
  .map((id) => document.getElementById(id))
9
11
  .filter(Boolean);
10
12
 
11
- // Close navbar when overlay is clicked
12
- overlay?.addEventListener("click", () => {
13
+ // Helper to set aria-hidden
14
+ const setAriaHidden = (hidden) => {
15
+ hideTargets.forEach((el) => {
16
+ if (hidden) {
17
+ el.setAttribute("aria-hidden", "true");
18
+ } else {
19
+ el.removeAttribute("aria-hidden");
20
+ }
21
+ });
22
+ };
23
+
24
+ // Focus trap instances (created on-demand)
25
+ let mobileFocusTrap = null;
26
+ const dropdownFocusTraps = new Map();
27
+
28
+ // Helper function to close navbar
29
+ function closeNavbar() {
13
30
  if (navbarCollapse?.classList.contains("show")) {
14
31
  navbarCollapse.classList.remove("show");
15
- overlay.classList.remove("show");
32
+ overlay?.classList.remove("show");
16
33
  document.body.style.overflow = "";
34
+ setAriaHidden(false);
35
+
36
+ // Deactivate and destroy mobile focus trap
37
+ if (mobileFocusTrap) {
38
+ mobileFocusTrap.deactivate();
39
+ mobileFocusTrap = null;
40
+ }
17
41
  }
42
+ }
43
+
44
+ // Create mobile focus trap on-demand (when mobile menu opens)
45
+ function createMobileFocusTrap() {
46
+ if (!mobileFocusTrap && navbarCollapse) {
47
+ mobileFocusTrap = createFocusTrap(navbarCollapse, {
48
+ returnFocusElement: burgerBtn,
49
+ onEscape: () => {
50
+ closeNavbar();
51
+ },
52
+ });
53
+ }
54
+ return mobileFocusTrap;
55
+ }
56
+
57
+ // Create dropdown focus trap on-demand (when dropdown opens)
58
+ function getOrCreateDropdownFocusTrap(dropdown, toggle) {
59
+ if (!dropdownFocusTraps.has(dropdown)) {
60
+ const dropdownTrap = createFocusTrap(dropdown, {
61
+ returnFocusElement: toggle,
62
+ onEscape: () => {
63
+ // Close the dropdown using Bootstrap's API
64
+ const bsDropdown = bootstrap.Dropdown.getInstance(toggle);
65
+ if (bsDropdown) {
66
+ bsDropdown.hide();
67
+ }
68
+ },
69
+ });
70
+ dropdownFocusTraps.set(dropdown, dropdownTrap);
71
+ }
72
+ return dropdownFocusTraps.get(dropdown);
73
+ }
74
+
75
+ // Setup dropdown event listeners
76
+ function setupDropdownListeners() {
77
+ // Find all dropdown toggles (elements with data-bs-toggle="dropdown")
78
+ const dropdownToggles = navbarCollapse?.querySelectorAll(
79
+ '[data-bs-toggle="dropdown"]',
80
+ );
81
+
82
+ dropdownToggles?.forEach((toggle) => {
83
+ // Find the associated dropdown menu within the same parent container
84
+ const parentItem =
85
+ toggle.closest(".dropdown") || toggle.closest(".nav-item");
86
+ if (!parentItem) return;
87
+
88
+ const dropdown = parentItem.querySelector(".dropdown-menu");
89
+ if (!dropdown) return;
90
+
91
+ // Listen for dropdown show event (desktop only)
92
+ toggle.addEventListener("shown.bs.dropdown", () => {
93
+ const isMobile = window.innerWidth < 992;
94
+ if (!isMobile) {
95
+ // Create and activate focus trap on-demand
96
+ const dropdownTrap = getOrCreateDropdownFocusTrap(dropdown, toggle);
97
+ setTimeout(() => dropdownTrap.activate(), 0);
98
+ }
99
+ });
100
+
101
+ // Listen for dropdown hide event
102
+ toggle.addEventListener("hidden.bs.dropdown", () => {
103
+ const dropdownTrap = dropdownFocusTraps.get(dropdown);
104
+ if (dropdownTrap && dropdownTrap.isActive()) {
105
+ dropdownTrap.deactivate();
106
+ }
107
+ });
108
+ });
109
+ }
110
+
111
+ // Setup dropdown listeners on load
112
+ if (navbarCollapse) {
113
+ setupDropdownListeners();
114
+ }
115
+
116
+ // Close navbar when overlay is clicked
117
+ overlay?.addEventListener("click", () => {
118
+ closeNavbar();
18
119
  });
19
120
 
20
121
  const resetNavbarState = () => {
@@ -42,23 +143,25 @@ export function initializeNavbar() {
42
143
  window.addEventListener("resize", resetNavbarState);
43
144
  resetNavbarState();
44
145
 
45
- // Helper to set aria-hidden
46
- const setAriaHidden = (hidden) => {
47
- hideTargets.forEach((el) => {
48
- if (hidden) {
49
- el.setAttribute("aria-hidden", "true");
50
- } else {
51
- el.removeAttribute("aria-hidden");
146
+ // Burger buttons - handle open (mobile only)
147
+ navbarCollapse?.addEventListener("shown.bs.collapse", () => {
148
+ // Check if navbar is opening
149
+ setTimeout(() => {
150
+ if (navbarCollapse?.classList.contains("show")) {
151
+ setAriaHidden(true);
152
+
153
+ // Create and activate focus trap when navbar opens (mobile only - whole navbar)
154
+ const isMobile = window.innerWidth < 992;
155
+ if (isMobile) {
156
+ const trap = createMobileFocusTrap();
157
+ trap.activate();
158
+ }
52
159
  }
53
- });
54
- };
160
+ }, 0);
161
+ });
55
162
 
56
- // Burger buttons
57
- [burgerBtn, burgerCloseBtn].forEach((btn) => {
58
- if (btn) {
59
- btn.addEventListener("click", () => {
60
- setAriaHidden(true);
61
- });
62
- }
163
+ // Close button
164
+ burgerCloseBtn?.addEventListener("click", () => {
165
+ closeNavbar();
63
166
  });
64
167
  }