diff --git a/.changeset/ninety-hotels-jump.md b/.changeset/ninety-hotels-jump.md new file mode 100644 index 000000000..5af075ba2 --- /dev/null +++ b/.changeset/ninety-hotels-jump.md @@ -0,0 +1,5 @@ +--- +'@cloudfour/patterns': major +--- + +Use `clamp` instead of media queries to simplify fluid logic. diff --git a/src/base/_typography.scss b/src/base/_typography.scss index 4be32d1e6..1179d79e8 100644 --- a/src/base/_typography.scss +++ b/src/base/_typography.scss @@ -16,11 +16,11 @@ */ html { - @include fluid.font-size( - breakpoint.$xs, - breakpoint.$xl, + font-size: fluid.fluid-clamp( ms.step(0, 1rem), - ms.step(1, 1rem) + ms.step(1, 1rem), + breakpoint.$xs, + breakpoint.$xl ); /* 1 */ line-sizing: normal; /* 2 */ } diff --git a/src/components/card/card.scss b/src/components/card/card.scss index fb43fa4fa..d5b4320f5 100644 --- a/src/components/card/card.scss +++ b/src/components/card/card.scss @@ -42,29 +42,29 @@ $_focus-overflow: (size.$edge-large * -1); /** * The main card container * - * 1. We use `minmax(0, auto)` to prevent these rows from displaying in some + * 1. We define our column gap here instead of in the `c-card--horizontal` + * modifiers so we don't have to define it within multiple media queries. + * 2. We use `minmax(0, auto)` to prevent these rows from displaying in some * browsers even if their elements are nonexistent. - * 2. This allows our `c-card__link` pseudo element to position itself relative + * 3. This allows our `c-card__link` pseudo element to position itself relative * to this container. - * 3. We define our column gap here instead of in the `c-card--horizontal` - * modifiers so we don't have to define it within multiple media queries. */ .c-card { display: grid; + grid-column-gap: fluid.fluid-clamp( + size.$spacing-gap-fluid-min, + size.$spacing-gap-fluid-max, + breakpoint.$s, + breakpoint.$xl + ); /* 1 */ grid-template-areas: 'cover' 'header' 'content' 'footer'; - grid-template-rows: minmax(0, auto) minmax(0, auto) 1fr minmax(0, auto); /* 1 */ - position: relative; /* 2 */ - @include fluid.grid-column-gap( - breakpoint.$s, - breakpoint.$xl, - size.$spacing-gap-fluid-min, - size.$spacing-gap-fluid-max - ); /* 3 */ + grid-template-rows: minmax(0, auto) minmax(0, auto) 1fr minmax(0, auto); /* 2 */ + position: relative; /* 3 */ } .c-card--contained { diff --git a/src/components/sky-nav/sky-nav.scss b/src/components/sky-nav/sky-nav.scss index c8e33eddc..69ced8113 100644 --- a/src/components/sky-nav/sky-nav.scss +++ b/src/components/sky-nav/sky-nav.scss @@ -56,13 +56,14 @@ $_masthead-height-sm: ms.step(7); /** * Fluidly add more vertical whitespace as the viewport width increases */ - @include fluid.padding-block( - $_breakpoint-wide, - $_breakpoint-grow-max, - $_grow-vertical-min, - $_grow-vertical-max, - false - ); + @media (min-width: $_breakpoint-wide) { + padding-block: fluid.fluid-clamp( + $_grow-vertical-min, + $_grow-vertical-max, + $_breakpoint-wide, + $_breakpoint-grow-max + ); + } } /** @@ -184,10 +185,10 @@ $_masthead-height-sm: ms.step(7); transition: opacity transition.$slow ease.$out, transform transition.$quick ease.$out; width: fluid.fluid-calc( - breakpoint.$s, - breakpoint.$l, ms.step(7), - ms.step(9) /* 3 */ + ms.step(9), + /* 3 */ breakpoint.$s, + breakpoint.$l ); @media (min-width: breakpoint.$l) { width: ms.step(8); diff --git a/src/mixins/_fluid.scss b/src/mixins/_fluid.scss index c7bb4246a..509938072 100644 --- a/src/mixins/_fluid.scss +++ b/src/mixins/_fluid.scss @@ -1,174 +1,66 @@ @use 'unit'; - -/** - * Mixins and functions for dynamically adjusting a CSS property from a minimum - * value to a maximum, starting at a minimum breakpoint width and capping at a - * maximum breakpoint. - * - * Similar to Bootstrap's RFS, except it's mobile-first and only contains the - * logic we need. - * - * @see https://blog.typekit.com/2016/08/17/flexible-typography-with-css-locks/ - * @see https://betterwebtype.com/articles/2019/05/14/the-state-of-fluid-web-typography/ - * @see https://github.com/twbs/rfs - */ - -/** - * Although the `calc` statement is only part of the equation, breaking it into - * its own function makes it easier to add more fluid mixins in the future. - * - * 1. `$min-width` and `$max-width` should be in `em` units to avoid cross- - * browser inconsistencies with `rem` units in some browsers. But we need to - * convert the `min-width` value to `rem` so the `calc` won't be influenced - * by the current `font-size`, which would cause the fluid transformation to - * fall out of step with the viewport. - * - * @see https://zellwk.com/blog/media-query-units/ - */ - -@function fluid-calc($min-width, $max-width, $min, $max) { +@use '../compiled/tokens/scss/breakpoint'; + +/// This file contains functions for dynamically adjusting a CSS value from a +/// minimum amount to a maximum, starting from a minimum breakpoint width and +/// capping at a maximum width. +/// +/// This is similar to Bootstrap's RFS, except that it's mobile-first and uses +/// the `clamp` function so as not to rely on media queries. +/// +/// @link https://blog.typekit.com/2016/08/17/flexible-typography-with-css-locks/ +/// @link https://betterwebtype.com/articles/2019/05/14/the-state-of-fluid-web-typography/ +/// @link https://github.com/twbs/rfs + +/// Generate a fluid `calc` function. Note that this won't cap the value on its +/// own: In most cases, you will want `fluid-clamp` instead. +/// +/// @link https://zellwk.com/blog/media-query-units/ +/// @param {Number} $min - The minimum amount. +/// @param {Number} $max - The maximum amount. +/// @param {Number} $min-width [breakpoint.$s] - The minimum viewport width in ems. +/// @param {Number} $max-width [breakpoint.$xl] - The maximum viewport width in ems. +/// @return CSS calc function. +@function fluid-calc( + $min, + $max, + $min-width: breakpoint.$s, + $max-width: breakpoint.$xl +) { $delta: unit.strip($max - $min); $delta-width: unit.strip($max-width - $min-width); - $min-width-rem: unit.swap($min-width, rem); /* 1 */ + // We need to convert the `min-width` value to `rem` so the `calc` won't be + // influenced by the current `font-size`, which would cause the fluid + // transformation to fall out of step with the viewport. + $min-width-rem: unit.swap($min-width, rem); @return calc( #{$min} + #{$delta} * ((100vw - #{$min-width-rem}) / #{$delta-width}) ); } -/** - * Fluid properties - */ - -@mixin column-gap($min-width, $max-width, $min, $max, $include-min: true) { - @if $include-min { - column-gap: $min; - } - - @media (min-width: $min-width) { - column-gap: fluid-calc($min-width, $max-width, $min, $max); - } - - @media (min-width: $max-width) { - column-gap: $max; - } -} - -@mixin font-size($min-width, $max-width, $min, $max) { - font-size: $min; - - @media (min-width: $min-width) { - font-size: fluid-calc($min-width, $max-width, $min, $max); - } - - @media (min-width: $max-width) { - font-size: $max; - } -} - -@mixin grid-gap($min-width, $max-width, $min, $max, $include-min: true) { - @if $include-min { - grid-gap: $min; - } - - @media (min-width: $min-width) { - grid-gap: fluid-calc($min-width, $max-width, $min, $max); - } - - @media (min-width: $max-width) { - grid-gap: $max; - } -} - -@mixin grid-column-gap($min-width, $max-width, $min, $max, $include-min: true) { - @if $include-min { - grid-column-gap: $min; - } - - @media (min-width: $min-width) { - grid-column-gap: fluid-calc($min-width, $max-width, $min, $max); - } - - @media (min-width: $max-width) { - grid-column-gap: $max; - } -} - -@mixin grid-row-gap($min-width, $max-width, $min, $max, $include-min: true) { - @if $include-min { - grid-row-gap: $min; - } - - @media (min-width: $min-width) { - grid-row-gap: fluid-calc($min-width, $max-width, $min, $max); - } - - @media (min-width: $max-width) { - grid-row-gap: $max; - } -} - -@mixin margin-inline($min-width, $max-width, $min, $max, $include-min: true) { - @if $include-min { - margin-inline-end: $min; - margin-inline-start: $min; - } - - @media (min-width: $min-width) { - margin-inline-end: fluid-calc($min-width, $max-width, $min, $max); - margin-inline-start: fluid-calc($min-width, $max-width, $min, $max); - } - - @media (min-width: $max-width) { - margin-inline-end: $max; - margin-inline-start: $max; - } -} - -@mixin padding($min-width, $max-width, $min, $max, $include-min: true) { - @if $include-min { - padding: $min; - } - - @media (min-width: $min-width) { - padding: fluid-calc($min-width, $max-width, $min, $max); - } - - @media (min-width: $max-width) { - padding: $max; - } -} - -@mixin padding-block($min-width, $max-width, $min, $max, $include-min: true) { - @if $include-min { - padding-block-end: $min; - padding-block-start: $min; - } - - @media (min-width: $min-width) { - padding-block-end: fluid-calc($min-width, $max-width, $min, $max); - padding-block-start: fluid-calc($min-width, $max-width, $min, $max); - } - - @media (min-width: $max-width) { - padding-block-end: $max; - padding-block-start: $max; - } -} - -@mixin padding-inline($min-width, $max-width, $min, $max, $include-min: true) { - @if $include-min { - padding-inline-end: $min; - padding-inline-start: $min; - } - - @media (min-width: $min-width) { - padding-inline-end: fluid-calc($min-width, $max-width, $min, $max); - padding-inline-start: fluid-calc($min-width, $max-width, $min, $max); - } - - @media (min-width: $max-width) { - padding-inline-end: $max; - padding-inline-start: $max; - } +/// Generate a fluid `clamp` function. Unlike `fluid-calc`, this will prevent +/// the value from going below `$min` or above `$max`. +/// +/// @param {Number} $min - The minimum amount. +/// @param {Number} $max - The maximum amount. +/// @param {Number} $min-width [breakpoint.$s] - The minimum viewport width in ems. +/// @param {Number} $max-width [breakpoint.$xl] - The maximum viewport width in ems. +/// @return CSS clamp function. +@function fluid-clamp( + $min, + $max, + $min-width: breakpoint.$s, + $max-width: breakpoint.$xl +) { + $val: fluid-calc($min, $max, $min-width, $max-width); + + // Negative ranges are useful for fluid negation, such as negative padding + // that breaks out of a container's padding. To support this, we need to swap + // $min and $max when $min is greater. + @if $min > $max { + @return clamp(#{$max}, #{$val}, #{$min}); + } + + @return clamp(#{$min}, #{$val}, #{$max}); } diff --git a/src/mixins/_headings.scss b/src/mixins/_headings.scss index 980efd51b..919739c65 100644 --- a/src/mixins/_headings.scss +++ b/src/mixins/_headings.scss @@ -23,11 +23,11 @@ $size-tokens: meta.module-variables('size'); // If tokens were found... @if $min-size and $max-size { // Output a fluid font size - @include fluid.font-size( - breakpoint.$xs, - breakpoint.$l, + font-size: fluid.fluid-clamp( $min-size, - $max-size + $max-size, + breakpoint.$xs, + breakpoint.$l ); // Line heights above standard heading levels should be tighter diff --git a/src/objects/bio/bio.scss b/src/objects/bio/bio.scss index 01f359512..dd9fdd43f 100644 --- a/src/objects/bio/bio.scss +++ b/src/objects/bio/bio.scss @@ -4,17 +4,17 @@ .o-bio { display: grid; + grid-gap: fluid.fluid-clamp( + size.$spacing-gap-fluid-min, + size.$spacing-gap-fluid-max, + breakpoint.$s, + breakpoint.$xl + ); grid-template-areas: 'avatar' 'content' 'meta'; grid-template-rows: repeat(3, minmax(0, auto)); - @include fluid.grid-gap( - breakpoint.$s, - breakpoint.$xl, - size.$spacing-gap-fluid-min, - size.$spacing-gap-fluid-max - ); @media (min-width: breakpoint.$l) { grid-template-areas: diff --git a/src/objects/container/container.scss b/src/objects/container/container.scss index 1bcaa34e3..a7f367afd 100644 --- a/src/objects/container/container.scss +++ b/src/objects/container/container.scss @@ -18,29 +18,29 @@ $pad-max: size.$padding-container-max; */ .o-container--pad { - @include fluid.padding( - $pad-breakpoint-min, - $pad-breakpoint-max, + padding: fluid.fluid-clamp( $pad-min, - $pad-max + $pad-max, + $pad-breakpoint-min, + $pad-breakpoint-max ); } .o-container--pad-block { - @include fluid.padding-block( - $pad-breakpoint-min, - $pad-breakpoint-max, + padding-block: fluid.fluid-clamp( $pad-min, - $pad-max + $pad-max, + $pad-breakpoint-min, + $pad-breakpoint-max ); } .o-container--pad-inline { - @include fluid.padding-inline( - $pad-breakpoint-min, - $pad-breakpoint-max, + padding-inline: fluid.fluid-clamp( $pad-min, - $pad-max + $pad-max, + $pad-breakpoint-min, + $pad-breakpoint-max ); } @@ -72,11 +72,11 @@ $pad-max: size.$padding-container-max; .o-container__fill-pad { .o-container--pad &, .o-container--pad-inline & { - @include fluid.margin-inline( - $pad-breakpoint-min, - $pad-breakpoint-max, + margin-inline: fluid.fluid-clamp( $pad-min * -1, - $pad-max * -1 + $pad-max * -1, + $pad-breakpoint-min, + $pad-breakpoint-max ); } } @@ -91,11 +91,11 @@ $pad-max: size.$padding-container-max; .o-container__fill-pad { .o-container--pad &, .o-container--pad-inline & { - @include fluid.padding-inline( - $pad-breakpoint-min, - $pad-breakpoint-max, + padding-inline: fluid.fluid-clamp( $pad-min, - $pad-max + $pad-max, + $pad-breakpoint-min, + $pad-breakpoint-max ); } } diff --git a/src/objects/deck/deck.scss b/src/objects/deck/deck.scss index 73a3b0987..702b4ab7c 100644 --- a/src/objects/deck/deck.scss +++ b/src/objects/deck/deck.scss @@ -14,11 +14,11 @@ .o-deck { display: grid; grid-auto-flow: dense; /* 1 */ - @include fluid.grid-gap( - breakpoint.$s, - breakpoint.$xl, + grid-gap: fluid.fluid-clamp( size.$spacing-gap-fluid-min, - size.$spacing-gap-fluid-max + size.$spacing-gap-fluid-max, + breakpoint.$s, + breakpoint.$xl ); /** diff --git a/src/objects/feature-group/feature-group.scss b/src/objects/feature-group/feature-group.scss index 463e04be1..fa4b2da96 100644 --- a/src/objects/feature-group/feature-group.scss +++ b/src/objects/feature-group/feature-group.scss @@ -6,17 +6,17 @@ .o-feature-group { display: grid; + grid-gap: fluid.fluid-clamp( + size.$spacing-gap-fluid-min, + size.$spacing-gap-fluid-max, + breakpoint.$s, + breakpoint.$xl + ); grid-template-areas: 'intro' 'content' 'action'; grid-template-rows: repeat(3, minmax(0, auto)); - @include fluid.grid-gap( - breakpoint.$s, - breakpoint.$xl, - size.$spacing-gap-fluid-min, - size.$spacing-gap-fluid-max - ); @media (min-width: breakpoint.$l) { grid-row-gap: 0; diff --git a/src/objects/list/list.scss b/src/objects/list/list.scss index 30cbc9682..e32357427 100644 --- a/src/objects/list/list.scss +++ b/src/objects/list/list.scss @@ -47,13 +47,13 @@ @for $i from 2 through 3 { .o-list--#{$i}-column { @include media-query.breakpoint-classes($from: s, $to: xl) { - columns: #{$i}; - @include fluid.column-gap( - breakpoint.$s, - breakpoint.$xl, + column-gap: fluid.fluid-clamp( size.$spacing-gap-fluid-min, - size.$spacing-gap-fluid-max + size.$spacing-gap-fluid-max, + breakpoint.$s, + breakpoint.$xl ); + columns: #{$i}; } } } diff --git a/src/objects/overview/overview.scss b/src/objects/overview/overview.scss index 745f04b4a..8a2001751 100644 --- a/src/objects/overview/overview.scss +++ b/src/objects/overview/overview.scss @@ -20,14 +20,13 @@ @supports (display: grid) { @media (min-width: breakpoint.$l) { .o-overview { - @include fluid.column-gap( - breakpoint.$s, - breakpoint.$xl, + align-items: center; + column-gap: fluid.fluid-clamp( size.$spacing-gap-fluid-min, - size.$spacing-gap-fluid-max + size.$spacing-gap-fluid-max, + breakpoint.$s, + breakpoint.$xl ); - - align-items: center; display: grid; grid-template-areas: 'header header actions' diff --git a/src/prototypes/article-listing/example/example.scss b/src/prototypes/article-listing/example/example.scss index e20702910..1074f666c 100644 --- a/src/prototypes/article-listing/example/example.scss +++ b/src/prototypes/article-listing/example/example.scss @@ -22,11 +22,11 @@ .section { display: grid; grid-auto-flow: dense; - @include fluid.grid-gap( - breakpoint.$s, - breakpoint.$xl, + grid-gap: fluid.fluid-clamp( size.$spacing-gap-fluid-min, - size.$spacing-gap-fluid-max + size.$spacing-gap-fluid-max, + breakpoint.$s, + breakpoint.$xl ); @media (min-width: breakpoint.$m) {