@qhealth-design-system/core 1.22.2 → 1.23.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.
Files changed (78) hide show
  1. package/.storybook/assets/image-position-demo-portrait.svg +54 -0
  2. package/.storybook/assets/image-position-demo.svg +55 -0
  3. package/.storybook/globals.js +9 -0
  4. package/.storybook/preview.js +8 -1
  5. package/CHANGELOG.md +2 -0
  6. package/README.md +0 -6
  7. package/package.json +13 -33
  8. package/src/assets/img/svg-icons.svg +154 -154
  9. package/src/component-loader.js +11 -2
  10. package/src/components/_global/css/admin/component.scss +1 -0
  11. package/src/components/_global/css/btn/component.scss +9 -0
  12. package/src/components/_global/css/img/image-position-utility-classes.scss +30 -0
  13. package/src/components/_global/css/img/image-ratio-utility-classes.scss +41 -0
  14. package/src/components/_global/css/img/images.scss +74 -5
  15. package/src/components/_global/css/modal/component.scss +1 -0
  16. package/src/components/_global/js/animate/global.js +272 -294
  17. package/src/components/_global/js/collapsible.js +3 -3
  18. package/src/components/_global/js/global.js +304 -316
  19. package/src/components/_global/js/legacyGlobal.js +63 -0
  20. package/src/components/_global/js/modal/global.js +218 -223
  21. package/src/components/_global/js/popover/controller.js +4 -4
  22. package/src/components/_global/js/tabs/global.js +264 -276
  23. package/src/components/a-z_listing/js/global.js +106 -92
  24. package/src/components/accordion/css/component.scss +24 -41
  25. package/src/components/accordion/html/component.hbs +23 -5
  26. package/src/components/accordion/js/global.js +5 -10
  27. package/src/components/basic_search/js/global.js +0 -7
  28. package/src/components/code/js/global.js +6 -12
  29. package/src/components/file_upload/js/global.js +1 -5
  30. package/src/components/internal_navigation/css/component.scss +2 -2
  31. package/src/components/internal_navigation/html/component.hbs +16 -1
  32. package/src/components/main_navigation/js/global.js +341 -335
  33. package/src/components/overflow_menu/css/component.scss +1 -0
  34. package/src/components/search_box/css/component.scss +0 -1
  35. package/src/components/tab/js/global.js +5 -10
  36. package/src/components/toggle_tip/js/global.js +0 -6
  37. package/src/components/tool_tip/js/global.js +0 -6
  38. package/src/components/video_player/css/component.scss +27 -9
  39. package/src/components/video_player/html/component.hbs +32 -6
  40. package/src/components/video_player/js/global.js +8 -1
  41. package/src/index.js +4 -0
  42. package/src/stories/Accordion/accordion.stories.js +1 -0
  43. package/src/stories/Banner/bannerContained.stories.js +123 -0
  44. package/src/stories/Flexbox/Flexbox.stories.js +316 -0
  45. package/src/stories/Image/Image.stories.js +182 -0
  46. package/src/stories/Templates/ContentPage.stories.js +101 -0
  47. package/src/stories/Templates/LandingPage.stories.js +193 -0
  48. package/src/stories/Templates/templateHelpers.js +102 -0
  49. package/src/stories/VideoPlayer/VideoPlayer.stories.js +16 -0
  50. package/src/styles/imports/utilities.scss +15 -25
  51. package/src/styles/imports/variables.scss +26 -0
  52. package/src/components/_global/html/head.html +0 -22
  53. package/src/components/_global/html/scripts.html +0 -28
  54. package/src/components/_global/js/_polyfills/global.js +0 -19
  55. package/src/components/_template/html/component-page.html +0 -75
  56. package/src/components/_template/js/global.js +0 -3
  57. package/src/components/accordion/html/accordion-group.html +0 -163
  58. package/src/components/accordion/html/default.html +0 -99
  59. package/src/components/banner_basic/js/global.js +0 -3
  60. package/src/components/loading_spinner/js/global.js +0 -3
  61. package/src/components/pagination/html/default.html +0 -39
  62. package/src/components/search_box/html/component.html +0 -26
  63. package/src/data/current.json +0 -400
  64. package/src/data/current_admin.json +0 -305
  65. package/src/data/model_component.js +0 -14
  66. package/src/data/site.json +0 -977
  67. package/src/externals/tinymce_classes.css +0 -62
  68. package/src/html/component-global-elements.html +0 -2857
  69. package/src/html/component-horizontal_rule.html +0 -647
  70. package/src/html/component-internal_navigation.html +0 -583
  71. package/src/html/component-left_hand_navigation.html +0 -57
  72. package/src/html/component-main_navigation.html +0 -583
  73. package/src/html/component-multi_column.html +0 -582
  74. package/src/html/components.html +0 -354
  75. package/src/html/home.html +0 -1898
  76. package/src/html/index.html +0 -129
  77. package/src/html/inner-with-nav.html +0 -605
  78. package/src/html/inner.html +0 -618
