@qld-gov-au/qgds-bootstrap5 2.0.9 → 2.0.11

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 (54) hide show
  1. package/.storybook/main.mjs +2 -2
  2. package/dist/assets/components/bs5/banner/banner.hbs +3 -6
  3. package/dist/assets/components/bs5/card/card.hbs +2 -2
  4. package/dist/assets/components/bs5/head/head.hbs +1 -1
  5. package/dist/assets/components/bs5/tag/tag.hbs +1 -1
  6. package/dist/assets/css/qld.bootstrap.css +2 -1
  7. package/dist/assets/css/qld.bootstrap.css.map +2 -2
  8. package/dist/assets/css/qld.bootstrap.legacy.css +2 -1
  9. package/dist/assets/css/qld.bootstrap.legacy.css.map +2 -2
  10. package/dist/assets/js/handlebars.helpers.bundle.js +2 -1
  11. package/dist/assets/js/handlebars.helpers.bundle.js.map +2 -2
  12. package/dist/assets/js/handlebars.init.min.js +9 -11
  13. package/dist/assets/js/handlebars.init.min.js.map +2 -2
  14. package/dist/assets/js/handlebars.partials.js +9 -11
  15. package/dist/assets/js/handlebars.partials.js.map +2 -2
  16. package/dist/assets/js/qld.bootstrap.min.js +6 -5
  17. package/dist/assets/js/qld.bootstrap.min.js.map +4 -4
  18. package/dist/assets/node/handlebars.init.min.js +9 -5
  19. package/dist/assets/node/handlebars.init.min.js.map +2 -2
  20. package/dist/components/bs5/banner/banner.hbs +3 -6
  21. package/dist/components/bs5/card/card.hbs +2 -2
  22. package/dist/components/bs5/head/head.hbs +1 -1
  23. package/dist/components/bs5/tag/tag.hbs +1 -1
  24. package/dist/package.json +1 -1
  25. package/dist/sample-data/card/card.data.json +4 -1
  26. package/dist/sample-data/tag/tag.data.json +149 -143
  27. package/esbuild.js +8 -0
  28. package/package.json +1 -1
  29. package/src/components/bs5/banner/banner.hbs +3 -6
  30. package/src/components/bs5/banner/banner.scss +10 -7
  31. package/src/components/bs5/banner/banner.stories.js +2 -5
  32. package/src/components/bs5/button/button.scss +4 -11
  33. package/src/components/bs5/button/button.stories.js +17 -15
  34. package/src/components/bs5/card/Card.js +31 -2
  35. package/src/components/bs5/card/Card.mdx +4 -0
  36. package/src/components/bs5/card/card--icon-list-footer.stories.js +2 -24
  37. package/src/components/bs5/card/card--multi-action.stories.js +9 -28
  38. package/src/components/bs5/card/card--no-action.stories.js +5 -27
  39. package/src/components/bs5/card/card--single-action.stories.js +4 -33
  40. package/src/components/bs5/card/card.data.json +4 -1
  41. package/src/components/bs5/card/card.hbs +2 -2
  42. package/src/components/bs5/footer/footer_formio.scss +5 -5
  43. package/src/components/bs5/modal/modal.scss +106 -99
  44. package/src/components/bs5/navbar/navbar.functions.js +122 -19
  45. package/src/components/bs5/tag/tag--status.stories.js +1 -0
  46. package/src/components/bs5/tag/tag.data.json +149 -143
  47. package/src/components/bs5/tag/tag.hbs +1 -1
  48. package/src/components/bs5/tag/tag.scss +2 -5
  49. package/src/components/bs5/tag/tag.stories.js +1 -0
  50. package/src/components/bs5/typography/typography.stories.js +1 -1
  51. package/src/css/mixins/focusable.scss +1 -1
  52. package/src/css/mixins/make-icon.scss +1 -1
  53. package/src/js/handlebars.helpers.js +9 -1
  54. package/src/js/utils.js +142 -0
@@ -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
  }
