diff --git a/package.json b/package.json index 9bf5c70e8..f3770cc26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vanilla-framework", - "version": "4.18.4", + "version": "4.19.0", "author": { "email": "webteam@canonical.com", "name": "Canonical Webteam" diff --git a/releases.yml b/releases.yml index 132e309c5..1251263a1 100644 --- a/releases.yml +++ b/releases.yml @@ -1,3 +1,9 @@ +- version: 4.19.0 + features: + - component: List + url: /docs/patterns/lists#horizontal-section-new + status: New + notes: We've introduced the .p-list--horizontal-section list variant. - version: 4.18.0 features: - component: Quote wrapper diff --git a/scss/_base_placeholders.scss b/scss/_base_placeholders.scss index be2a86404..f7d91a221 100644 --- a/scss/_base_placeholders.scss +++ b/scss/_base_placeholders.scss @@ -67,17 +67,6 @@ padding-left: 0; } - %vf-list-with-state-icons { - @extend %vf-list; - - .p-list__item { - &.is-ticked::before, - &.is-crossed::before { - top: $spv--medium; - } - } - } - // Bars and borders %vf-pseudo-border { background-color: $colors--theme--border-default; diff --git a/scss/_patterns_lists.scss b/scss/_patterns_lists.scss index 1a0f9aa07..f28c1846c 100644 --- a/scss/_patterns_lists.scss +++ b/scss/_patterns_lists.scss @@ -134,6 +134,13 @@ $list-step-bullet-margin: $sph--x-large; } } + %vf-list-item-with-divisor-common-properties { + box-shadow: inset 0px 1px 0px 0px $colors--theme--border-low-contrast; + margin: 0; + padding-bottom: $sph--large; + padding-top: $sph--small; + } + %vf-list-item-bullet { &::before { color: $colors--theme--text-default; @@ -149,6 +156,7 @@ $list-step-bullet-margin: $sph--x-large; position: relative; > .p-list--divided, + > .p-list--horizontal-section, > .p-list { margin-left: 0; } @@ -161,10 +169,7 @@ $list-step-bullet-margin: $sph--x-large; } @mixin vf-list-item-divided { - box-shadow: inset 0px 1px 0px 0px $colors--theme--border-low-contrast; - margin: 0; - padding-bottom: $sph--large; - padding-top: $sph--small; + @extend %vf-list-item-with-divisor-common-properties; &:first-child { box-shadow: none; @@ -175,6 +180,18 @@ $list-step-bullet-margin: $sph--x-large; } } +@mixin vf-list-item-horizontal-section { + @extend %vf-list-item-with-divisor-common-properties; +} + +// Offset a list item icon (tick or cross) away from the divisor above it +@mixin vf-list-item-icon-divisor-offset { + &.is-ticked::before, + &.is-crossed::before { + top: $spv--medium; + } +} + // Mixin for inline list items @mixin vf-inline-list-item { display: inline; @@ -199,64 +216,58 @@ $list-step-bullet-margin: $sph--x-large; } } -// todo this extraction may not be necessary but keeping it for now should the list items need more styling -@mixin list-horizontal-item-intrinsic-sizing-impl($num-cols) { - grid-column-end: span #{$num-cols}; -} +@mixin vf-p-list-horizontal-section { + .p-list--horizontal-section { + @extend %vf-grid-row; + @extend %vf-list; -/** - * Applies intrinsic sizing to horizontal list items by adjusting their grid column span based on the container's width. - * Requires that the parent container has `container-type` set to `inline-size` or `size`. - * @param {number} $min-width - The min width that the entire list should span, not including gutter adjustments - * @param {number} $num-cols - The number of columns the list item should span. - * @param {number} $gutter-width - The width of the gutter (space) between grid items. This is used to calculate the required width for the item. - * - * Example Usage: - * ```scss - * { - * @include list-horizontal-item-intrinsic-sizing(30, 4, 1rem); - * } - * ``` - */ -@mixin list-horizontal-item-intrinsic-sizing($min-width, $num-cols, $gutter-width) { - @if $min-width { - // Constrain to min width, taking gutters into account - @container (width > #{$min-width - ($num-cols * $gutter-width)}) { - @include list-horizontal-item-intrinsic-sizing-impl($num-cols); - } - } @else { - @include list-horizontal-item-intrinsic-sizing-impl($num-cols); - } -} + .p-list__item { + @extend %vf-list-item; + @include vf-list-item-icon-divisor-offset; + @include vf-list-item-horizontal-section; -@mixin vf-p-list-horizontal { - .p-list--horizontal { - @extend %vf-list-with-state-icons; - @extend %vf-grid-row; - container-type: inline-size; - } - .p-list--horizontal .p-list__item { - @extend %vf-list-item; - border-top: $border; - } + // Only apply custom grid colspans if there is more than one list item. + // If there's only one list item, it will span the full row width. + &:not(:only-child) { + grid-column-end: span $grid-8-columns-small; + + @media (min-width: $threshold-4-6-col) { + grid-column-end: span $grid-8-columns-medium / 2; + } - .p-list--horizontal.is-4-cols-on-large .p-list__item { - // note that the 4/4/8 grid will snap at different breakpoints than these list items - @include list-horizontal-item-intrinsic-sizing(null, $grid-8-columns-small, map-get($grid-gutter-widths, small)); - @include list-horizontal-item-intrinsic-sizing(40rem, $grid-8-columns-medium / 2, map-get($grid-gutter-widths, default)); - // Technically, this "large grid" rule is not needed because half of the medium grid and a quarter of the large grid are both 2 columns. - // @include list-horizontal-item-intrinsic-sizing(80, $grid-8-columns / 4, map-get($grid-gutter-widths, default)); + @media (min-width: $threshold-6-12-col) { + grid-column-end: span $grid-8-columns / 4; + } + } + } + } + .p-list--horizontal-section.is-25-75 { + .p-list__item { + // Skip the first two columns on large + @media (min-width: $threshold-6-12-col) { + &:nth-child(3n + 1) { + grid-column-start: 3; + } + &:nth-child(3n + 2) { + grid-column-start: 5; + } + &:nth-child(3n + 3) { + grid-column-start: 7; + } + } + } } } // A list with separators between items @mixin vf-p-list-divided { .p-list--divided { - @extend %vf-list-with-state-icons; + @extend %vf-list; .p-list__item { @extend %vf-list-item; @include vf-list-item-divided; + @include vf-list-item-icon-divisor-offset; } & & { @@ -271,7 +282,8 @@ $list-step-bullet-margin: $sph--x-large; } // stylelint-disable selector-max-type -- we want to target ordered lists - ol.p-list--divided { + ol.p-list--divided, + ol.p-list--horizontal-section { counter-reset: p-list-divided-counter; list-style: none; @@ -534,7 +546,7 @@ $list-step-bullet-margin: $sph--x-large; @include vf-p-list-placeholders; @include vf-p-list; - @include vf-p-list-horizontal; + @include vf-p-list-horizontal-section; @include vf-p-list-divided; @include vf-p-list-item-state; @include vf-p-inline-list; diff --git a/templates/docs/examples/patterns/lists/_list-horizontal-section-example.jinja b/templates/docs/examples/patterns/lists/_list-horizontal-section-example.jinja new file mode 100644 index 000000000..0b795ec68 --- /dev/null +++ b/templates/docs/examples/patterns/lists/_list-horizontal-section-example.jinja @@ -0,0 +1,51 @@ +{# + Generates an example of the horizontal section list pattern. + Params: + - list_item_type: The style of the list items (optional). Options are: "has-bullet", "is-ticked", "is-crossed", "ordered". + - list_variant_class: Modifier classes to apply to the list (optional). +#} +{% macro list_horizontal_section_example(list_item_type="", list_variant_class="") %} + {% set list_item_type = list_item_type | trim %} + {% set list_variant_class = list_variant_class | trim %} + + {% set list_classes = "p-list--horizontal-section" %} + {% if list_variant_class | length > 0 %} + {% set list_classes = list_classes + " " + list_variant_class %} + {% endif %} + + {% set list_tag = "ul" %} + {% if list_item_type == "ordered" %} + {% set list_tag = "ol" %} + {% set list_item_type = "" %} + {% endif %} + + {% set list_item_classes = "p-list__item" %} + {% if list_item_type | length > 0 %} + {% set list_item_classes = list_item_classes + " " + list_item_type %} + {% endif %} + + <{{ list_tag }} class="{{ list_classes }}"> +
  • + 10 year security maintenance and CVE Patching +
  • +
  • + Kernel Livepatch for 24/7 patching with no downtime +
  • +
  • + Expanded security for infrastructure and applications +
  • +
  • + FIPS 140-2 cryptographic modules certified by NIST +
  • +
  • + Common Criteria EAL2: ISO/IEC IS 15408 validated by CSEC +
  • +
  • + DISA/STIG hardening for DoD + compliance +
  • +
  • + CIS profiles for cyber defence and malware prevention +
  • + +{% endmacro %} \ No newline at end of file diff --git a/templates/docs/examples/patterns/lists/combined.html b/templates/docs/examples/patterns/lists/combined.html index fab19673b..374adebe5 100644 --- a/templates/docs/examples/patterns/lists/combined.html +++ b/templates/docs/examples/patterns/lists/combined.html @@ -26,5 +26,15 @@
    {% include 'docs/examples/patterns/lists/lists-stepped-heading-classes.html' %}
    {% include 'docs/examples/patterns/lists/lists-stepped-headings.html' %}
    {% include 'docs/examples/patterns/lists/lists-stepped-without-headings.html' %}
    +
    {% include 'docs/examples/patterns/lists/lists-horizontal-section-responsive.html' %}
    +
    {% include 'docs/examples/patterns/lists/lists-horizontal-section-responsive-bulleted.html' %}
    +
    {% include 'docs/examples/patterns/lists/lists-horizontal-section-responsive-crossed.html' %}
    +
    {% include 'docs/examples/patterns/lists/lists-horizontal-section-responsive-ordered.html' %}
    +
    {% include 'docs/examples/patterns/lists/lists-horizontal-section-responsive-ticked.html' %}
    +
    {% include 'docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive.html' %}
    +
    {% include 'docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-bulleted.html' %}
    +
    {% include 'docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-crossed.html' %}
    +
    {% include 'docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-ordered.html' %}
    +
    {% include 'docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-ticked.html' %}
    {% endwith %} {% endblock %} diff --git a/templates/docs/examples/patterns/lists/lists-dividers-bulleted.html b/templates/docs/examples/patterns/lists/lists-dividers-bulleted.html index bd4509249..eac995f41 100644 --- a/templates/docs/examples/patterns/lists/lists-dividers-bulleted.html +++ b/templates/docs/examples/patterns/lists/lists-dividers-bulleted.html @@ -1,5 +1,5 @@ {% extends "_layouts/examples.html" %} -{% block title %}Lists / Bulletted with horizontal divider{% endblock %} +{% block title %}Lists / Bulleted with horizontal divider{% endblock %} {% block standalone_css %}patterns_lists{% endblock %} diff --git a/templates/docs/examples/patterns/lists/lists-horizontal-4.html b/templates/docs/examples/patterns/lists/lists-horizontal-4.html deleted file mode 100644 index e1a1c5b98..000000000 --- a/templates/docs/examples/patterns/lists/lists-horizontal-4.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends "_layouts/examples.html" %} -{% block title %}Lists / Horizontal / 4 columns on large{% endblock %} - -{% block standalone_css %}patterns_lists{% endblock %} - -{% block content %} - -{% endblock %} diff --git a/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-bulleted.html b/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-bulleted.html new file mode 100644 index 000000000..e95eccbfa --- /dev/null +++ b/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-bulleted.html @@ -0,0 +1,9 @@ +{% extends "_layouts/examples.html" %} +{% block title %}Lists / Horizontal section / 25/75 / Bulleted{% endblock %} +{% from "docs/examples/patterns/lists/_list-horizontal-section-example.jinja" import list_horizontal_section_example %} + +{% block standalone_css %}patterns_lists{% endblock %} + +{% block content %} +{{ list_horizontal_section_example(list_item_type='has-bullet', list_variant_class='is-25-75') }} +{% endblock %} diff --git a/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-crossed.html b/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-crossed.html new file mode 100644 index 000000000..5c2798423 --- /dev/null +++ b/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-crossed.html @@ -0,0 +1,9 @@ +{% extends "_layouts/examples.html" %} +{% block title %}Lists / Horizontal section / 25/75 / Crossed{% endblock %} +{% from "docs/examples/patterns/lists/_list-horizontal-section-example.jinja" import list_horizontal_section_example %} + +{% block standalone_css %}patterns_lists{% endblock %} + +{% block content %} +{{ list_horizontal_section_example(list_item_type='is-crossed', list_variant_class='is-25-75') }} +{% endblock %} diff --git a/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-ordered.html b/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-ordered.html new file mode 100644 index 000000000..d32f16cef --- /dev/null +++ b/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-ordered.html @@ -0,0 +1,9 @@ +{% extends "_layouts/examples.html" %} +{% block title %}Lists / Horizontal section / 25/75 / Ordered{% endblock %} +{% from "docs/examples/patterns/lists/_list-horizontal-section-example.jinja" import list_horizontal_section_example %} + +{% block standalone_css %}patterns_lists{% endblock %} + +{% block content %} +{{ list_horizontal_section_example(list_item_type='ordered', list_variant_class='is-25-75') }} +{% endblock %} diff --git a/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-ticked.html b/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-ticked.html new file mode 100644 index 000000000..f482e3ff8 --- /dev/null +++ b/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive-ticked.html @@ -0,0 +1,9 @@ +{% extends "_layouts/examples.html" %} +{% block title %}Lists / Horizontal section / 25/75 / Ticked{% endblock %} +{% from "docs/examples/patterns/lists/_list-horizontal-section-example.jinja" import list_horizontal_section_example %} + +{% block standalone_css %}patterns_lists{% endblock %} + +{% block content %} +{{ list_horizontal_section_example(list_item_type='is-ticked', list_variant_class='is-25-75') }} +{% endblock %} diff --git a/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive.html b/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive.html new file mode 100644 index 000000000..48f69dc64 --- /dev/null +++ b/templates/docs/examples/patterns/lists/lists-horizontal-section-25-75-responsive.html @@ -0,0 +1,9 @@ +{% extends "_layouts/examples.html" %} +{% block title %}Lists / Horizontal section / 25/75{% endblock %} +{% from "docs/examples/patterns/lists/_list-horizontal-section-example.jinja" import list_horizontal_section_example %} + +{% block standalone_css %}patterns_lists{% endblock %} + +{% block content %} +{{ list_horizontal_section_example(list_variant_class='is-25-75') }} +{% endblock %} diff --git a/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive-bulleted.html b/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive-bulleted.html new file mode 100644 index 000000000..68dd50dbb --- /dev/null +++ b/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive-bulleted.html @@ -0,0 +1,9 @@ +{% extends "_layouts/examples.html" %} +{% block title %}Lists / Horizontal section / Bulleted{% endblock %} +{% from "docs/examples/patterns/lists/_list-horizontal-section-example.jinja" import list_horizontal_section_example %} + +{% block standalone_css %}patterns_lists{% endblock %} + +{% block content %} +{{ list_horizontal_section_example(list_item_type='has-bullet') }} +{% endblock %} diff --git a/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive-crossed.html b/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive-crossed.html new file mode 100644 index 000000000..47e67fcf2 --- /dev/null +++ b/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive-crossed.html @@ -0,0 +1,9 @@ +{% extends "_layouts/examples.html" %} +{% block title %}Lists / Horizontal section / Crossed{% endblock %} +{% from "docs/examples/patterns/lists/_list-horizontal-section-example.jinja" import list_horizontal_section_example %} + +{% block standalone_css %}patterns_lists{% endblock %} + +{% block content %} +{{ list_horizontal_section_example(list_item_type='is-crossed') }} +{% endblock %} diff --git a/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive-ordered.html b/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive-ordered.html new file mode 100644 index 000000000..a988c9eb7 --- /dev/null +++ b/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive-ordered.html @@ -0,0 +1,9 @@ +{% extends "_layouts/examples.html" %} +{% block title %}Lists / Horizontal section / Ordered{% endblock %} +{% from "docs/examples/patterns/lists/_list-horizontal-section-example.jinja" import list_horizontal_section_example %} + +{% block standalone_css %}patterns_lists{% endblock %} + +{% block content %} +{{ list_horizontal_section_example(list_item_type='ordered') }} +{% endblock %} diff --git a/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive-ticked.html b/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive-ticked.html new file mode 100644 index 000000000..45db6ae4c --- /dev/null +++ b/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive-ticked.html @@ -0,0 +1,9 @@ +{% extends "_layouts/examples.html" %} +{% block title %}Lists / Horizontal section / Ticked{% endblock %} +{% from "docs/examples/patterns/lists/_list-horizontal-section-example.jinja" import list_horizontal_section_example %} + +{% block standalone_css %}patterns_lists{% endblock %} + +{% block content %} +{{ list_horizontal_section_example(list_item_type='is-ticked') }} +{% endblock %} diff --git a/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive.html b/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive.html new file mode 100644 index 000000000..daa040337 --- /dev/null +++ b/templates/docs/examples/patterns/lists/lists-horizontal-section-responsive.html @@ -0,0 +1,9 @@ +{% extends "_layouts/examples.html" %} +{% block title %}Lists / Horizontal section{% endblock %} +{% from "docs/examples/patterns/lists/_list-horizontal-section-example.jinja" import list_horizontal_section_example %} + +{% block standalone_css %}patterns_lists{% endblock %} + +{% block content %} +{{ list_horizontal_section_example() }} +{% endblock %} diff --git a/templates/docs/patterns/lists/index.md b/templates/docs/patterns/lists/index.md index bcff1fc20..903be1e9d 100644 --- a/templates/docs/patterns/lists/index.md +++ b/templates/docs/patterns/lists/index.md @@ -163,6 +163,29 @@ If you wish to split the items in a list into two columns above `$breakpoint-sma View example of the patterns list split +## Horizontal section {{ status("new") }} + +To display a list of items that flow horizontally in a grid, use `.p-list--horizontal-section`. + +By default, the horizontal splits items 25/25/25/25 on large, 50/50 on medium and 100% on small screens. + +For optimal behaviour, you should use enough list items to fit at least 1 row on large screen size (4 items). + +
    +View example of the default horizontal list pattern +
    + +### 25/75 Horizontal section + +You can also add the `.is-25-75` modifier to reserve 25% space at the start of the list and place the remaining items in the remaining 75% space. +This is especially effective when a section heading precedes the list. + +For optimal behaviour, you should use enough list items to fit at least 1 row on large screen size (3 items). + +
    +View example of the horizontal list pattern in a 25/75 split +
    + ## Related components To separate non-list content with a responsive divider, see our [Divider component](/docs/patterns/divider). diff --git a/tests/snapshots.test.js b/tests/snapshots.test.js index 55e865b13..4c2e0bd5a 100644 --- a/tests/snapshots.test.js +++ b/tests/snapshots.test.js @@ -12,6 +12,7 @@ const RESPONSIVE_COMBINED_EXAMPLES = [ 'patterns/divider/combined', 'patterns/image/combined', 'patterns/equal-height-row/combined', + 'patterns/lists/combined', ]; test('Returns correct widths for snapshots, including additional breakpoint for responsive examples', async () => {