@@ -0,0 +1,316 @@
1
+ // Flexbox utility-class reference and Chromatic snapshot target.
2
+ //
3
+ // Same approach as Colours.stories.js / Typography.stories.js: a set of
4
+ // collapsible <details> accordions rendered as plain, deterministic HTML. Each
5
+ // card is a *live* demo — the qld__ flex classes are the real ones compiled into
6
+ // Storybook (via src/components/_global/css/flex/flexbox-utility-classes.scss),
7
+ // so what you see is exactly what the class does — paired with the class name in
8
+ // mono and a short description.
9
+ //
10
+ // Story chrome (cards, grid, accordions) uses inline styles that mirror
11
+ // Storybook's light docs theme; the demo containers/items use the real utility
12
+ // classes plus a little inline styling for visibility (gap, background).
13
+
14
+ const TEXT = "#2e3438";
15
+ const MUTED = "rgba(46, 52, 56, 0.7)";
16
+ const BORDER = "rgba(38, 85, 115, 0.15)";
17
+ const SANS =
18
+ "'Nunito Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif";
19
+ const MONO = "ui-monospace, Menlo, Monaco, Consolas, 'Courier New', monospace";
20
+ const ITEM_BG = "#0a5a8f";
21
+ const LINK = "#005eb8";
22
+
23
+ const DS_URL = "https://www.designsystem.qld.gov.au/styles/flex-box-utilities";
24
+ const SOURCE = "src/components/_global/css/flex/flexbox-utility-classes.scss";
25
+
26
+ // One flex item (the coloured boxes inside a demo container). `cls` puts a real
27
+ // utility class on the item itself (used by the grow/shrink/align-self demos);
28
+ // `style` adds inline chrome such as extra height for align-items demos.
29
+ const item = (label, { cls = "", style = "" } = {}) =>
30
+ `<div class="${cls}" style="background:${ITEM_BG};color:#fff;padding:10px 14px;border-radius:4px;font-size:13px;font-weight:600;white-space:nowrap;${style}">${label}</div>`;
31
+
32
+ // A live demo container: `cls` is the real class string applied to it (e.g.
33
+ // "qld__display-flex qld__justify-content-center"); the dashed border and grey
34
+ // fill are chrome so the container's bounds and free space are visible.
35
+ const container = (cls, inner, style = "") =>
36
+ `<div class="${cls}" style="gap:8px;border:1px dashed ${BORDER};background:#f6f8fa;padding:12px;border-radius:6px;${style}">${inner}</div>`;
37
+
38
+ // One card: the live demo above, the class name (mono) and a description below.
39
+ const card = (codeLabel, desc, demoHtml) => `
40
+ <div style="border:1px solid ${BORDER};border-radius:6px;overflow:hidden;background:#fff;display:flex;flex-direction:column;">
41
+ <div style="padding:14px;border-bottom:1px solid ${BORDER};">${demoHtml}</div>
42
+ <div style="padding:10px 12px;">
43
+ <div style="font-family:${MONO};font-size:12px;color:${TEXT};font-weight:600;word-break:break-word;">${codeLabel}</div>
44
+ ${desc ? `<div style="font-size:12px;color:${MUTED};margin-top:4px;line-height:1.4;">${desc}</div>` : ""}
45
+ </div>
46
+ </div>`;
47
+
48
+ // A responsive grid of cards that wraps to fill the available width.
49
+ const grid = (cards) => `
50
+ <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;">
51
+ ${cards.join("")}
52
+ </div>`;
53
+
54
+ // Each group is a native <details> accordion, open by default so every group is
55
+ // expanded for Chromatic and a developer can collapse the ones they don't need.
56
+ const section = (text, count, cards) => `
57
+ <details open style="margin-top:24px;">
58
+ <summary style="cursor:pointer;font-size:20px;font-weight:700;color:${TEXT};padding:12px 0 8px;border-bottom:1px solid ${BORDER};">${text} <span style="font-weight:400;color:${MUTED};font-size:14px;">(${count})</span></summary>
59
+ <div style="padding-top:16px;">${grid(cards)}</div>
60
+ </details>`;
61
+
62
+ const threeItems = item("1") + item("2") + item("3");
63
+
64
+ // display
65
+ const displayCards = [
66
+ {
67
+ cls: "qld__display-flex",
68
+ desc: "Block-level flex container (fills its own line).",
69
+ },
70
+ {
71
+ cls: "qld__display-inline-flex",
72
+ desc: "Inline-level flex container (sits inline with surrounding text).",
73
+ },
74
+ ].map((d) => card(`.${d.cls}`, d.desc, container(d.cls, threeItems)));
75
+
76
+ // flex-direction (each demo is display-flex + the direction class)
77
+ const directionCards = [
78
+ ["qld__flex-row", "Main axis left → right (the default)."],
79
+ ["qld__flex-column", "Main axis top → bottom; items stack."],
80
+ ["qld__flex-row-reverse", "Main axis right → left."],
81
+ ["qld__flex-column-reverse", "Main axis bottom → top."],
82
+ ].map(([cls, desc]) =>
83
+ card(`.${cls}`, desc, container(`qld__display-flex ${cls}`, threeItems)),
84
+ );
85
+
86
+ // justify-content — spacing along the main axis
87
+ const justifyCards = [
88
+ ["qld__justify-content-start", "Pack items to the start."],
89
+ ["qld__justify-content-end", "Pack items to the end."],
90
+ ["qld__justify-content-center", "Centre items on the main axis."],
91
+ ["qld__justify-content-between", "First/last flush; equal space between."],
92
+ ["qld__justify-content-around", "Equal space around each item."],
93
+ ["qld__justify-content-evenly", "Equal space between and at the ends."],
94
+ ].map(([cls, desc]) =>
95
+ card(`.${cls}`, desc, container(`qld__display-flex ${cls}`, threeItems)),
96
+ );
97
+
98
+ // align-items — cross-axis alignment of items. Vary item heights + a tall
99
+ // container so the alignment is visible.
100
+ const alignItemsDemo = (cls) =>
101
+ container(
102
+ `qld__display-flex ${cls}`,
103
+ item("1", { style: "padding:6px 14px;" }) +
104
+ item("2", { style: "padding:24px 14px;" }) +
105
+ item("3", { style: "padding:14px 14px;" }),
106
+ "min-height:110px;",
107
+ );
108
+ const alignItemsCards = [
109
+ ["qld__align-items-start", "Align items to the cross-start edge."],
110
+ ["qld__align-items-end", "Align items to the cross-end edge."],
111
+ ["qld__align-items-center", "Centre items on the cross axis."],
112
+ ["qld__align-items-baseline", "Align item text baselines."],
113
+ ["qld__align-items-stretch", "Stretch items to fill the cross axis."],
114
+ ].map(([cls, desc]) =>
115
+ card(
116
+ `.${cls}`,
117
+ desc,
118
+ cls === "qld__align-items-stretch"
119
+ ? // stretch only shows when items have no fixed height
120
+ container(
121
+ `qld__display-flex ${cls}`,
122
+ item("1") + item("2") + item("3"),
123
+ "min-height:110px;",
124
+ )
125
+ : alignItemsDemo(cls),
126
+ ),
127
+ );
128
+
129
+ // flex-wrap — many items in a width-constrained container
130
+ const wrapDemo = (cls) =>
131
+ container(
132
+ `qld__display-flex ${cls}`,
133
+ [1, 2, 3, 4, 5, 6, 7, 8].map((n) => item(String(n))).join(""),
134
+ "max-width:260px;",
135
+ );
136
+ const wrapCards = [
137
+ ["qld__flex-wrap", "Items wrap onto multiple lines as needed."],
138
+ ["qld__flex-nowrap", "Items stay on one line (may overflow/shrink)."],
139
+ ["qld__flex-wrap-reverse", "Items wrap onto multiple lines, reversed."],
140
+ ].map(([cls, desc]) => card(`.${cls}`, desc, wrapDemo(cls)));
141
+
142
+ // align-content — spacing of wrapped lines on the cross axis. Needs wrap + a
143
+ // tall container + enough items to form multiple lines.
144
+ const alignContentDemo = (cls) =>
145
+ container(
146
+ `qld__display-flex qld__flex-wrap ${cls}`,
147
+ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((n) => item(String(n))).join(""),
148
+ "min-height:170px;max-width:260px;",
149
+ );
150
+ const alignContentCards = [
151
+ ["qld__align-content-start", "Pack lines to the cross-start."],
152
+ ["qld__align-content-end", "Pack lines to the cross-end."],
153
+ ["qld__align-content-center", "Centre the lines."],
154
+ ["qld__align-content-between", "First/last lines flush; space between."],
155
+ ["qld__align-content-around", "Equal space around each line."],
156
+ ["qld__align-content-stretch", "Stretch lines to fill the cross axis."],
157
+ ].map(([cls, desc]) => card(`.${cls}`, desc, alignContentDemo(cls)));
158
+
159
+ // flex grow / shrink / fill — classes applied to the items
160
+ const growShrinkCards = [
161
+ card(
162
+ ".qld__flex-fill",
163
+ "Item grows and shrinks to take the available space (flex: 1 1 auto).",
164
+ container(
165
+ "qld__display-flex",
166
+ item("auto") +
167
+ item("qld__flex-fill", { cls: "qld__flex-fill" }) +
168
+ item("auto"),
169
+ ),
170
+ ),
171
+ card(
172
+ ".qld__flex-grow-1",
173
+ "Item grows to absorb free space; siblings stay at their content size.",
174
+ container(
175
+ "qld__display-flex",
176
+ item("grow-1", { cls: "qld__flex-grow-1" }) + item("2") + item("3"),
177
+ ),
178
+ ),
179
+ card(
180
+ ".qld__flex-grow-0",
181
+ "Item does not grow (the default) — stays at its content size.",
182
+ container(
183
+ "qld__display-flex",
184
+ item("grow-0", { cls: "qld__flex-grow-0" }) +
185
+ item("grow-1", { cls: "qld__flex-grow-1" }),
186
+ ),
187
+ ),
188
+ card(
189
+ ".qld__flex-shrink-0",
190
+ "Item refuses to shrink below its content size when space is tight.",
191
+ container(
192
+ "qld__display-flex",
193
+ item("shrink-0 — a long, unshrinkable label", {
194
+ cls: "qld__flex-shrink-0",
195
+ }) + item("shrink-1", { cls: "qld__flex-shrink-1" }),
196
+ "max-width:260px;",
197
+ ),
198
+ ),
199
+ ];
200
+
201
+ // align-self — override the container's align-items for a single item. Container
202
+ // is align-items-start; the highlighted item overrides it.
203
+ const alignSelfDemo = (cls) =>
204
+ container(
205
+ "qld__display-flex qld__align-items-start",
206
+ item("1") + item(cls.replace("qld__align-self-", ""), { cls }) + item("3"),
207
+ "min-height:110px;",
208
+ );
209
+ const alignSelfCards = [
210
+ ["qld__align-self-start", "This item aligns to the cross-start."],
211
+ ["qld__align-self-end", "This item aligns to the cross-end."],
212
+ ["qld__align-self-center", "This item centres on the cross axis."],
213
+ ["qld__align-self-stretch", "This item stretches to fill the cross axis."],
214
+ ].map(([cls, desc]) => card(`.${cls}`, desc, alignSelfDemo(cls)));
215
+
216
+ // order — the two examples from the brief, rendered live.
217
+ const orderCards = [
218
+ card(
219
+ ".qld__order-1 … .qld__order-5",
220
+ "Numeric order (0–5) reorders items regardless of source order.",
221
+ container(
222
+ "qld__display-flex",
223
+ item("item 1", { cls: "qld__order-3" }) +
224
+ item("item 2", { cls: "qld__order-2" }) +
225
+ item("item 3", { cls: "qld__order-1" }) +
226
+ item("item 4", { cls: "qld__order-5" }) +
227
+ item("item 5", { cls: "qld__order-4" }),
228
+ ),
229
+ ),
230
+ card(
231
+ ".qld__order-first / .qld__order-last",
232
+ "order-first (order: -1) and order-last (order: 6) jump an item to the ends.",
233
+ container(
234
+ "qld__display-flex",
235
+ item("item 1", { cls: "qld__order-last" }) +
236
+ item("item 2") +
237
+ item("item 3", { cls: "qld__order-first" }),
238
+ ),
239
+ ),
240
+ ];
241
+
242
+ // Responsive-variant note. Every utility has breakpoint variants that apply from
243
+ // the named breakpoint up; widths mirror the QLD-media breakpoints in globals.js.
244
+ const responsiveNote = `
245
+ <div style="margin-top:28px;padding:16px;border:1px solid ${BORDER};border-radius:6px;background:#f6f8fa;">
246
+ <div style="font-weight:700;font-size:15px;color:${TEXT};margin-bottom:6px;">Responsive variants</div>
247
+ <p style="font-size:13px;color:${MUTED};margin:0 0 8px;line-height:1.5;">
248
+ Every class above has <code>-sm</code>, <code>-md</code>,
249
+ <code>-lg</code> and <code>-xl</code> variants that take effect from
250
+ that breakpoint up — e.g. <code>.qld__flex-md-column</code>,
251
+ <code>.qld__justify-content-lg-between</code>,
252
+ <code>.qld__order-sm-first</code>.
253
+ </p>
254
+ <p style="font-size:13px;color:${MUTED};margin:0;line-height:1.5;">
255
+ Breakpoints — <strong>sm</strong> 400px · <strong>md</strong> 699px · <strong>lg</strong> 992px · <strong>xl</strong> 1312px.
256
+ </p>
257
+ </div>`;
258
+
259
+ // Page shell shared by both stories: a title, intro blurb, the section
260
+ // accordions, then the responsive-variant note and source line.
261
+ const page = (title, blurb, sections) => `
262
+ <div style="font-family:${SANS};color:${TEXT};padding:1rem;">
263
+ <h1 style="font-size:28px;font-weight:700;margin:0 0 8px;">${title}</h1>
264
+ <p style="color:${MUTED};margin:0 0 8px;max-width:72ch;line-height:1.5;">${blurb}</p>
265
+ <p style="margin:0 0 8px;font-size:13px;color:${MUTED};">
266
+ <strong>Resources:</strong>
267
+ <a href="${DS_URL}" target="_blank" rel="noopener noreferrer" style="color:${LINK};">QLD Design System ↗</a>
268
+ </p>
269
+ <p style="margin:0 0 8px;font-size:13px;color:${MUTED};">
270
+ Source: <code>${SOURCE}</code>
271
+ </p>
272
+ ${sections}
273
+ ${responsiveNote}
274
+ </div>`;
275
+
276
+ // Parent utilities go on the flex container itself.
277
+ const renderParent = () =>
278
+ page(
279
+ "Flexbox utilities — flex container (parent)",
280
+ `Classes that go on the flex <strong>container</strong>. Start with
281
+ <code>.qld__display-flex</code> to make an element a flex parent, then
282
+ control the flow of its children with direction, wrap, and the justify/align utilities.
283
+ Every demo below is live — it uses the real <code>qld__</code> classes
284
+ compiled into the design system.`,
285
+ `${section("Display", displayCards.length, displayCards)}
286
+ ${section("Flex direction", directionCards.length, directionCards)}
287
+ ${section("Justify content — main axis", justifyCards.length, justifyCards)}
288
+ ${section("Align items — cross axis", alignItemsCards.length, alignItemsCards)}
289
+ ${section("Flex wrap", wrapCards.length, wrapCards)}
290
+ ${section("Align content — wrapped lines", alignContentCards.length, alignContentCards)}`,
291
+ );
292
+
293
+ // Child utilities go on the individual flex items.
294
+ const renderChildren = () =>
295
+ page(
296
+ "Flexbox utilities — flex items (children)",
297
+ `Classes that go on the individual flex <strong>items</strong> inside a
298
+ <code>.qld__display-flex</code> parent. Reorder items, control how they grow
299
+ and shrink to share space, and override the parent's cross-axis alignment for a single item.
300
+ Every demo below is live — it uses the real <code>qld__</code> classes
301
+ compiled into the design system.`,
302
+ `${section("Order", orderCards.length, orderCards)}
303
+ ${section("Grow, shrink & fill", growShrinkCards.length, growShrinkCards)}
304
+ ${section("Align self", alignSelfCards.length, alignSelfCards)}`,
305
+ );
306
+
307
+ export default {
308
+ title: "1. Core Styles/Flexbox utilities",
309
+ // This is a utility reference page, so opt out of the auto-generated docs page
310
+ // (autodocs is enabled globally in preview.js), matching Colours.stories.js.
311
+ tags: ["!autodocs"],
312
+ };
313
+
314
+ // Split by where the class is applied: container-level utilities vs item-level.
315
+ export const FlexContainerParent = { render: () => renderParent() };
316
+ export const FlexItemsChildren = { render: () => renderChildren() };
@@ -1,12 +1,30 @@
1
1
  import {
2
2
  dummyImageLink,
3
3
  dummyText,
4
+ imagePositionDemoImage,
5
+ imagePositionDemoImagePortrait,
4
6
  storyParams,
5
7
  } from "../../../.storybook/globals";