@@ -42,6 +42,7 @@ function statusVariantsMarkup() {
42
42
  //Generate Tag component markup from all possible tag types.
43
43
  const tagHtml = new Tag({
44
44
  variant: defaultdata.status.variant,
45
+ classes: "p-4 my-2",
45
46
  tagItems: tagItems,
46
47
  size: sizeClass,
47
48
  emphasis: emClass,
@@ -1,145 +1,151 @@
1
1
  {
2
- "default": {
3
- "variant": "tag-default",
4
- "tagItems": [
5
- {
6
- "id": "One",
7
- "content": "Tag text"
8
- },
9
- {
10
- "id": "Two",
11
- "content": "Tag text"
12
- },
13
- {
14
- "id": "Three",
15
- "content": "Tag text"
16
- },
17
- {
18
- "id": "Four",
19
- "content": "Tag text"
20
- }
21
- ]
22
- },
23
- "large": {
24
- "variant": "",
25
- "tagItems": [
26
- {
27
- "id": "One",
28
- "content": "Tag text",
29
- "classes": "tag-large"
30
- },
31
- {
32
- "id": "Two",
33
- "content": "Tag text",
34
- "classes": "tag-large"
35
- },
36
- {
37
- "id": "Three",
38
- "content": "Tag text",
39
- "classes": "tag-large"
40
- }
41
- ]
42
- },
43
- "action": {
44
- "variant": "",
45
- "tagItems": [
46
- {
47
- "id": "One",
48
- "content": "<a href='javascript:void(0)'>Tag text</a>",
49
- "classes": "tag-link"
50
- },
51
- {
52
- "id": "Two",
53
- "content": "<a href='javascript:void(0)'>Tag text</a>",
54
- "classes": "tag-link"
55
- },
56
- {
57
- "id": "Three",
58
- "content": "<a href='javascript:void(0)'>Tag text</a>",
59
- "classes": "tag-link"
60
- }
61
- ]
62
- },
63
- "dark": {
64
- "classes": "",
65
- "variant": "tag-dark",
66
- "tagItems": [
67
- {
68
- "id": "One",
69
- "content": "Tag text"
70
- },
71
- {
72
- "id": "Two",
73
- "content": "Tag text"
74
- },
75
- {
76
- "id": "Three",
77
- "content": "Tag text"
78
- }
79
- ]
80
- },
81
- "info": {
82
- "variant": "",
83
- "tagItems": [
84
- {
85
- "id": "One",
86
- "content": "Tag text",
87
- "classes": "tag-info"
88
- },
89
- {
90
- "id": "Two",
91
- "content": "Tag text",
92
- "classes": "tag-info"
93
- },
94
- {
95
- "id": "Three",
96
- "content": "Tag text",
97
- "classes": "tag-info"
98
- }
99
- ]
100
- },
101
- "filter": {
102
- "variant": "",
103
- "tagItems": [
104
- {
105
- "id": "One",
106
- "content": "Policies and procedures",
107
- "classes": "tag-large",
108
- "applied-filter": true
109
- },
110
- {
111
- "id": "Two",
112
- "content": "Publications",
113
- "classes": "tag-large",
114
- "applied-filter": true
115
- }
116
- ]
117
- },
118
- "status": {
119
- "variant": "tag-status",
120
- "tagItems": [
121
- {
122
- "content": "Neutral",
123
- "classes": "tag-neutral"
124
- },
125
- {
126
- "content": "Success",
127
- "classes": "tag-success"
128
- },
129
- {
130
- "content": "Warning",
131
- "classes": "tag-warning"
132
- },
133
- {
134
- "content": "Error",
135
- "classes": "tag-error"
136
- },
137
- {
138
- "content": "Information",
139
- "classes": "tag-information"
140
- }
141
- ],
142
- "size": "tag-small",
143
- "emphasis": "tag-low"
144
- }
2
+ "default": {
3
+ "variant": "tag-default",
4
+ "classes": "p-4 my-2",
5
+ "tagItems": [
6
+ {
7
+ "id": "One",
8
+ "content": "Tag text"
9
+ },
10
+ {
11
+ "id": "Two",
12
+ "content": "Tag text"
13
+ },
14
+ {
15
+ "id": "Three",
16
+ "content": "Tag text"
17
+ },
18
+ {
19
+ "id": "Four",
20
+ "content": "Tag text"
21
+ }
22
+ ]
23
+ },
24
+ "large": {
25
+ "variant": "",
26
+ "classes": "p-4 my-2",
27
+ "tagItems": [
28
+ {
29
+ "id": "One",
30
+ "content": "Tag text",
31
+ "classes": "tag-large"
32
+ },
33
+ {
34
+ "id": "Two",
35
+ "content": "Tag text",
36
+ "classes": "tag-large"
37
+ },
38
+ {
39
+ "id": "Three",
40
+ "content": "Tag text",
41
+ "classes": "tag-large"
42
+ }
43
+ ]
44
+ },
45
+ "action": {
46
+ "variant": "",
47
+ "classes": "p-4 my-2",
48
+ "tagItems": [
49
+ {
50
+ "id": "One",
51
+ "content": "<a href='javascript:void(0)'>Tag text</a>",
52
+ "classes": "tag-link"
53
+ },
54
+ {
55
+ "id": "Two",
56
+ "content": "<a href='javascript:void(0)'>Tag text</a>",
57
+ "classes": "tag-link"
58
+ },
59
+ {
60
+ "id": "Three",
61
+ "content": "<a href='javascript:void(0)'>Tag text</a>",
62
+ "classes": "tag-link"
63
+ }
64
+ ]
65
+ },
66
+ "dark": {
67
+ "classes": "p-4 my-2",
68
+ "variant": "tag-dark",
69
+ "tagItems": [
70
+ {
71
+ "id": "One",
72
+ "content": "Tag text"
73
+ },
74
+ {
75
+ "id": "Two",
76
+ "content": "Tag text"
77
+ },
78
+ {
79
+ "id": "Three",
80
+ "content": "Tag text"
81
+ }
82
+ ]
83
+ },
84
+ "info": {
85
+ "variant": "",
86
+ "classes": "p-4 my-2",
87
+ "tagItems": [
88
+ {
89
+ "id": "One",
90
+ "content": "Tag text",
91
+ "classes": "tag-info"
92
+ },
93
+ {
94
+ "id": "Two",
95
+ "content": "Tag text",
96
+ "classes": "tag-info"
97
+ },
98
+ {
99
+ "id": "Three",
100
+ "content": "Tag text",
101
+ "classes": "tag-info"
102
+ }
103
+ ]
104
+ },
105
+ "filter": {
106
+ "variant": "",
107
+ "classes": "p-4 my-2",
108
+ "tagItems": [
109
+ {
110
+ "id": "One",
111
+ "content": "Policies and procedures",
112
+ "classes": "tag-large",
113
+ "applied-filter": true
114
+ },
115
+ {
116
+ "id": "Two",
117
+ "content": "Publications",
118
+ "classes": "tag-large",
119
+ "applied-filter": true
120
+ }
121
+ ]
122
+ },
123
+ "status": {
124
+ "variant": "tag-status",
125
+ "classes": "p-4 my-2",
126
+ "tagItems": [
127
+ {
128
+ "content": "Neutral",
129
+ "classes": "tag-neutral"
130
+ },
131
+ {
132
+ "content": "Success",
133
+ "classes": "tag-success"
134
+ },
135
+ {
136
+ "content": "Warning",
137
+ "classes": "tag-warning"
138
+ },
139
+ {
140
+ "content": "Error",
141
+ "classes": "tag-error"
142
+ },
143
+ {
144
+ "content": "Information",
145
+ "classes": "tag-information"
146
+ }
147
+ ],
148
+ "size": "tag-small",
149
+ "emphasis": "tag-low"
150
+ }
145
151
  }
@@ -1,6 +1,6 @@
1
1
  <!-- QGDS Component: Tag -->
2
2
 
3
- <ul class="tag-list {{variant}}">
3
+ <ul class="tag-list {{variant}} {{classes}}">
4
4
  {{#each tagItems}}
5
5
  <li class="tag-item {{classes}} {{../size}} {{../emphasis}}">
6
6
  {{{content}}}
@@ -91,9 +91,8 @@ $_icon-status-cancel: url('data:image/svg+xml,<svg width="32" height="32" viewBo
91
91
  // General styling rules.
92
92
  .tag-list {
93
93
  list-style-type: none;
94
- margin: 0.5rem 0;
95
- padding: 30px;
96
94
  display: flex;
95
+ flex-wrap: wrap;
97
96
  align-items: center;
98
97
  gap: 1rem;
99
98
 
@@ -114,9 +113,7 @@ $_icon-status-cancel: url('data:image/svg+xml,<svg width="32" height="32" viewBo
114
113
  border: var(--_border-width) solid var(--#{$prefix}neutral-lighter);
115
114
  border-radius: 1rem;
116
115
  font-size: 0.875rem;
117
- line-height: 1.5;
118
-
119
- height: var(--_height);
116
+ line-height: 1.4;
120
117
 
121
118
  &.tag-large {
122
119
  --_padding-y: 0.5rem;
@@ -45,6 +45,7 @@ export const ParentContextComparison = {
45
45
  render: () => {
46
46
  const testTags = {
47
47
  variant: "",
48
+ classes: "p-4 my-2",
48
49
  tagItems: [
49
50
  {
50
51
  content: "default",
@@ -69,8 +69,8 @@ export default {
69
69
  },
70
70
  ],
71
71
  backgrounds: { disable: false },
72
- globals: { backgrounds: { value: "default" } },
73
72
  },
73
+ globals: { backgrounds: { value: "default" } },
74
74
  };
75
75
 
76
76
  /**
@@ -35,7 +35,7 @@
35
35
  style: solid;
36
36
  color: var(
37
37
  --qld-focus-color,
38
- #0085b3
38
+ #002e85
39
39
  ); // --qld-focus-color defined in qld-type.scss
40
40
  offset: $offsetOutline;
41
41
  }
@@ -11,7 +11,7 @@ $prefix: "qld-" !default;
11
11
  /// @param {String} $name [null] - The name of icon
12
12
  /// @param {String} $size ["sm"] - The icon size, valid values are "xs" | "sm" | "md" | "lg" | "xl" | "xxl"
13
13
  /// @param {String | null} $pseudo [null] - Whether the mixin applies styles as a pseudo-element. Valid string values are "before" | "after"
14
- /// @param {false} $is-inline - Applies extra styles to assist with vertical alignent when used inline with text.
14
+ /// @param {Boolean} $is-inline [false] - Applies extra styles to assist with vertical alignent when used inline with text.
15
15
  /// @param {Boolean} $include-base [true] - Apply all base styles. Set to false when reusing this mixin to create modifier classes where base styles are already applied (For instance qld-icon-{name})
16
16
  /// @content
17
17
  @mixin make-icon(
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  *
5
5
  * @param {*} v1 The left value
6
- * @param { "==" | "===" | "!=" | "!==" | "<" | "<=" | ">" | ">=" | "&&" | "||" | "contains"} operator the operator to handle comparison
6
+ * @param { "==" | "===" | "!=" | "!==" | "<" | "<=" | ">" | ">=" | "&&" | "||" | "in" | "contains"} operator the operator to handle comparison
7
7
  * @param {*} v2 The right value
8
8
  * @returns {Boolean} the result of comparison
9
9
  * @example
@@ -92,6 +92,14 @@ export default function handlebarsHelpers(handlebars) {
92
92
  return args.some((arg) => !!arg) ? options.fn(this) : options.inverse(this);
93
93
  });
94
94
 
95
+ // ifAll - {{{#ifAll variable1 variable2 variable3 variable4 etc}}, if all true return true
96
+ handlebars.registerHelper("ifAll", function (...args) {
97
+ const options = args.pop(); // The last argument is the options object
98
+ return args.every((arg) => !!arg)
99
+ ? options.fn(this)
100
+ : options.inverse(this);
101
+ });
102
+
95
103
  // now - return current timestamp i.e {{now}}
96
104
  handlebars.registerHelper("now", function () {
97
105
  return new Date().toISOString();