vanilla-framework 4.43.0 → 4.45.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-framework",
3
- "version": "4.43.0",
3
+ "version": "4.45.0",
4
4
  "author": {
5
5
  "email": "webteam@canonical.com",
6
6
  "name": "Canonical Webteam"
@@ -97,6 +97,18 @@
97
97
  }
98
98
  }
99
99
 
100
+ %vf-pseudo-border--left {
101
+ position: relative;
102
+
103
+ &::before {
104
+ @extend %vf-pseudo-border;
105
+ bottom: 0;
106
+ height: auto;
107
+ top: 0;
108
+ width: 1px;
109
+ }
110
+ }
111
+
100
112
  %hr {
101
113
  background: $colors--theme--border-default;
102
114
  border: 0;
@@ -0,0 +1,440 @@
1
+ @import 'settings';
2
+
3
+ /*
4
+ @classreference
5
+ in-page-navigation:
6
+ Root element:
7
+ .p-in-page-navigation:
8
+ in-page navigation in default variant.
9
+ "&.is-sticky":
10
+ Sticky version of in-page navigation.
11
+
12
+ Dropdown toggle:
13
+ .p-in-page-navigation__dropdown-toggle:
14
+ Button to toggle between horizontal and vertical dropdown navigation on small/medium screens.
15
+ .p-in-page-navigation__dropdown-toggle-icon:
16
+ Chevron icon inside the dropdown toggle button.
17
+
18
+ Items list heading:
19
+ .p-in-page-navigation__heading:
20
+ Heading for in-page navigation items group (text only).
21
+
22
+ Items list:
23
+ .p-in-page-navigation__list:
24
+ Group of in-page navigation items (usually a `<ul>` element).
25
+
26
+ Item element:
27
+ .p-in-page-navigation__item:
28
+ Single item in in-page navigation (usually a `<li>` element). May contain nested items lists.
29
+
30
+ Item link:
31
+ .p-in-page-navigation__link:
32
+ Single link in the in-page navigation.
33
+ */
34
+
35
+ // Shared text styles mixin
36
+ @mixin vf-in-page-navigation-text {
37
+ @include vf-in-page-navigation-spacing-left;
38
+
39
+ padding-bottom: $spv--x-small;
40
+ padding-right: 0;
41
+ padding-top: $spv--x-small;
42
+ }
43
+
44
+ // Shared link styles for large screens and expanded dropdown
45
+ @mixin vf-in-page-navigation-link-vertical {
46
+ -webkit-box-orient: vertical;
47
+ display: -webkit-box;
48
+ -webkit-line-clamp: 2;
49
+ line-clamp: 2;
50
+ overflow: hidden;
51
+ text-overflow: ellipsis;
52
+ }
53
+
54
+ @mixin vf-p-in-page-navigation {
55
+ // Use a consistent border width for left-highlight and list border
56
+ $in-page-navigation-border-width: 3px;
57
+
58
+ // Scroll margins for headings within the parent grid section
59
+ .grid-row:has(.p-in-page-navigation) {
60
+ /* stylelint-disable selector-max-type */
61
+ h2,
62
+ h3,
63
+ h4 {
64
+ /* stylelint-enable selector-max-type */
65
+ scroll-margin-top: 72px;
66
+
67
+ @media screen and (width >= $threshold-4-8-col) {
68
+ scroll-margin-top: 24px;
69
+ }
70
+
71
+ // When the page has a sticky main navigation, increase scroll margin
72
+ // to account for the fixed header height. This prevents headings from
73
+ // being hidden behind the navigation when scrolling to anchors.
74
+ @at-root body:has(.p-navigation.is-sticky) &,
75
+ body:has(.p-navigation--sliding.is-sticky) & {
76
+ scroll-margin-top: 120px;
77
+
78
+ @media screen and (width >= $threshold-4-8-col) {
79
+ scroll-margin-top: 80px;
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ // Sticky styles for large screens
86
+ .p-in-page-navigation {
87
+ margin-bottom: $spv--strip-regular;
88
+ max-height: 100vh;
89
+ overflow-y: auto;
90
+ position: sticky;
91
+ top: $spv--strip-shallow;
92
+ }
93
+
94
+ // We re-enable scrolling when the dropdown is expanded on small/medium screens
95
+ // `is-expanded` class is toggled via JS
96
+ .p-in-page-navigation.is-expanded {
97
+ overflow-y: auto;
98
+ }
99
+
100
+ // Custom tooltip styles for scrollable navigation
101
+ // We dynamically calculate the position using JS
102
+ .p-in-page-navigation .p-tooltip--right {
103
+ position: relative;
104
+
105
+ .p-tooltip__message {
106
+ // Position will be set via JS using CSS custom properties
107
+ left: var(--tooltip-left, auto);
108
+ // Use fixed positioning to escape overflow clipping
109
+ position: fixed;
110
+ top: var(--tooltip-top, auto);
111
+ z-index: 100;
112
+ }
113
+ }
114
+
115
+ // Left hand border
116
+ .p-in-page-navigation__container > .p-in-page-navigation__list {
117
+ @extend %vf-pseudo-border--left;
118
+
119
+ &::before {
120
+ background-color: $colors--theme--border-low-contrast;
121
+ width: $in-page-navigation-border-width;
122
+ }
123
+ }
124
+
125
+ .p-in-page-navigation__list {
126
+ @extend %vf-list;
127
+ color: $colors--theme--text-inactive;
128
+
129
+ &:last-of-type::after {
130
+ content: none;
131
+ }
132
+ }
133
+
134
+ .p-in-page-navigation__heading {
135
+ @extend %common-default-text-properties;
136
+ @extend %small-caps-text;
137
+
138
+ display: block;
139
+ font-size: map-get($base-font-sizes, base);
140
+ font-weight: $font-weight-bold;
141
+ margin: 0;
142
+ max-width: none;
143
+ padding: $spv--x-small 0 $spv--small;
144
+ }
145
+
146
+ .p-in-page-navigation__link {
147
+ @include vf-in-page-navigation-text;
148
+ @include vf-in-page-navigation-link-vertical;
149
+ @include vf-focus-themed;
150
+
151
+ color: $colors--theme--text-muted;
152
+
153
+ &:visited {
154
+ color: $colors--theme--text-muted;
155
+ }
156
+
157
+ &:hover {
158
+ color: $colors--theme--text-default;
159
+ text-decoration: none;
160
+ }
161
+
162
+ // vf-highlight-bar is rendered above focus outline
163
+ // so we need to hide it on focus
164
+ &:focus::before {
165
+ display: none;
166
+ }
167
+
168
+ // Display the highlight when focussing in modern browsers that support
169
+ // focus-visible.
170
+ &:focus:not(:focus-visible)::before {
171
+ display: block;
172
+ }
173
+
174
+ // Active state for current navigation item
175
+ &.is-active,
176
+ &[aria-current='true'] {
177
+ @extend %vf-pseudo-border--left;
178
+
179
+ color: $colors--theme--text-default;
180
+ cursor: default;
181
+
182
+ &::before {
183
+ background-color: $colors--theme--text-default;
184
+ width: $in-page-navigation-border-width;
185
+ }
186
+ }
187
+ }
188
+
189
+ // Dropdown toggle button
190
+ .p-in-page-navigation__dropdown-toggle {
191
+ background-color: $colors--theme--background-default;
192
+ border: 0;
193
+ display: none;
194
+ line-height: map-get($icon-sizes, default);
195
+ margin: 0;
196
+ padding: $spv--large $sph--x-large;
197
+
198
+ &:focus {
199
+ @include vf-focus;
200
+ }
201
+
202
+ &:hover {
203
+ background: $colors--theme--background-default;
204
+ }
205
+
206
+ .p-in-page-navigation__dropdown-toggle-icon {
207
+ height: map-get($icon-sizes, default);
208
+ width: map-get($icon-sizes, default);
209
+ }
210
+ }
211
+
212
+ .p-in-page-navigation__container {
213
+ position: relative;
214
+ }
215
+
216
+ // Nested 2nd level of navigation indentation
217
+ .p-in-page-navigation__item .p-in-page-navigation__list .p-in-page-navigation__link {
218
+ // @media screen and (width >= $threshold-4-8-col) {
219
+ @include vf-in-page-navigation-spacing-left($multiplier: 2);
220
+ // }
221
+ }
222
+
223
+ // Small/medium screens
224
+ @media screen and (width < $threshold-4-8-col) {
225
+ .p-in-page-navigation {
226
+ background-color: $colors--theme--background-default;
227
+ margin-bottom: $spv--strip-shallow;
228
+ margin-left: (-$spv--x-large);
229
+ margin-right: (-$spv--x-large);
230
+ overflow-y: unset;
231
+ }
232
+
233
+ // Sticky styles for parent column
234
+ // IMPORTANT: Pattern requires a grid-col as a parent
235
+ [class^='grid-col']:has(> .p-in-page-navigation) {
236
+ position: sticky;
237
+ top: 0;
238
+ z-index: 1;
239
+
240
+ > .p-in-page-navigation {
241
+ position: static;
242
+ }
243
+ }
244
+
245
+ // Top level navigation list
246
+ .p-in-page-navigation__container > .p-in-page-navigation__list {
247
+ background-color: $colors--theme--background-default;
248
+ padding-left: $spv--large;
249
+
250
+ &::before {
251
+ left: $spv--large;
252
+ }
253
+
254
+ > .p-in-page-navigation__item {
255
+ scroll-margin-left: $spv--large;
256
+ }
257
+ }
258
+
259
+ // Hide heading unless dropdown is expanded
260
+ .p-in-page-navigation__heading {
261
+ display: none;
262
+ }
263
+
264
+ // Link styles for horizontal layout
265
+ .p-in-page-navigation__link {
266
+ display: inline-block;
267
+ line-height: $spv--x-large;
268
+ max-width: 33.33vw; // Limit link width to 1/3 viewport
269
+ overflow: hidden;
270
+ padding: $spv--medium $spv--large;
271
+ text-overflow: ellipsis;
272
+ vertical-align: middle;
273
+
274
+ // Adjust active state pseudo-border for horizontal layout
275
+ &.is-active,
276
+ &[aria-current='true'] {
277
+ &::before {
278
+ height: 50%;
279
+ top: 25%;
280
+ }
281
+ }
282
+ }
283
+
284
+ // Nested link indentation when expanded
285
+ .p-in-page-navigation.is-expanded .p-in-page-navigation__item .p-in-page-navigation__list .p-in-page-navigation__link {
286
+ @include vf-in-page-navigation-spacing-left($multiplier: 2);
287
+ }
288
+
289
+ // Container styles for horizontal layout
290
+ .p-in-page-navigation__container {
291
+ align-items: center;
292
+ background-color: $colors--theme--background-default;
293
+ display: grid;
294
+ grid-template-columns: 1fr auto;
295
+
296
+ // Full-width bottom border
297
+ &::after {
298
+ background-color: $colors--theme--border-low-contrast;
299
+ bottom: 0;
300
+ content: '';
301
+ height: 1px;
302
+ left: 50%;
303
+ position: absolute;
304
+ transform: translateX(-50%);
305
+ width: 100%;
306
+ }
307
+ }
308
+
309
+ .p-in-page-navigation__dropdown-toggle {
310
+ display: block;
311
+ grid-column: 2;
312
+ justify-self: end;
313
+ }
314
+
315
+ // Collapsed dropdown styles
316
+ .p-in-page-navigation:not(.is-expanded) .p-in-page-navigation__container {
317
+ .p-in-page-navigation__list {
318
+ grid-column: 1;
319
+ margin-bottom: 0;
320
+ min-width: 0;
321
+ overflow-x: auto;
322
+ overflow-y: hidden;
323
+ scroll-behavior: smooth;
324
+ scrollbar-width: none; // Firefox
325
+ white-space: nowrap;
326
+
327
+ &::-webkit-scrollbar {
328
+ display: none; // Chrome/Safari/Opera
329
+ }
330
+
331
+ // Remove left side border
332
+ &::before {
333
+ content: none;
334
+ }
335
+
336
+ .p-in-page-navigation__item {
337
+ display: inline-block;
338
+ }
339
+ }
340
+
341
+ // Hide nested lists
342
+ .p-in-page-navigation__item .p-in-page-navigation__list {
343
+ display: none;
344
+ }
345
+ }
346
+
347
+ // Expanded dropdown styles
348
+ // Grid layout: [list content | toggle button]
349
+ // Row 1: heading * | toggle
350
+ // Row 2: nav list |
351
+ // * If present, otherwise nav list is in row 1
352
+ .p-in-page-navigation.is-expanded .p-in-page-navigation__container {
353
+ align-items: start;
354
+ background-color: $colors--theme--background-alt;
355
+ display: grid;
356
+ grid-template-columns: 1fr auto;
357
+
358
+ > .p-in-page-navigation__list {
359
+ background-color: $colors--theme--background-alt;
360
+ grid-column: 1 / 2;
361
+ grid-row: 2;
362
+ margin-bottom: $spv--large;
363
+ }
364
+
365
+ .p-in-page-navigation__item .p-in-page-navigation__list {
366
+ display: block;
367
+ }
368
+
369
+ .p-in-page-navigation__heading {
370
+ display: block;
371
+ grid-column: 1 / 2;
372
+ grid-row: 1;
373
+ padding: $spv--medium $spv--large;
374
+ }
375
+
376
+ // Override for when there is no heading
377
+ &:not(:has(> .p-in-page-navigation__heading)) > .p-in-page-navigation__list {
378
+ grid-row: 1;
379
+ margin-top: $spv--medium;
380
+ }
381
+
382
+ .p-in-page-navigation__dropdown-toggle {
383
+ background-color: $colors--theme--background-alt;
384
+ display: block;
385
+ grid-column: 2 / 3;
386
+ grid-row: 1;
387
+ justify-self: end;
388
+ }
389
+ }
390
+
391
+ .p-in-page-navigation.is-expanded .p-in-page-navigation__link {
392
+ @include vf-in-page-navigation-text;
393
+ @include vf-in-page-navigation-link-vertical;
394
+
395
+ max-width: unset;
396
+ padding-right: $spv--large;
397
+
398
+ &.is-active,
399
+ &[aria-current='true'] {
400
+ &::before {
401
+ height: auto;
402
+ top: 0;
403
+ }
404
+ }
405
+ }
406
+ }
407
+
408
+ // Small screens only
409
+ @media screen and (width < $threshold-4-small-4-med-col) {
410
+ .p-in-page-navigation {
411
+ margin-left: (-$spv--large);
412
+ margin-right: (-$spv--large);
413
+ }
414
+
415
+ .p-in-page-navigation__link {
416
+ max-width: 50vw; // Limit link width to 1/2 viewport
417
+ }
418
+
419
+ .p-in-page-navigation__dropdown-toggle {
420
+ padding: $spv--large;
421
+ }
422
+ }
423
+ }
424
+
425
+ // Helper
426
+
427
+ // elements in side nav should be aligned from left based on grid margin for given screen size
428
+ // additional offset is added when icons are used or for nested navigation levels
429
+ @mixin vf-in-page-navigation-spacing-left(
430
+ // property to adjust spacing, defaults to `padding-left`
431
+ $prop: padding-left,
432
+ // how many times grid margin width should be multiplied (for nested navigation levels)
433
+ $multiplier: 1
434
+ ) {
435
+ #{$prop}: $multiplier * map-get($grid-margin-widths, small);
436
+
437
+ @media screen and (width >= $threshold-4-small-4-med-col) {
438
+ #{$prop}: $multiplier * map-get($grid-margin-widths, default);
439
+ }
440
+ }
@@ -15,7 +15,7 @@ $tooltip-overlay-top-border-radius: 0% 0% 100% 100%;
15
15
 
16
16
  &:focus,
17
17
  &:hover {
18
- .p-tooltip__message {
18
+ > .p-tooltip__message:first-of-type {
19
19
  display: inline;
20
20
  text-decoration: initial;
21
21
  }
@@ -105,7 +105,7 @@ $tooltip-overlay-top-border-radius: 0% 0% 100% 100%;
105
105
  .p-tooltip--btm-center {
106
106
  @extend %tooltip;
107
107
 
108
- .p-tooltip__message {
108
+ > .p-tooltip__message {
109
109
  bottom: inherit;
110
110
  left: 50%;
111
111
  top: 100%;
@@ -54,6 +54,7 @@
54
54
  @import 'patterns_separator';
55
55
  @import 'patterns_side-navigation-expandable';
56
56
  @import 'patterns_side-navigation';
57
+ @import 'patterns_in-page-navigation';
57
58
  @import 'patterns_slider';
58
59
  @import 'patterns_status-label';
59
60
  @import 'patterns_strip';
@@ -156,6 +157,7 @@
156
157
  @include vf-p-separator;
157
158
  @include vf-p-side-navigation;
158
159
  @include vf-p-side-navigation-expandable;
160
+ @include vf-p-in-page-navigation;
159
161
  @include vf-p-slider;
160
162
  @include vf-p-status-label;
161
163
  @include vf-p-strip;
@@ -6,7 +6,7 @@
6
6
  {% from "_macros/shared/vf_linked-logo-block.jinja" import vf_linked_logo_block %}
7
7
  {% from "_macros/shared/vf_logo-block.jinja" import vf_logo_block %}
8
8
 
9
- {% macro vf_basic_section_blocks(items=[]) %}
9
+ {% macro vf_basic_section_blocks(items=[], override_last_item_padding=false) %}
10
10
  {%- for item in items -%}
11
11
  {%- set item_padding = (item.get("padding", "")) | trim -%}
12
12
  {%- if item_padding not in ["shallow"] -%}
@@ -19,7 +19,7 @@
19
19
  {%- endif -%}
20
20
 
21
21
  {#- Last item should not have any additional padding - the pattern itself already has bottom padding -#}
22
- {%- if loop.last -%}
22
+ {%- if loop.last and not override_last_item_padding -%}
23
23
  {%- set item_classes = "" -%}
24
24
  {%- endif -%}
25
25
  <div{%- if item_classes | length > 0 %} class="{{ item_classes }}"{%- endif -%}>
@@ -41,22 +41,27 @@
41
41
  {%- endmacro -%}
42
42
 
43
43
  # image_config
44
- # - aspect_ratio: "16-9" | "3-2" | "" (default is "")
44
+ # - aspect_ratio: "16-9" | "3-2" | "2-3", "cinematic" | "" (default is "")
45
45
  # - caption_html: The HTML content for the caption of the image (optional). Will be wrapped in <figcaption>, and the image and caption will be wrapped in a <figure>.
46
+ # - is_highlighted: Whether to apply the "is-highlighted" class to the image container (default is true).
47
+ # - is_cover: Whether to apply the "is-cover" class to the image container (default is false).
46
48
  # - attrs: A dictionary of attributes to apply to the image
47
49
  {% macro _basic_section_image(image_config={}) %}
48
50
  {%- set aspect_ratio = image_config.get("aspect_ratio", "") | trim -%}
49
- {%- if aspect_ratio not in ["16-9", "3-2"] -%}
51
+ {%- if aspect_ratio not in ["16-9", "3-2", "2-3", "cinematic"] -%}
50
52
  {%- set aspect_ratio = "" -%}
51
53
  {%- endif -%}
52
54
 
53
55
  {%- set caption_html = image_config.get("caption_html", "") | trim -%}
54
56
  {%- set has_caption = caption_html | length > 0 -%}
55
57
 
58
+ {%- set is_highlighted = image_config.get("is_highlighted", true) -%}
59
+ {%- set is_cover = image_config.get("is_cover", false) -%}
60
+
56
61
  {%- if has_caption -%}
57
62
  <figure>
58
63
  {%- endif -%}
59
- <div class="p-image-container{%- if aspect_ratio | length > 0 -%}--{{- aspect_ratio -}}{%- endif %} is-highlighted">
64
+ <div class="p-image-container{%- if aspect_ratio | length > 0 -%}--{{- aspect_ratio -}}{%- endif %}{%- if is_highlighted %} is-highlighted{% endif -%}{%- if is_cover %} is-cover{% endif -%}">
60
65
  <img class="p-image-container__image {{ image_config.get("attrs", {}).get("class", "") -}}"
61
66
  {%- for attr, value in image_config.get("attrs", {}).items() -%}
62
67
  {% if attr != "class" %}
@@ -238,6 +243,7 @@
238
243
  padding="default",
239
244
  is_split_on_medium=false,
240
245
  top_rule_variant="default",
246
+ override_last_item_padding=false,
241
247
  attrs={}
242
248
  ) -%}
243
249
 
@@ -280,7 +286,7 @@
280
286
  {%- if has_subtitle -%}
281
287
  <p class="p-heading--{{ subtitle_heading_level }}">{{- subtitle_text -}}</p>
282
288
  {%- endif -%}
283
- {{- vf_basic_section_blocks(items=items) -}}
289
+ {{- vf_basic_section_blocks(items=items, override_last_item_padding=override_last_item_padding) -}}
284
290
  </div>
285
291
  </div>
286
292
  </section>
@@ -1,3 +1,6 @@
1
+ {% from "_macros/shared/vf_cta-block.jinja" import vf_cta_block %}
2
+ {% from "_macros/vf_basic-section.jinja" import vf_basic_section_blocks %}
3
+
1
4
  # Params
2
5
  # title_text: Hero title text (required)
3
6
  # subtitle_text: Hero subtitle text
@@ -5,28 +8,35 @@
5
8
  # is_split_on_medium: whether the layout is split on tablet in 50/50, 25/75, and 75/25 layouts.
6
9
  # If false, the layout is stacked on tablet.
7
10
  # If true, the layout is split on tablet.
8
- # Slots
9
- # description: paragraph-style content below the title and subtitle
10
- # cta: call-to-action block below the description
11
- # image: slot for image content
12
- # signpost_image: slot for signpost (left column) image content in 25/75 layout. Required for 25/75 layout.
13
11
  # display_blank_signpost_image_space: whether to indent the content for 25/75 layout on large screens.
12
+ # blocks: list of content blocks for the hero section. Includes description, cta, image, and signpost_image blocks.
13
+ # Slots
14
+ # description (deprecated): paragraph-style content below the title and subtitle
15
+ # cta (deprecated): call-to-action block below the description
16
+ # image (deprecated): slot for image content
17
+ # signpost_image (deprecated): slot for signpost (left column) image content in 25/75 layout. Required for 25/75 layout.
14
18
  {% macro vf_hero(
15
19
  title_text,
16
20
  subtitle_text='',
17
21
  layout="fallback",
18
22
  is_split_on_medium=false,
19
- display_blank_signpost_image_space=false
23
+ display_blank_signpost_image_space=false,
24
+ blocks=[]
20
25
  ) -%}
26
+ {%- set description_blocks = blocks | selectattr("type", "equalto", "description") | list -%}
27
+ {%- set cta_block = blocks | selectattr("type", "equalto", "cta-block") | list | first | default(None) -%}
28
+ {%- set image_block = blocks | selectattr("type", "equalto", "image") | list | first | default(None) -%}
29
+ {%- set signpost_image_block = blocks | selectattr("type", "equalto", "signpost_image") | list | first | default(None) -%}
30
+
21
31
  {% set has_subtitle = subtitle_text|trim|length > 0 %}
22
32
  {% set description_content = caller('description') %}
23
- {% set has_description = description_content|trim|length > 0 %}
33
+ {% set has_description = description_blocks | length > 0 or description_content|trim|length > 0 %}
24
34
  {% set cta_content = caller('cta') %}
25
- {% set has_cta = cta_content|trim|length > 0 %}
35
+ {% set has_cta = cta_block or cta_content|trim|length > 0 %}
26
36
  {% set image_content = caller('image') %}
27
- {% set has_image = image_content|trim|length > 0 %}
37
+ {% set has_image = image_block or image_content|trim|length > 0 %}
28
38
  {% set signpost_image_content = caller('signpost_image') %}
29
- {% set has_signpost_image = signpost_image_content|trim|length > 0 or display_blank_signpost_image_space %}
39
+ {% set has_signpost_image = signpost_image_block or signpost_image_content|trim|length > 0 or display_blank_signpost_image_space %}
30
40
 
31
41
  {#- User can pass layout as "X-Y" or "X/Y" -#}
32
42
  {% set layout = layout | trim | replace('/', '-') %}
@@ -98,7 +108,9 @@
98
108
  {%- endmacro %}
99
109
 
100
110
  {%- macro _hero_cta_block() -%}
101
- {% if has_cta -%}
111
+ {%- if cta_block -%}
112
+ {{- vf_basic_section_blocks(items=[cta_block], override_last_item_padding=true) -}}
113
+ {% elif has_cta -%}
102
114
  <div class="p-cta-block">
103
115
  {{ cta_content }}
104
116
  </div>
@@ -106,19 +118,35 @@
106
118
  {%- endmacro %}
107
119
 
108
120
  {%- macro _hero_description_block() -%}
109
- {% if has_description %}
121
+ {%- if description_blocks | length > 0 -%}
122
+ {% for description_block in description_blocks %}
123
+ {{ vf_basic_section_blocks(items=[description_block], override_last_item_padding=true) }}
124
+ {% endfor %}
125
+ {% elif has_description %}
110
126
  <div class="p-section--shallow">
111
127
  {{ description_content }}
112
128
  </div>
113
129
  {% endif %}
114
130
  {%- endmacro %}
115
131
 
132
+ {%- macro _hero_image_block() -%}
133
+ {%- if image_block -%}
134
+ {{- vf_basic_section_blocks(items=[image_block], override_last_item_padding=true) -}}
135
+ {% elif has_image -%}
136
+ {{ image_content }}
137
+ {% endif %}
138
+ {%- endmacro %}
139
+
116
140
  {%- macro _hero_signpost_image_block() -%}
117
- {% if layout == '25-75' and has_signpost_image -%}
118
- <div class="p-section--shallow">
141
+ <div class="p-section--shallow">
142
+ {%- if signpost_image_block -%}
143
+ {% set _ = signpost_image_block.update(type="image") %}
144
+ {% set _ = signpost_image_block.item.attrs.update({"class": "u-no-margin"}) %}
145
+ {{- vf_basic_section_blocks(items=[signpost_image_block], override_last_item_padding=true) -}}
146
+ {% else -%}
119
147
  {{ signpost_image_content }}
120
- </div>
121
- {% endif %}
148
+ {% endif %}
149
+ </div>
122
150
  {%- endmacro %}
123
151
 
124
152
  <section class="p-section--hero">
@@ -140,7 +168,7 @@
140
168
  </div>
141
169
  {% if has_image -%}
142
170
  <div class="{{ col_classes[1] }}">
143
- {{ image_content }}
171
+ {{ _hero_image_block() }}
144
172
  </div>
145
173
  {% endif -%}
146
174
  {% elif (has_full_width_image and not has_signpost_image) or is_50_50_no_image %}
@@ -156,8 +184,8 @@
156
184
  {{- _hero_description_block() -}}
157
185
  {{- _hero_cta_block() -}}
158
186
  </div>
159
- {{ image_content -}}
160
- {% elif has_signpost_image %}
187
+ {{ _hero_image_block() }}
188
+ {% elif has_signpost_image and layout == '25-75' %}
161
189
  {#- 25/75 Signpost layout -#}
162
190
  <div class="{{ col_classes[0] }}">
163
191
  {{ _hero_signpost_image_block() -}}
@@ -172,7 +200,7 @@
172
200
  </div>
173
201
  {% if has_image %}
174
202
  {#- Signpost with image is always full-width, so set it after the columns -#}
175
- {{- image_content }}
203
+ {{- _hero_image_block() }}
176
204
  {% endif -%}
177
205
  {% else %}
178
206
  <div class="{{ col_classes[0] }}">
@@ -185,7 +213,7 @@
185
213
  </div>
186
214
  {% if has_image -%}
187
215
  <div class="{{ col_classes[1] }}">
188
- {{ image_content }}
216
+ {{ _hero_image_block() }}
189
217
  </div>
190
218
  {% endif -%}
191
219
  {% endif -%}
@@ -0,0 +1,93 @@
1
+ {#
2
+ Parameters:
3
+ - title (string) (optional): The text to be displayed as the title. Defauts to None and doesn"t render.
4
+ - navigation_items (list<object>) (optional): A list of navigation items to be included in the in-page navigation. Each item is an object with the following properties:
5
+ - id (string) (required): The ID of the section to link to.
6
+ - text (string) (required): The text to be displayed for the navigation item.
7
+ - scope (string) (optional): Rendering mode for navigation items.
8
+ - "manual" (default): Render from `navigation_items`.
9
+ - "full-page": Autogenerate from headings via JS.
10
+ - primary_heading (string) (optional, requires scope="full-page"): The heading level to treat as primary. Accepts "h2" or "h3".
11
+ - secondary_heading (string) (optional, requires primary_heading): The heading level to treat as secondary. Accepts "h3" or "h4"
12
+ - excludes (list<string>) (optional): A list of exclusions for headings (only applies when scope="full-page").
13
+ Supports two formats:
14
+ - CSS selector (e.g. "#some-id", ".some-class"): excludes headings matching the selector.
15
+ - Text match (e.g. "text:Newsletter signup"): excludes headings whose text content matches (case-insensitive).
16
+ #}
17
+
18
+ {%- macro vf_in_page_navigation(title=None, navigation_items=[], scope="full-page", primary_heading="h2", secondary_heading=None, excludes=[]) -%}
19
+ {%- set nav_id = "in-page-navigation-list" -%}
20
+ {%- set valid_primary_headings = ["h2", "h3"] %}
21
+ {%- set valid_secondary_headings = ["h3", "h4"] %}
22
+ {%- set valid_scopes = ["full-page", "manual"] %}
23
+ {%- if scope not in valid_scopes -%}
24
+ {%- set scope = "full-page" -%}
25
+ {%- endif -%}
26
+ {%- set excludes_queries = excludes | join(",") + ",.p-in-page-navigation__heading" -%}
27
+
28
+ <div class="p-in-page-navigation"
29
+ data-in-page-navigation-scope="{{ scope }}"
30
+ {%- if primary_heading and primary_heading in valid_primary_headings -%} data-in-page-navigation-primary="{{ primary_heading }}" {%- if secondary_heading and secondary_heading in valid_secondary_headings -%} data-in-page-navigation-secondary="{{ secondary_heading }}" {%- endif -%} {%- else -%} data-in-page-navigation-primary="h2" {% endif %}
31
+ data-in-page-navigation-excludes="{{ excludes_queries }}">
32
+
33
+ <nav class="p-in-page-navigation__container"
34
+ aria-label="{% if title %}{{ title }}{% else %}In-page navigation{% endif %}">
35
+
36
+ {%- if title -%}
37
+ <h3 class="p-in-page-navigation__heading">{{ title | safe }}</h3>
38
+ {%- endif -%}
39
+ <ul class="p-in-page-navigation__list js-in-page-nav-list"
40
+ id="{{ nav_id }}"
41
+ aria-expanded="false">
42
+ {%- if scope != "full-page" -%}
43
+ {%- for item in navigation_items -%}
44
+ <li class="p-in-page-navigation__item p-tooltip--right"
45
+ aria-describedby="{{ item.id }}-tooltip">
46
+ <a class="p-in-page-navigation__link{% if loop.first %} is-active{% endif %}"
47
+ href="#{{ item.id }}">{{ item.text }}</a>
48
+ <span class="p-tooltip__message u-hide"
49
+ role="tooltip"
50
+ id="{{ item.id }}-tooltip">{{ item.text }}</span>
51
+ {%- if item.children -%}
52
+ <ul class="p-in-page-navigation__list">
53
+ {%- for child in item.children -%}
54
+ <li class="p-in-page-navigation__item p-tooltip--right"
55
+ aria-describedby="{{ child.id }}-tooltip">
56
+ <a class="p-in-page-navigation__link" href="#{{ child.id }}">{{ child.text }}</a>
57
+ <span class="p-tooltip__message u-hide"
58
+ role="tooltip"
59
+ id="{{ child.id }}-tooltip">{{ child.text }}</span>
60
+ </li>
61
+ {%- endfor -%}
62
+ </ul>
63
+ {%- endif -%}
64
+ </li>
65
+ {%- endfor -%}
66
+ {%- endif -%}
67
+ </ul>
68
+
69
+ <button class="p-in-page-navigation__dropdown-toggle"
70
+ aria-expanded="false"
71
+ aria-controls="{{ nav_id }}">
72
+ <i class="p-icon--chevron-down p-in-page-navigation__dropdown-toggle-icon"></i>
73
+ <i class="p-icon--chevron-up p-in-page-navigation__dropdown-toggle-icon u-hide"></i>
74
+ </button>
75
+
76
+ </nav>
77
+ </div>
78
+
79
+ {# Templates for JS auto-generation (used when scope="full-page") #}
80
+ {%- if scope == "full-page" -%}
81
+ <template class="js-in-page-nav-template-item">
82
+ <li class="p-in-page-navigation__item p-tooltip--right"
83
+ aria-describedby="#">
84
+ <a class="p-in-page-navigation__link" href="#"></a>
85
+ <span class="p-tooltip__message u-hide" role="tooltip" id="#"></span>
86
+ </li>
87
+ </template>
88
+ <template class="js-in-page-nav-template-sublist">
89
+ <ul class="p-in-page-navigation__list">
90
+ </ul>
91
+ </template>
92
+ {%- endif -%}
93
+ {%- endmacro -%}