vanilla-framework 4.37.2 → 4.39.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 +1 -1
- package/scss/_patterns_data-spotlight.scss +2 -3
- package/scss/_patterns_equal-height-row.scss +18 -11
- package/templates/_macros/shared/vf_article_block.jinja +1 -0
- package/templates/_macros/shared/vf_blog-block.jinja +60 -0
- package/templates/_macros/shared/vf_cta-block.jinja +93 -0
- package/templates/_macros/shared/vf_description-block.jinja +33 -0
- package/templates/_macros/shared/vf_divided-section-block.jinja +67 -0
- package/templates/_macros/shared/vf_equal-heights-block.jinja +68 -0
- package/templates/_macros/shared/vf_linked-logo-block.jinja +34 -0
- package/templates/_macros/shared/vf_logo-block.jinja +29 -0
- package/templates/_macros/shared/vf_section_top_rule.jinja +27 -4
- package/templates/_macros/shared/vf_tabs.jinja +78 -0
- package/templates/_macros/vf_basic-section.jinja +58 -139
- package/templates/_macros/vf_blog.jinja +4 -48
- package/templates/_macros/vf_cta-section.jinja +9 -2
- package/templates/_macros/vf_divided-section.jinja +4 -56
- package/templates/_macros/vf_equal-heights.jinja +28 -48
- package/templates/_macros/vf_linked-logo-section.jinja +5 -32
- package/templates/_macros/vf_pricing-block.jinja +9 -1
- package/templates/_macros/vf_quote-wrapper.jinja +278 -139
- package/templates/_macros/vf_tab-section.jinja +238 -0
package/package.json
CHANGED
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
.p-data-spotlight--2-blocks .p-data-spotlight__title-col {
|
|
18
|
-
|
|
19
|
-
grid-column: span 6 !important;
|
|
18
|
+
grid-column: span 4 !important;
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
21
|
|
|
@@ -44,7 +43,7 @@
|
|
|
44
43
|
|
|
45
44
|
.p-data-spotlight--2-blocks .p-data-spotlight__title-col {
|
|
46
45
|
// TODO: update the following after the equal height row is updated to use the new grid system
|
|
47
|
-
grid-column: span
|
|
46
|
+
grid-column: span 4 !important;
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
49
|
}
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
Item element within an equal height row column.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
@use 'sass:math';
|
|
25
26
|
@import 'settings';
|
|
26
27
|
|
|
27
28
|
@mixin vf-p-equal-height-row {
|
|
28
29
|
.p-equal-height-row,
|
|
29
30
|
.p-equal-height-row--wrap {
|
|
30
|
-
|
|
31
|
-
@extend %vf-row;
|
|
31
|
+
@extend %vf-grid-row;
|
|
32
32
|
|
|
33
33
|
position: relative;
|
|
34
34
|
|
|
@@ -38,20 +38,20 @@
|
|
|
38
38
|
border-top-style: solid;
|
|
39
39
|
border-top-width: 1px;
|
|
40
40
|
display: grid;
|
|
41
|
-
grid-column: span $grid-columns-small;
|
|
41
|
+
grid-column: span $grid-8-columns-small;
|
|
42
42
|
grid-row: span 4;
|
|
43
43
|
grid-template-rows: subgrid;
|
|
44
44
|
margin-bottom: $spv--small;
|
|
45
45
|
position: relative;
|
|
46
46
|
|
|
47
47
|
@media screen and ($breakpoint-small <= width < $breakpoint-large) {
|
|
48
|
-
grid-column: span $grid-columns-medium;
|
|
48
|
+
grid-column: span $grid-8-columns-medium;
|
|
49
49
|
grid-template-columns: subgrid;
|
|
50
50
|
|
|
51
51
|
// At medium size, each column item will take half of the available
|
|
52
52
|
// cols from the parent grid
|
|
53
53
|
.p-equal-height-row__item {
|
|
54
|
-
grid-column: span calc($grid-columns-medium / 2);
|
|
54
|
+
grid-column: span calc($grid-8-columns-medium / 2);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// At medium size, position the first column item on the left of the
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
|
|
66
66
|
@media screen and (width >= $breakpoint-large) {
|
|
67
67
|
border: none;
|
|
68
|
-
grid-column: span calc($grid-columns / 4);
|
|
68
|
+
grid-column: span calc($grid-8-columns / 4);
|
|
69
69
|
margin-bottom: 0;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
background-color: $colors--theme--border-low-contrast;
|
|
90
90
|
// Row-level dividers are not visible on smaller screen sizes
|
|
91
91
|
display: none;
|
|
92
|
-
grid-column-end: span
|
|
92
|
+
grid-column-end: span $grid-8-columns;
|
|
93
93
|
grid-column-start: 1;
|
|
94
94
|
margin: auto;
|
|
95
95
|
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
.p-equal-height-row__col {
|
|
130
130
|
@media screen and ($breakpoint-small <= width < $breakpoint-large) {
|
|
131
131
|
// Fit 2 columns onto each row at medium size
|
|
132
|
-
grid-column: span calc($grid-columns-medium / 2);
|
|
132
|
+
grid-column: span calc($grid-8-columns-medium / 2);
|
|
133
133
|
grid-template-columns: none;
|
|
134
134
|
|
|
135
135
|
.p-equal-height-row__item {
|
|
@@ -146,23 +146,30 @@
|
|
|
146
146
|
// ADVANCED GRID SUPPORT
|
|
147
147
|
|
|
148
148
|
// Support for 25-75 split on large screen size
|
|
149
|
-
//
|
|
149
|
+
// The equal height row has been moved to the 8-column grid, but we still support the equal height row component
|
|
150
|
+
// being placed inside legacy grid row to ensure backward compatibility, this works since the ratio of columns
|
|
151
|
+
// remains the same.
|
|
152
|
+
// This would break if legacy grid is used inside the component itself.
|
|
153
|
+
.grid-row--25-75-on-large > .grid-col,
|
|
154
|
+
.grid-row > .grid-col-6,
|
|
150
155
|
.row--25-75-on-large > .col,
|
|
151
156
|
.row > .col-9 {
|
|
152
157
|
& .p-equal-height-row,
|
|
153
158
|
& .p-equal-height-row--wrap {
|
|
154
159
|
@media screen and (width >= $breakpoint-large) {
|
|
155
|
-
grid-template-columns: repeat(
|
|
160
|
+
grid-template-columns: repeat(#{(math.div($grid-8-columns, 4) * 3)}, minmax(0, 1fr));
|
|
156
161
|
}
|
|
157
162
|
}
|
|
158
163
|
}
|
|
159
164
|
|
|
165
|
+
.grid-row--50-50-on-large > .grid-col,
|
|
166
|
+
.grid-row > .grid-col-4,
|
|
160
167
|
.row--50-50-on-large > .col,
|
|
161
168
|
.row > .col-6 {
|
|
162
169
|
& .p-equal-height-row,
|
|
163
170
|
& .p-equal-height-row--wrap {
|
|
164
171
|
@media screen and (width >= $breakpoint-large) {
|
|
165
|
-
grid-template-columns: repeat(
|
|
172
|
+
grid-template-columns: repeat(#{math.div($grid-8-columns, 2)}, minmax(0, 1fr));
|
|
166
173
|
}
|
|
167
174
|
}
|
|
168
175
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{% from "_macros/shared/vf_article_block.jinja" import vf_article_block %}
|
|
2
|
+
|
|
3
|
+
{#
|
|
4
|
+
Shared embeddable blog block.
|
|
5
|
+
Exposes `vf_blog_block(articles, template_config, template_mode, fallback_image_url)`
|
|
6
|
+
which renders the articles container (template mode or list) and uses the
|
|
7
|
+
internal `_blog_article` helper to render each article (image/title/description).
|
|
8
|
+
#}
|
|
9
|
+
|
|
10
|
+
{%- macro _blog_article(article_config={}, template_mode=False, fallback_image_url="https://assets.ubuntu.com/v1/94c82a15-blog_fallback_image.png") -%}
|
|
11
|
+
{%- set base_image_container_classes = "p-image-container--16-9 is-cover article-image" -%}
|
|
12
|
+
{%- if not template_mode -%}
|
|
13
|
+
{%- set default_image_attrs = {
|
|
14
|
+
'src': fallback_image_url,
|
|
15
|
+
'alt': 'Blog fallback image'
|
|
16
|
+
}
|
|
17
|
+
-%}
|
|
18
|
+
{%- set input_image_attrs = article_config.get('image', {}).get('attrs', {}) -%}
|
|
19
|
+
{#- Merge user attributes over the defaults -#}
|
|
20
|
+
{%- set img_attrs = default_image_attrs.copy() -%}
|
|
21
|
+
{%- set _ = img_attrs.update(input_image_attrs) -%}
|
|
22
|
+
{#- class merging -#}
|
|
23
|
+
{%- set base_image_class = 'p-image-container__image' -%}
|
|
24
|
+
{%- set input_image_class = input_image_attrs.get('class', '') -%}
|
|
25
|
+
{%- if input_image_class -%}
|
|
26
|
+
{%- set final_image_classes = base_image_class + ' ' + input_image_class -%}
|
|
27
|
+
{%- else -%}
|
|
28
|
+
{%- set final_image_classes = base_image_class -%}
|
|
29
|
+
{%- endif -%}
|
|
30
|
+
{%- set _ = img_attrs.update({'class': final_image_classes}) -%}
|
|
31
|
+
{#- Build the HTML tag -#}
|
|
32
|
+
{%- set ns = namespace(image_html="<img") -%}
|
|
33
|
+
{%- for attr, value in img_attrs.items() -%}
|
|
34
|
+
{%- set ns.image_html = ns.image_html + " " + attr + "=\"" + value + "\"" -%}
|
|
35
|
+
{%- endfor -%}
|
|
36
|
+
{%- set image_html = ns.image_html + ">" -%}
|
|
37
|
+
{%- set _ = article_config.update({'image_html': '<div class="' + base_image_container_classes + '">\n ' + image_html + '\n </div>'}) -%}
|
|
38
|
+
{%- else -%}
|
|
39
|
+
{#- template mode - the template JS will fill in the image slot. -#}
|
|
40
|
+
{%- set _ = article_config.update({'image_html': '<div class="' + base_image_container_classes + '"></div>'}) -%}
|
|
41
|
+
{%- endif -%}
|
|
42
|
+
{{ vf_article_block(article_config=article_config, attrs={"class": "grid-col-2 grid-col-medium-2"}, template_mode=template_mode) }}
|
|
43
|
+
|
|
44
|
+
{%- endmacro -%}
|
|
45
|
+
|
|
46
|
+
{%- macro vf_blog_block(articles=[], template_config={}, fallback_image_url="https://assets.ubuntu.com/v1/94c82a15-blog_fallback_image.png") -%}
|
|
47
|
+
{%- if template_config.get("enabled", False) -%}
|
|
48
|
+
<div id="{{ template_config.get('template_container_id', 'articles') }}" class="p-blog__articles"></div>
|
|
49
|
+
<template style="display: none;" id="{{ template_config.get('template_id', 'template') }}">
|
|
50
|
+
{{ _blog_article(template_mode=True) }}
|
|
51
|
+
</template>
|
|
52
|
+
{%- else -%}
|
|
53
|
+
<div class="p-blog__articles grid-row">
|
|
54
|
+
{%- for article in articles -%}
|
|
55
|
+
{{ _blog_article(article_config=article, fallback_image_url=fallback_image_url) }}
|
|
56
|
+
{%- endfor -%}
|
|
57
|
+
</div>
|
|
58
|
+
{%- endif -%}
|
|
59
|
+
|
|
60
|
+
{%- endmacro -%}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{#-
|
|
2
|
+
Params
|
|
3
|
+
- content_html: The inner HTML for the call-to-action button.
|
|
4
|
+
- button_type: "primary" | "secondary" (default is "primary")
|
|
5
|
+
- attrs: A dictionary of attributes to apply to the button or link.
|
|
6
|
+
#}
|
|
7
|
+
{% macro _cta_button(content_html, button_type="", attrs={}) %}
|
|
8
|
+
{%- set cta_element_tag = "button" -%}
|
|
9
|
+
{%- if "href" in attrs and attrs.get("href", "") | length > 0 -%}
|
|
10
|
+
{%- set cta_element_tag = "a" -%}
|
|
11
|
+
{%- endif -%}
|
|
12
|
+
|
|
13
|
+
{%- if button_type not in ["primary", "secondary"] -%}
|
|
14
|
+
{%- set button_type = "" -%}
|
|
15
|
+
{%- endif -%}
|
|
16
|
+
|
|
17
|
+
{%- set cta_class = "" -%}
|
|
18
|
+
{%- if button_type == "primary" -%}
|
|
19
|
+
{%- set cta_class = "p-button--positive" -%}
|
|
20
|
+
{%- elif button_type == "secondary" -%}
|
|
21
|
+
{%- set cta_class = "p-button" -%}
|
|
22
|
+
{%- endif -%}
|
|
23
|
+
|
|
24
|
+
<{{ cta_element_tag }} class="{{ cta_class }} {{ attrs.get("class", "") }}"
|
|
25
|
+
{%- for attr, value in attrs.items() -%}
|
|
26
|
+
{% if attr != "class" %}
|
|
27
|
+
{{ attr }}="{{ value }}"
|
|
28
|
+
{%- endif -%}
|
|
29
|
+
{%- endfor -%}
|
|
30
|
+
>
|
|
31
|
+
{{- content_html | safe -}}
|
|
32
|
+
</{{ cta_element_tag }}>
|
|
33
|
+
{% endmacro %}
|
|
34
|
+
|
|
35
|
+
{#-
|
|
36
|
+
Helper macro to render a container with primary, secondary, and link CTA elements.
|
|
37
|
+
|
|
38
|
+
Parameters:
|
|
39
|
+
- primary (Object, Optional) - Primary call-to-action button configuration. Properties:
|
|
40
|
+
- content_html (String, Required) - The inner HTML for the primary button
|
|
41
|
+
- attrs (Object, Optional) - HTML attributes for the button (e.g., href, class)
|
|
42
|
+
Default: {} (no primary CTA rendered)
|
|
43
|
+
|
|
44
|
+
- secondaries (Array, Optional) - Array of secondary CTA buttons (typically 2 recommended).
|
|
45
|
+
Each item has the same structure as primary:
|
|
46
|
+
- content_html (String, Required) - The inner HTML for the secondary button
|
|
47
|
+
- attrs (Object, Optional) - HTML attributes for the button
|
|
48
|
+
Default: [] (no secondary CTAs rendered)
|
|
49
|
+
|
|
50
|
+
- link (Object, Optional) - Text link configuration. Properties:
|
|
51
|
+
- content_html (String, Required) - The inner HTML for the link
|
|
52
|
+
- attrs (Object, Optional) - HTML attributes for the link (e.g., href, class)
|
|
53
|
+
Default: {} (no link rendered)
|
|
54
|
+
|
|
55
|
+
Note: If any 'attrs' contains an 'href' attribute, that element will render as an <a> tag.
|
|
56
|
+
Otherwise, it renders as a <button> tag.
|
|
57
|
+
-#}
|
|
58
|
+
{% macro vf_cta_block(primary={}, secondaries=[], link={}) %}
|
|
59
|
+
{%- if not primary -%}{%- set primary = {} -%}{%- endif -%}
|
|
60
|
+
{%- if not secondaries -%}{%- set secondaries = [] -%}{%- endif -%}
|
|
61
|
+
{%- if not link -%}{%- set link = {} -%}{%- endif -%}
|
|
62
|
+
{%- set primary_content_html = primary.get("content_html", "") | trim -%}
|
|
63
|
+
{%- set has_primary = primary_content_html | length > 0 -%}
|
|
64
|
+
{%- set has_secondaries = secondaries | length > 0 -%}
|
|
65
|
+
{%- set link_content_html = link.get("content_html", "") | trim -%}
|
|
66
|
+
{% set has_link = link_content_html | length > 0 -%}
|
|
67
|
+
{%- if has_primary or has_secondaries or has_link -%}
|
|
68
|
+
<div class="p-cta-block u-no-padding--bottom">
|
|
69
|
+
{%- if has_primary %}
|
|
70
|
+
{{ _cta_button(
|
|
71
|
+
content_html=primary_content_html,
|
|
72
|
+
attrs=primary.get("attrs", {}),
|
|
73
|
+
button_type="primary"
|
|
74
|
+
) }}
|
|
75
|
+
{%- endif -%}
|
|
76
|
+
{%- if has_secondaries %}
|
|
77
|
+
{%- for secondary in secondaries -%}
|
|
78
|
+
{{ _cta_button(
|
|
79
|
+
content_html=secondary.get("content_html", ""),
|
|
80
|
+
attrs=secondary.get("attrs", {}),
|
|
81
|
+
button_type="secondary"
|
|
82
|
+
) }}
|
|
83
|
+
{%- endfor -%}
|
|
84
|
+
{%- endif -%}
|
|
85
|
+
{%- if has_link %}
|
|
86
|
+
{{ _cta_button(
|
|
87
|
+
content_html=link_content_html,
|
|
88
|
+
attrs=link.get("attrs", {})
|
|
89
|
+
) }}
|
|
90
|
+
{%- endif -%}
|
|
91
|
+
</div>
|
|
92
|
+
{%- endif -%}
|
|
93
|
+
{% endmacro %}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{#-
|
|
2
|
+
Helper macro to render description/subtitle text content within a section.
|
|
3
|
+
Supports both plain text (wrapped in <p>) and raw HTML rendering.
|
|
4
|
+
|
|
5
|
+
Parameters:
|
|
6
|
+
- type (String, Optional): Content type. One of:
|
|
7
|
+
- "text" (default) - Content is rendered within a <p> tag
|
|
8
|
+
- "html" - Content is rendered as raw HTML without wrapping
|
|
9
|
+
Default: "text"
|
|
10
|
+
|
|
11
|
+
- content (String, Optional): The description content.
|
|
12
|
+
If type is "text", plain text is safe to use.
|
|
13
|
+
If type is "html", pass HTML markup that should be rendered as-is.
|
|
14
|
+
Default: ""
|
|
15
|
+
-#}
|
|
16
|
+
{% macro vf_description_block(type="text", content="") %}
|
|
17
|
+
{%- if not type -%}{%- set type = "text" -%}{%- endif -%}
|
|
18
|
+
{%- if not content -%}{%- set content = "" -%}{%- endif -%}
|
|
19
|
+
|
|
20
|
+
{%- set type = type | trim -%}
|
|
21
|
+
{%- set content = content | trim -%}
|
|
22
|
+
{%- if type not in ["text", "html"] -%}
|
|
23
|
+
{%- set type = "text" -%}
|
|
24
|
+
{%- endif -%}
|
|
25
|
+
|
|
26
|
+
{%- if content | length > 0 -%}
|
|
27
|
+
{%- if type == "text" -%}
|
|
28
|
+
<p>{{- content -}}</p>
|
|
29
|
+
{%- elif type == "html" -%}
|
|
30
|
+
{{- content | safe -}}
|
|
31
|
+
{%- endif -%}
|
|
32
|
+
{%- endif -%}
|
|
33
|
+
{% endmacro %}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{% from "_macros/vf_basic-section.jinja" import basic_section_item %}
|
|
2
|
+
|
|
3
|
+
{#
|
|
4
|
+
Embeddable right-hand column renderer for divided-section.
|
|
5
|
+
Renders optional description-block items, then divided-blocks with
|
|
6
|
+
muted rules between items. Uses shallow bottom padding for items,
|
|
7
|
+
and ensures the last item of the last block has no bottom padding.
|
|
8
|
+
|
|
9
|
+
Params:
|
|
10
|
+
- blocks: array (same shape as vf_divided_section's `blocks` param)
|
|
11
|
+
#}
|
|
12
|
+
|
|
13
|
+
{% macro _get_item_padding_classes_divided(item_config={}, isLastLoopItr=False, isLastBlockItr=False) -%}
|
|
14
|
+
{%- set item_padding = (item_config.get("padding", "")) | trim -%}
|
|
15
|
+
{%- if item_padding not in ["shallow"] -%}
|
|
16
|
+
{%- set item_padding = "" -%}
|
|
17
|
+
{%- endif -%}
|
|
18
|
+
{%- set item_classes = "" -%}
|
|
19
|
+
{%- if item_padding | length > 0 -%}
|
|
20
|
+
{%- set item_classes = "p-section--" + item_padding -%}
|
|
21
|
+
{%- endif -%}
|
|
22
|
+
{%- if isLastLoopItr and isLastBlockItr -%}
|
|
23
|
+
{%- set item_classes = "" -%}
|
|
24
|
+
{%- endif -%}
|
|
25
|
+
{{ item_classes | trim }}
|
|
26
|
+
{%- endmacro %}
|
|
27
|
+
|
|
28
|
+
{% macro vf_divided_section_block(blocks=[]) -%}
|
|
29
|
+
{%- set description_block = blocks | selectattr("type", "equalto", "description-block") | first -%}
|
|
30
|
+
{%- set divided_blocks = blocks | selectattr("type", "equalto", "divided-block") | list -%}
|
|
31
|
+
|
|
32
|
+
{%- if description_block | length > 0 -%}
|
|
33
|
+
{%- for item in description_block.get("items", []) -%}
|
|
34
|
+
{%- set item_classes = _get_item_padding_classes_divided(item, loop.last, (divided_blocks|length == 0)) -%}
|
|
35
|
+
<div {%- if item_classes | length > 0 %} class="{{ item_classes }}"{%- endif -%}>{{- basic_section_item(item) -}}</div>
|
|
36
|
+
{%- endfor -%}
|
|
37
|
+
{%- if divided_blocks | length > 0 -%}<hr class="p-rule--muted" />{%- endif %}
|
|
38
|
+
{%- endif -%}
|
|
39
|
+
|
|
40
|
+
{%- for block in divided_blocks -%}
|
|
41
|
+
{%- set outer_last = loop.last -%}
|
|
42
|
+
{%- set ns = namespace(list_element="ul", list_class="p-list") -%}
|
|
43
|
+
{%- if block.get("bullet_type") == "number" -%}
|
|
44
|
+
{%- set ns.list_element = "ol" -%}
|
|
45
|
+
{%- set ns.list_class = "p-stepped-list" -%}
|
|
46
|
+
{%- endif -%}
|
|
47
|
+
|
|
48
|
+
<{{ ns.list_element }} class="p-divided__block {{ ns.list_class }} u-no-padding">
|
|
49
|
+
{%- for item in block.get("items", []) -%}
|
|
50
|
+
{%- set list_bullet_type = "has-bullet" if block.bullet_type == "bullet" else "is-ticked" if block.bullet_type == "status" -%}
|
|
51
|
+
<li class="p-divided__heading u-no-padding--bottom {{ list_bullet_type }}{% if ns.list_class == 'p-stepped-list' %} p-stepped-list__item{% else %} p-list__item{% endif %}">
|
|
52
|
+
<div class="{%- if not loop.last -%}p-section--shallow{%- endif -%}{% if ns.list_class == 'p-stepped-list' %} p-stepped-list__title{% endif %}">
|
|
53
|
+
{%- if item.title_text -%}
|
|
54
|
+
<h3 class="p-heading--5 u-no-padding">{{ item.title_text }}</h3>
|
|
55
|
+
{%- endif -%}
|
|
56
|
+
{%- for content in item.contents -%}
|
|
57
|
+
{%- set item_classes = _get_item_padding_classes_divided(content, loop.last, outer_last) -%}
|
|
58
|
+
<div {%- if item_classes | length > 0 %} class="{{ item_classes }}"{%- endif -%}>{{- basic_section_item(content) -}}</div>
|
|
59
|
+
{%- endfor -%}
|
|
60
|
+
</div>
|
|
61
|
+
</li>
|
|
62
|
+
{%- if not loop.last %}<hr class="p-rule--muted" />{% endif %}
|
|
63
|
+
{%- endfor -%}
|
|
64
|
+
</{{ ns.list_element }}>
|
|
65
|
+
{%- endfor -%}
|
|
66
|
+
|
|
67
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{#-
|
|
2
|
+
vf_equal_heights_block
|
|
3
|
+
Renders the inner Equal Heights grid (no outer section/rows), suitable for embedding in other patterns (e.g. tab panels).
|
|
4
|
+
|
|
5
|
+
Params:
|
|
6
|
+
- items (array) (required): Each item may include 'image_html', 'title_text', 'title_link', 'description_html', and 'cta_html'.
|
|
7
|
+
- subtitle_heading_level (int) (optional): 4 or 5 (default 5) — used for item titles.
|
|
8
|
+
- highlight_images (boolean) (optional): Whether to add a subtle grey background behind images.
|
|
9
|
+
- image_aspect_ratio_small (string) (optional): "square" | "2-3" | "3-2" | "16-9" | "cinematic" | "auto" (default: "square").
|
|
10
|
+
- image_aspect_ratio_medium (string) (optional): same options as above (default: "square").
|
|
11
|
+
- image_aspect_ratio_large (string) (optional): same options as above (default: "2-3").
|
|
12
|
+
-#}
|
|
13
|
+
{%- macro vf_equal_heights_block(
|
|
14
|
+
items=[],
|
|
15
|
+
subtitle_heading_level=5,
|
|
16
|
+
highlight_images=false,
|
|
17
|
+
image_aspect_ratio_small="square",
|
|
18
|
+
image_aspect_ratio_medium="square",
|
|
19
|
+
image_aspect_ratio_large="2-3"
|
|
20
|
+
) -%}
|
|
21
|
+
<div class="p-equal-height-row--wrap">
|
|
22
|
+
{%- for item in items -%}
|
|
23
|
+
{%- set image = item.get("image_html", "") | trim -%}
|
|
24
|
+
{%- set title = item.get("title_text", "") | trim -%}
|
|
25
|
+
{%- set title_link_attrs = item.get("title_link_attrs", {}) -%}
|
|
26
|
+
{%- set description = item.get("description_html", "") | trim -%}
|
|
27
|
+
{%- set cta = item.get("cta_html", "") | trim -%}
|
|
28
|
+
<div class="p-equal-height-row__col is-borderless">
|
|
29
|
+
{#- Image item -#}
|
|
30
|
+
<div class="p-equal-height-row__item">
|
|
31
|
+
{%- if image | length > 0 %}
|
|
32
|
+
<div
|
|
33
|
+
class="p-image-container{% if image_aspect_ratio_small != 'auto' %} p-image-container--{{ image_aspect_ratio_small }}-on-small{% endif %}{% if image_aspect_ratio_medium != 'auto' %} p-image-container--{{ image_aspect_ratio_medium }}-on-medium{% endif %}{% if image_aspect_ratio_large != 'auto' %} p-image-container--{{ image_aspect_ratio_large }}-on-large{% endif %} is-cover{% if highlight_images %} is-highlighted{% endif %}">
|
|
34
|
+
{{- image | safe -}}
|
|
35
|
+
</div>
|
|
36
|
+
<hr class="p-rule--highlight"/>
|
|
37
|
+
{%- endif -%}
|
|
38
|
+
</div>
|
|
39
|
+
{#- Title item -#}
|
|
40
|
+
<div class="p-equal-height-row__item">
|
|
41
|
+
{%- if title | length > 0 -%}
|
|
42
|
+
<p class="p-heading--{{ subtitle_heading_level }}">
|
|
43
|
+
{%- if title_link_attrs and "href" in title_link_attrs -%}
|
|
44
|
+
<a {% for attr, value in title_link_attrs.items() -%} {{ attr }}="{{ value }}" {%- endfor -%}>
|
|
45
|
+
{%- endif -%}
|
|
46
|
+
{{- title -}}
|
|
47
|
+
{%- if title_link_attrs and "href" in title_link_attrs -%}
|
|
48
|
+
</a>
|
|
49
|
+
{%- endif -%}
|
|
50
|
+
</p>
|
|
51
|
+
{%- endif -%}
|
|
52
|
+
</div>
|
|
53
|
+
{#- Description item (optional) -#}
|
|
54
|
+
<div class="p-equal-height-row__item">
|
|
55
|
+
{{- description | safe -}}
|
|
56
|
+
</div>
|
|
57
|
+
{#- CTA item (optional) -#}
|
|
58
|
+
<div class="p-equal-height-row__item">
|
|
59
|
+
{%- if cta | length > 0 -%}
|
|
60
|
+
<p>
|
|
61
|
+
{{- cta | safe -}}
|
|
62
|
+
</p>
|
|
63
|
+
{%- endif -%}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
{%- endfor -%}
|
|
67
|
+
</div>
|
|
68
|
+
{%- endmacro -%}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{#
|
|
2
|
+
- links: array of link objects
|
|
3
|
+
- cols_per_item: How many columns each logo should use. Defaults to 2.
|
|
4
|
+
#}
|
|
5
|
+
{% macro vf_linked_logo_block(linked_logo_config={}) -%}
|
|
6
|
+
{%- set links = linked_logo_config.get("links", []) -%}
|
|
7
|
+
{%- set cols_per_item = linked_logo_config.get("cols_per_item", 2) -%}
|
|
8
|
+
{%- if links|length > 1 -%}
|
|
9
|
+
<div class="grid-row">
|
|
10
|
+
{%- for link in links -%}
|
|
11
|
+
<div class="grid-col-{{ cols_per_item }} grid-col-medium-{{ cols_per_item }}">
|
|
12
|
+
<a href="{{ link.href }}" aria-label="{{ link.label }}">
|
|
13
|
+
<div class="p-image-container--16-9 is-highlighted">
|
|
14
|
+
{#- If image_attrs is present, use it to generate the image HTML -#}
|
|
15
|
+
{%- if "image_attrs" in link -%}
|
|
16
|
+
<img class="p-image-container__image {{ link["image_attrs"]["class"] }}"
|
|
17
|
+
{% for attr, value in link["image_attrs"].items() %}
|
|
18
|
+
{% if attr != "class" %}
|
|
19
|
+
{{ attr }}="{{ value }}"
|
|
20
|
+
{% endif %}
|
|
21
|
+
{% endfor %}
|
|
22
|
+
>
|
|
23
|
+
{% else %}
|
|
24
|
+
{{ link.image_html | safe }}
|
|
25
|
+
{% endif %}
|
|
26
|
+
</div>
|
|
27
|
+
<hr class="p-rule--muted">
|
|
28
|
+
<p>{{ link.text }}</p>
|
|
29
|
+
</a>
|
|
30
|
+
</div>
|
|
31
|
+
{%- endfor -%}
|
|
32
|
+
</div>
|
|
33
|
+
{%- endif -%}
|
|
34
|
+
{%- endmacro %}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{% from "_macros/shared/vf_dedent.jinja" import vf_dedent %}
|
|
2
|
+
|
|
3
|
+
# logo_block_config
|
|
4
|
+
# - logos: A list of logos to be displayed in the section.
|
|
5
|
+
# - - attrs: A dictionary of attributes to apply to the logo
|
|
6
|
+
# - - is_fixed_width: Boolean to indicate if the logo should be wrapped in a fixed-width container (default is true).
|
|
7
|
+
# - - has_line_break_after: Boolean to indicate if a line break should be included after the logo (default is false). This will be hidden on small screens.
|
|
8
|
+
# See https://vanillaframework.io/docs/patterns/logo-section#line-breaks
|
|
9
|
+
{% macro vf_logo_block(logo_block_config={}) %}
|
|
10
|
+
{%- set is_fixed_width = logo_block_config.get("is_fixed_width", true) != false -%}
|
|
11
|
+
{%- if is_fixed_width -%}<div class="u-fixed-width">{%- endif -%}
|
|
12
|
+
<div class="p-logo-section">
|
|
13
|
+
{%- for logo in logo_block_config.get("logos", []) -%}
|
|
14
|
+
<div class="p-logo-section__item">
|
|
15
|
+
<img class="p-logo-section__logo {{ logo.get("attrs", {}).get("class", "") -}}"
|
|
16
|
+
{%- for attr, value in logo.get("attrs", {}).items() -%}
|
|
17
|
+
{% if attr != "class" %}
|
|
18
|
+
{{ attr }}="{{ value }}"
|
|
19
|
+
{%- endif -%}
|
|
20
|
+
{%- endfor -%}
|
|
21
|
+
/>
|
|
22
|
+
{%- if logo.get("has_line_break_after", false) -%}
|
|
23
|
+
<br class="u-hide--small">
|
|
24
|
+
{%- endif -%}
|
|
25
|
+
</div>
|
|
26
|
+
{%- endfor -%}
|
|
27
|
+
</div>
|
|
28
|
+
{%- if is_fixed_width -%}</div>{%- endif -%}
|
|
29
|
+
{% endmacro %}
|
|
@@ -1,7 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
{#-
|
|
2
|
+
|
|
3
|
+
Helper macro to render a horizontal rule at the top of a section.
|
|
4
|
+
This macro is used by various section patterns to add visual separation.
|
|
5
|
+
|
|
6
|
+
Note: This file should be renamed to `vf_section-top-rule.jinja` for consistency with other patterns.
|
|
7
|
+
|
|
8
|
+
Parameters:
|
|
9
|
+
- top_rule_variant (String, Optional): Variant of horizontal rule to use. One of:
|
|
10
|
+
- "default" - Standard horizontal rule
|
|
11
|
+
- "muted" - Muted/lighter horizontal rule
|
|
12
|
+
- "highlighted" - Highlighted/emphasized horizontal rule (internally uses p-rule--highlight)
|
|
13
|
+
- "none" - No rule rendered
|
|
14
|
+
Default: "default"
|
|
15
|
+
|
|
16
|
+
- is_fixed_width (Boolean, Optional): Whether to constrain the rule to fixed content width.
|
|
17
|
+
When false (default), the rule should be wrapped in a .grid-row element.
|
|
18
|
+
Default: False
|
|
19
|
+
-#}
|
|
20
|
+
{%- macro vf_section_top_rule(
|
|
21
|
+
top_rule_variant="default",
|
|
22
|
+
is_fixed_width=False
|
|
23
|
+
) -%}
|
|
5
24
|
{%- set top_rule_variant = top_rule_variant | trim | lower -%}
|
|
6
25
|
{%- if top_rule_variant not in ["default", "muted", "highlighted", "none"] -%}
|
|
7
26
|
{%- set top_rule_variant = "default" -%}
|
|
@@ -23,6 +42,10 @@
|
|
|
23
42
|
{%- endif -%}
|
|
24
43
|
{%- endif -%}
|
|
25
44
|
|
|
45
|
+
{%- if is_fixed_width -%}
|
|
46
|
+
{%- set top_rule_classes = top_rule_classes + " " + "is-fixed-width" -%}
|
|
47
|
+
{%- endif -%}
|
|
48
|
+
|
|
26
49
|
{%- if top_rule_variant != "none" -%}
|
|
27
50
|
<hr class="{{- top_rule_classes -}}"/>
|
|
28
51
|
{%- endif -%}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{#-
|
|
2
|
+
Helper macro to render a tabbed interface with tabs and associated content panels.
|
|
3
|
+
This is a low-level building block used by higher-order components like vf_tab_section
|
|
4
|
+
to manage tab rendering and accessibility.
|
|
5
|
+
|
|
6
|
+
Parameters:
|
|
7
|
+
- list (Object) - Configuration object with the following properties:
|
|
8
|
+
- tabs_list_attrs (Object, Optional) - HTML attributes to forward to the p-tabs__list element.
|
|
9
|
+
Note: The 'role' attribute is always set to 'tablist' and cannot be customized.
|
|
10
|
+
See attribute forwarding docs for more info.
|
|
11
|
+
|
|
12
|
+
- tabs (Array, Required) - Array of tab configurations. Each tab must have:
|
|
13
|
+
- name (String, Required) - Unique identifier for the tab.
|
|
14
|
+
Used to link tab buttons and content via id and aria-controls attributes.
|
|
15
|
+
Must be unique within the tabs array.
|
|
16
|
+
|
|
17
|
+
- tab_html (String, Required) - HTML content for the tab button/label.
|
|
18
|
+
This is the visible text/markup shown in the tab bar.
|
|
19
|
+
|
|
20
|
+
- content_html (String, Required) - HTML content for the tab panel.
|
|
21
|
+
This is the content shown when the tab is active.
|
|
22
|
+
|
|
23
|
+
Accessibility: The macro automatically renders proper ARIA attributes for tab management:
|
|
24
|
+
- First tab is rendered as selected (aria-selected="true")
|
|
25
|
+
- Tab buttons have role="tab" and aria-controls linking to their content panels
|
|
26
|
+
- Content panels have role="tabpanel" and aria-labelledby linking to their tab button
|
|
27
|
+
- All but the first panel are initially hidden with hidden="hidden"
|
|
28
|
+
-#}
|
|
29
|
+
{%- macro vf_tabs(list={}) -%}
|
|
30
|
+
{%- set tabs_list_attrs = list.get("tabs_list_attrs", {}) -%}
|
|
31
|
+
{%- set list_class = tabs_list_attrs.get("class", "") | trim -%}
|
|
32
|
+
{%- set tabs = list.get("tabs", []) -%}
|
|
33
|
+
<div class="p-tabs">
|
|
34
|
+
<div
|
|
35
|
+
class="p-tabs__list{% if list_class | length > 0 %} {{ list_class -}}{% endif %}"
|
|
36
|
+
role="tablist"
|
|
37
|
+
{%- for attr, value in tabs_list_attrs.items() -%}
|
|
38
|
+
{% if attr not in ["class", "role", "tabs"] %}
|
|
39
|
+
{{ attr }}="{{ value }}"
|
|
40
|
+
{%- endif -%}
|
|
41
|
+
{%- endfor -%}
|
|
42
|
+
>
|
|
43
|
+
{%- for tab in tabs -%}
|
|
44
|
+
{#-
|
|
45
|
+
Append the tab index to the name to ensure uniqueness within the tabs
|
|
46
|
+
If any names are duplicated, the generated IDs would not be unique, causing tabs to control the wrong panels.
|
|
47
|
+
-#}
|
|
48
|
+
{%- set name = tab.get("name", "") + "-" + (loop.index | string) -%}
|
|
49
|
+
<div class="p-tabs__item">
|
|
50
|
+
<button
|
|
51
|
+
class="p-tabs__link"
|
|
52
|
+
role="tab"
|
|
53
|
+
aria-selected="{% if loop.first %}true{% else %}false{% endif %}"
|
|
54
|
+
aria-controls="{{ name }}-tab" id="{{ name }}"
|
|
55
|
+
>
|
|
56
|
+
{{ tab.get("tab_html", "") | trim | safe }}
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
{%- endfor -%}
|
|
60
|
+
</div>
|
|
61
|
+
{%- for tab in tabs -%}
|
|
62
|
+
{#-
|
|
63
|
+
Append the tab index to the name to ensure uniqueness within the tabs
|
|
64
|
+
If any names are duplicated, the generated IDs would not be unique, causing tabs to control the wrong panels.
|
|
65
|
+
-#}
|
|
66
|
+
{%- set name = tab.get("name", "") + "-" + (loop.index | string) -%}
|
|
67
|
+
<div
|
|
68
|
+
tabindex="0"
|
|
69
|
+
role="tabpanel"
|
|
70
|
+
id="{{ name }}-tab"
|
|
71
|
+
aria-labelledby="{{ name }}"
|
|
72
|
+
{% if not loop.first %}hidden="hidden"{% endif %}
|
|
73
|
+
>
|
|
74
|
+
{{ tab.get("content_html", "") | trim | safe }}
|
|
75
|
+
</div>
|
|
76
|
+
{%- endfor -%}
|
|
77
|
+
</div>
|
|
78
|
+
{%- endmacro -%}
|