6
8
 
9
+ const imageRatios = ["1x1", "3x2", "2x3", "4x3", "3x4", "16x9"];
10
+
11
+ const imagePositions = [
12
+ "center",
13
+ "left",
14
+ "right",
15
+ "top",
16
+ "bottom",
17
+ "left-top",
18
+ "right-top",
19
+ "left-bottom",
20
+ "right-bottom",
21
+ ];
22
+
7
23
  function render(args) {
8
24
  const classes = [
9
25
  args.responsiveMediaImg ? "qld__responsive-media-img" : "",
26
+ args.imageRatio ? `qld__image-ratio-${args.imageRatio}` : "",
27
+ args.imagePosition ? `qld__image-position--${args.imagePosition}` : "",
10
28
  args.extraClasses,
11
29
  ]
12
30
  .filter(Boolean)
@@ -57,6 +75,18 @@ const meta = {
57
75
  description: "Applies responsive media image styling to the image.",
58
76
  control: { type: "boolean" },
59
77
  },
78
+ imageRatio: {
79
+ description:
80
+ "Crops the image to a fixed aspect ratio via the qld__image-ratio-* utility.",
81
+ control: { type: "select" },
82
+ options: ["", ...imageRatios],
83
+ },
84
+ imagePosition: {
85
+ description:
86
+ "Chooses which part of a cropped image stays in frame via the qld__image-position--* utility. Needs a crop to act on, such as an image ratio.",
87
+ control: { type: "select" },
88
+ options: ["", ...imagePositions],
89
+ },
60
90
  extraClasses: {
61
91
  description: "Any additional classes to apply to the image.",
62
92
  control: { type: "text" },
@@ -71,6 +101,8 @@ const meta = {
71
101
  "Supplied by Stephen Doggett, Department of Medical Entomology, NSW Health Pathology.",
72
102
  rightAligned: false,
73
103
  responsiveMediaImg: false,
104
+ imageRatio: "",
105
+ imagePosition: "",
74
106
  extraClasses: "",
75
107
  },
76
108
  decorators: [
@@ -93,6 +125,156 @@ export const WithoutCaption = {
93
125
  args: { caption: "" },
94
126
  };
95
127
 
128
+ export const FixedRatio = {
129
+ args: { imageRatio: "1x1" },
130
+ };
131
+
132
+ export const RightAlignedFixedRatio = {
133
+ args: { rightAligned: true, imageRatio: "16x9" },
134
+ };
135
+
136
+ /**
137
+ * Every `qld__image-ratio-*` utility applied directly to the `img`, sharing one
138
+ * source image so the difference is purely the crop.
139
+ */
140
+ export const AllRatios = {
141
+ name: "All image ratios",
142
+ render: (args) =>
143
+ `<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 24px;">
144
+ ${imageRatios
145
+ .map(
146
+ (ratio) => `<figure>
147
+ <img
148
+ loading="lazy"
149
+ src="${args.src}"
150
+ alt="${args.alt}"
151
+ width="${args.width}"
152
+ height="${args.height}"
153
+ class="qld__image-ratio-${ratio}"
154
+ />
155
+ <figcaption>qld__image-ratio-${ratio}</figcaption>
156
+ </figure>`,
157
+ )
158
+ .join("\n ")}
159
+ </div>`,
160
+ };
161
+
162
+ /**
163
+ * The utility also works on a wrapper — put it on the `figure` (or any
164
+ * containing element) and the image inside is cropped to match.
165
+ */
166
+ export const RatioOnWrapper = {
167
+ name: "Ratio on the wrapper",
168
+ render: (args) =>
169
+ `<figure class="qld__image-ratio-16x9" style="max-width: 480px;">
170
+ <img
171
+ loading="lazy"
172
+ src="${args.src}"
173
+ alt="${args.alt}"
174
+ width="${args.width}"
175
+ height="${args.height}"
176
+ />
177
+ </figure>`,
178
+ };
179
+
180
+ /**
181
+ * Every `qld__image-position--*` utility, over a test sheet that names the zone
182
+ * left in frame.
183
+ *
184
+ * `object-position` can only slide an image along the axis its crop overflows,
185
+ * so no single crop shows all nine utilities doing something. The first row
186
+ * crops a landscape sheet to a tall frame — spare width, so the `left`/`right`
187
+ * half of each name bites and the `top`/`bottom` half sits idle. The second row
188
+ * crops a portrait sheet to a wide frame, which is the other way round.
189
+ */
190
+ export const AllPositions = {
191
+ name: "All image positions",
192
+ args: { alt: "Test sheet of nine labelled zones" },
193
+ render: (args) => {
194
+ const reference = (src) =>
195
+ `<figure style="max-width: 300px;">
196
+ <img loading="lazy" src="${src}" alt="${args.alt}" />
197
+ <figcaption>Uncropped source</figcaption>
198
+ </figure>`;
199
+
200
+ const grid = (src, ratio) =>
201
+ `<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 24px;">
202
+ ${imagePositions
203
+ .map(
204
+ (position) => `<figure>
205
+ <img
206
+ loading="lazy"
207
+ src="${src}"
208
+ alt="${args.alt}"
209
+ class="qld__image-ratio-${ratio} qld__image-position--${position}"
210
+ />
211
+ <figcaption>qld__image-position--${position}</figcaption>
212
+ </figure>`,
213
+ )
214
+ .join("\n ")}
215
+ </div>`;
216
+
217
+ return `<h3>Landscape image, tall crop — slides left and right</h3>
218
+ ${reference(imagePositionDemoImage)}
219
+ ${grid(imagePositionDemoImage, "2x3")}
220
+ <h3 class="qld__margin-t-component">Portrait image, wide crop — slides up and down</h3>
221
+ ${reference(imagePositionDemoImagePortrait)}
222
+ ${grid(imagePositionDemoImagePortrait, "16x9")}`;
223
+ },
224
+ };
225
+
226
+ const borderRadiusSizes = ["xs", "sm", "md", "lg"];
227
+
228
+ /**
229
+ * One figure per radius size, plus a final figure carrying the utility itself.
230
+ * `captioned` decides whether each figure gets a `figcaption`, which is what
231
+ * changes how the radius has to be applied.
232
+ */
233
+ function renderRoundedGrid(args, captioned) {
234
+ const label = (text) => (captioned ? `<figcaption>${text}</figcaption>` : "");
235
+
236
+ const image = (classes) => `<img
237
+ loading="lazy"
238
+ src="${args.src}"
239
+ alt="${args.alt}"
240
+ class="qld__image-ratio-4x3${classes ? ` ${classes}` : ""}"
241
+ />`;
242
+
243
+ return `<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 24px;">
244
+ ${borderRadiusSizes
245
+ .map(
246
+ (size) => `<figure>
247
+ ${image(`qld__border-radius--${size}`)}
248
+ ${label(`img.qld__border-radius--${size}`)}
249
+ </figure>`,
250
+ )
251
+ .join("\n ")}
252
+ <figure class="qld__border-radius--md">
253
+ ${image("")}
254
+ ${label("figure.qld__border-radius--md")}
255
+ </figure>
256
+ </div>`;
257
+ }
258
+
259
+ /**
260
+ * `qld__border-radius--*` on the image rounds the top corners and passes the
261
+ * matching radius to the caption, so the pair reads as one rounded block. Put
262
+ * the same utility on the `figure` instead and it clips its contents to shape.
263
+ */
264
+ export const RoundedCorners = {
265
+ name: "Rounded corners",
266
+ render: (args) => renderRoundedGrid(args, true),
267
+ };
268
+
269
+ /**
270
+ * Without a caption the image keeps all four corners, whether the utility sits
271
+ * on the `img` or on the `figure` around it.
272
+ */
273
+ export const RoundedCornersWithoutCaption = {
274
+ name: "Rounded corners without caption",
275
+ render: (args) => renderRoundedGrid(args, false),
276
+ };
277
+
96
278
  export const ResponsiveImage = {
97
279
  args: { responsiveMediaImg: true },
98
280
  decorators: [
@@ -0,0 +1,101 @@
1
+ import { initComponents } from "../../../.storybook/decorators";
2
+ import { initMegaMenu } from "../../components/mega_main_navigation/js/global";
3
+ import initCtaLinks from "../../components/_global/js/cta_links/global";
4
+ import { initAccordion } from "../../components/accordion/js/global";
5
+ import initInternalNavigation from "../../components/internal_navigation/js/global";
6
+ import initInPageNavigation from "../../components/in_page_navigation/js/global";
7
+ import initPromoPanel from "../../components/promo_panel/js/global";
8
+ import {
9
+ renderSiteHeader,
10
+ renderSiteFooter,
11
+ renderPrefooter,
12
+ renderBanner,
13
+ renderSideNav,
14
+ renderInPageNav,
15
+ renderPromoPanel,
16
+ contentBody,
17
+ } from "./templateHelpers.js";
18
+
19
+ /**
20
+ * A full content page: header + mega main nav, a banner with breadcrumbs, a
21
+ * side navigation beside the main content, an in-page ("On this page")
22
+ * navigation, body copy, a pre-footer and the footer.
23
+ */
24
+ function renderContentPage(args) {
25
+ const { site } = args;
26
+
27
+ return `
28
+ ${renderSiteHeader(site)}
29
+ <main class="main" role="main">
30
+ ${renderBanner({
31
+ headingPrimary: "Applying for a health card",
32
+ abstract:
33
+ "Everything you need to know about eligibility, the application process and what happens next.",
34
+ backgroundColour: "dark",
35
+ backgroundType: "texture",
36
+ showBreadcrumbs: true,
37
+ })}
38
+ <div class="qld__body">
39
+ <div class="container-fluid">
40
+ <div class="row">
41
+ <div class="col-xs-12 col-md-4 col-lg-3">
42
+ ${renderSideNav()}
43
+ </div>
44
+ <div class="col-xs-12 col-md-8 col-lg-9" id="content">
45
+ ${renderInPageNav()}
46
+ ${contentBody()}
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ ${renderPromoPanel(site, {
52
+ title: "Need help with your application?",
53
+ abstract:
54
+ "Our team can guide you through eligibility, the application process and what happens next.",
55
+ imageAlignment: "qld__promo-panel--image-right",
56
+ bodyBackground: "qld__body--light",
57
+ promoPanelIcon: undefined,
58
+ })}
59
+ ${renderPrefooter(site, { pageType: "content" })}
60
+ </main>
61
+ ${renderSiteFooter(site)}
62
+ `;
63
+ }
64
+
65
+ export default {
66
+ title: "4. Templates/Content page",
67
+ render: renderContentPage,
68
+ parameters: {
69
+ layout: "fullscreen",
70
+ docs: {
71
+ description: {
72
+ component:
73
+ "A representative content page assembled from the design system's " +
74
+ "layout and component stories: header with the horizontal mega " +
75
+ "navigation, a banner carrying breadcrumbs, a side navigation, the " +
76
+ "main content column with an in-page navigation and body copy, then " +
77
+ "the pre-footer and footer.",
78
+ },
79
+ },
80
+ },
81
+ // All the interactive chrome a content page needs: the mega menu and its CTA
82
+ // links, the side nav (an accordion below its breakpoint), and the in-page
83
+ // navigation that builds its list from the headings inside `#content`.
84
+ decorators: [
85
+ initComponents([
86
+ initMegaMenu,
87
+ initCtaLinks,
88
+ initAccordion,
89
+ initInternalNavigation,
90
+ initInPageNavigation,
91
+ initPromoPanel,
92
+ ]),
93
+ ],
94
+ // The page supplies its own full-width layout, so opt out of controls that
95
+ // don't apply to a whole-page composition.
96
+ argTypes: {
97
+ site: { table: { disable: true } },
98
+ },
99
+ };
100
+
101
+ export const Default = {};