diff --git a/packages/web/src/scss/components/Flex/_Flex.scss b/packages/web/src/scss/components/Flex/_Flex.scss index 64a2a76ee2..cb04fe3418 100644 --- a/packages/web/src/scss/components/Flex/_Flex.scss +++ b/packages/web/src/scss/components/Flex/_Flex.scss @@ -2,7 +2,6 @@ // 2. Use `display: grid` so the orientation of alignmentX and alignmentY does not change as it would with `flex-direction: column`. // 3. Generate both `wrap` and `noWrap` classes to enable mobile-first and breakpoint-specific wrapping at the same time. -@use 'sass:string'; @use '../../tools/breakpoint'; @use '../../tools/dictionaries'; @use '../../tools/reset'; @@ -12,8 +11,8 @@ .Flex { @include reset.list(); @include spacing.create( + $input-property-base-name: '--flex-spacing', $output-property-name: '--flex-gap', - $responsive-property-base-name: '--flex-spacing', $breakpoints: theme.$breakpoints, $default-spacing: theme.$gap ); @@ -26,7 +25,7 @@ // stylelint-disable-next-line selector-max-universal -- Let's be bold and tweak all direct descendants regardless of their type to avoid inheritance of spacing for nested Flex. .Flex > * { @include spacing.prevent-inheritance( - $responsive-property-base-name: '--flex-spacing', + $input-property-base-names: '--flex-spacing', $breakpoints: theme.$breakpoints ); } diff --git a/packages/web/src/scss/components/Grid/_Grid.scss b/packages/web/src/scss/components/Grid/_Grid.scss index 1c70fcdb78..bab6ff3292 100644 --- a/packages/web/src/scss/components/Grid/_Grid.scss +++ b/packages/web/src/scss/components/Grid/_Grid.scss @@ -1,7 +1,13 @@ -@use 'sass:map'; +// 1. For each breakpoint, generate: +// +// a) responsive alignment classes for both axes, +// b) `grid-template-columns` values for the equal-column Grid variant, +// c) responsive GridItem classes. + @use '../../tools/breakpoint'; +@use '../../tools/dictionaries'; @use '../../tools/reset'; -@use '../../tools/string' as spirit-string; +@use '../../tools/spacing'; @use 'theme'; @use 'tools'; @@ -11,39 +17,74 @@ width: 100%; @include reset.list(); - @include tools.spacings(theme.$grid-spacings, theme.$breakpoints); + @include spacing.create( + $input-property-base-name: '--grid-spacing-x', + $output-property-name: '--grid-column-gap', + $property: 'column-gap', + $breakpoints: theme.$breakpoints, + $default-spacing: theme.$grid-spacings + ); + @include spacing.create( + $input-property-base-name: '--grid-spacing-y', + $output-property-name: '--grid-row-gap', + $property: 'row-gap', + $breakpoints: theme.$breakpoints, + $default-spacing: theme.$grid-spacings + ); } // stylelint-disable-next-line selector-max-universal -- Let's be bold and reset spacing for all direct descendants regardless of their type to avoid inheritance of spacing for nested Grid. .Grid > * { - @each $breakpoint-name, $breakpoint-value in theme.$breakpoints { - @if $breakpoint-value > 0 { - --grid-spacing-x-#{$breakpoint-name}: initial; - --grid-spacing-y-#{$breakpoint-name}: initial; - } @else { - --grid-spacing-x: initial; - --grid-spacing-y: initial; - } - } + @include spacing.prevent-inheritance( + $input-property-base-names: ( + '--grid-spacing-x', + '--grid-spacing-y', + ), + $breakpoints: theme.$breakpoints + ); } +// 1. @each $breakpoint-name, $breakpoint-value in theme.$breakpoints { $infix: breakpoint.get-modifier('infix', $breakpoint-name, $breakpoint-value); @include breakpoint.up($breakpoint-value) { - @each $alignment-name, $alignment-value in theme.$alignments-x { - .Grid--#{$infix}#{spirit-string.convert-kebab-case-to-camel-case(alignment-x-#{$alignment-name})} { - justify-items: $alignment-value; - } - } + // 1.a + @include dictionaries.generate-alignments( + $class-name: 'Grid', + $dictionary-values: theme.$alignment-x-dictionary, + $axis: 'x', + $property: 'justify-items', + $infix: $infix + ); + @include dictionaries.generate-alignments( + $class-name: 'Grid', + $dictionary-values: theme.$alignment-y-dictionary, + $axis: 'y', + $infix: $infix + ); - @each $alignment-name, $alignment-value in theme.$alignments-y { - .Grid--#{$infix}#{spirit-string.convert-kebab-case-to-camel-case(alignment-y-#{$alignment-name})} { - align-items: $alignment-value; + // 1.b + @each $column in theme.$grid-equal-columns { + .Grid--#{breakpoint.get-modifier('infix', $breakpoint-name, $breakpoint-value)}cols-#{$column} { + grid-template-columns: repeat(#{$column}, 1fr); } } } } -@include tools.equal-columns(theme.$grid-equal-columns, theme.$breakpoints); +// 1.c @include tools.item(theme.$breakpoints); + +// stylelint-disable-next-line selector-max-universal -- Let's be bold and reset spacing for all direct descendants regardless of their type to avoid inheritance of spacing for nested GridItem. +.GridItem > * { + @include spacing.prevent-inheritance( + $input-property-base-names: ( + '--grid-item-column-start', + '--grid-item-column-end', + '--grid-item-row-start', + '--grid-item-row-end', + ), + $breakpoints: theme.$breakpoints + ); +} diff --git a/packages/web/src/scss/components/Grid/_theme.scss b/packages/web/src/scss/components/Grid/_theme.scss index 5bddd2413f..ca336d6755 100644 --- a/packages/web/src/scss/components/Grid/_theme.scss +++ b/packages/web/src/scss/components/Grid/_theme.scss @@ -2,18 +2,8 @@ @use '@tokens' as tokens; @use '../../settings/dictionaries'; -$alignments-x: ( - stretch: stretch, - left: start, - center: center, - right: end, -); -$alignments-y: ( - stretch: stretch, - top: start, - center: center, - bottom: end, -); +$alignment-x-dictionary: dictionaries.$alignments-x-extended; +$alignment-y-dictionary: dictionaries.$alignments-y-extended; $breakpoints: tokens.$breakpoints; diff --git a/packages/web/src/scss/components/Grid/_tools.scss b/packages/web/src/scss/components/Grid/_tools.scss index caa2c9d8e5..06676698fc 100644 --- a/packages/web/src/scss/components/Grid/_tools.scss +++ b/packages/web/src/scss/components/Grid/_tools.scss @@ -1,60 +1,5 @@ -@use 'sass:map'; -@use 'sass:string'; @use '../../tools/breakpoint'; -// Generates column-gap and row-gap values for each breakpoint -// Parameters are: -// * $spacings: the gaps map -// * $breakpoints: the breakpoints map -@mixin spacings($spacings, $breakpoints) { - $gap-fallback: map.get($spacings, 'mobile'); - - $column-property-name: '--grid-spacing-x'; - $row-property-name: '--grid-spacing-y'; - - $column-fallback: var(--grid-default-gap, #{$gap-fallback}); - $row-fallback: var(--grid-default-gap, #{$gap-fallback}); - - @each $breakpoint-name, $breakpoint-value in $breakpoints { - @if not map.has-key($spacings, $breakpoint-name) { - @error 'Invalid breakpoint specified! #{$breakpoint-name} doesn\'t exist. Use one of #{map.keys($breakpoints)}'; - } - - @if $breakpoint-value > 0 { - $column-property-name: --grid-spacing-x-#{$breakpoint-name}; - $row-property-name: --grid-spacing-y-#{$breakpoint-name}; - } - - @include breakpoint.up($breakpoint-value) { - --grid-default-gap: #{map.get($spacings, $breakpoint-name)}; - --grid-column-gap: var(#{string.unquote($column-property-name)}, #{$column-fallback}); - --grid-row-gap: var(#{string.unquote($row-property-name)}, #{$row-fallback}); - } - - $column-fallback: var(#{string.unquote($column-property-name)}, #{$column-fallback}); - $row-fallback: var(#{string.unquote($row-property-name)}, #{$row-fallback}); - } - - grid-column-gap: var(--grid-column-gap); - grid-row-gap: var(--grid-row-gap); -} - -// Generates grid-template-columns values for each breakpoint in equal columns Grid -// Parameters are: -// * $grid-equal-columns: the list of columns to generate -// * $breakpoints: the breakpoints map -@mixin equal-columns($column-count, $breakpoints) { - @each $breakpoint-name, $breakpoint-value in $breakpoints { - @include breakpoint.up($breakpoint-value) { - @each $column in $column-count { - .Grid--#{breakpoint.get-modifier('infix', $breakpoint-name, $breakpoint-value)}cols-#{$column} { - grid-template-columns: repeat(#{$column}, 1fr); - } - } - } - } -} - // Generates grid-column values for each breakpoint with fallbacks to the previous breakpoint. // Parameters are: // * $breakpoints: the breakpoints map @@ -81,15 +26,4 @@ } } } - - :where(.GridItem .GridItem) { - @each $breakpoint-name, $breakpoint-value in $breakpoints { - $suffix: breakpoint.get-modifier('suffix', $breakpoint-name, $breakpoint-value); - - --grid-item-column-start#{$suffix}: initial; - --grid-item-column-end#{$suffix}: initial; - --grid-item-row-start#{$suffix}: initial; - --grid-item-row-end#{$suffix}: initial; - } - } } diff --git a/packages/web/src/scss/components/Stack/_Stack.scss b/packages/web/src/scss/components/Stack/_Stack.scss index 3a8adf945b..c6f6225dc0 100644 --- a/packages/web/src/scss/components/Stack/_Stack.scss +++ b/packages/web/src/scss/components/Stack/_Stack.scss @@ -1,6 +1,3 @@ -@use 'sass:math'; -@use 'sass:string'; -@use '../../tools/breakpoint'; @use '../../tools/reset'; @use '../../tools/spacing'; @use 'theme'; @@ -13,8 +10,8 @@ .Stack--hasSpacing { @include spacing.create( + $input-property-base-name: '--stack-spacing', $output-property-name: '--stack-gap', - $responsive-property-base-name: '--stack-spacing', $breakpoints: theme.$breakpoints, $default-spacing: theme.$spacing-fallback ); @@ -37,7 +34,7 @@ .Stack--hasSpacing > * { @include spacing.prevent-inheritance( - $responsive-property-base-name: '--stack-spacing', + $input-property-base-names: '--stack-spacing', $breakpoints: theme.$breakpoints ); } diff --git a/packages/web/src/scss/tools/__tests__/_dictionaries.test.scss b/packages/web/src/scss/tools/__tests__/_dictionaries.test.scss index 65b226ab05..b8a7569749 100644 --- a/packages/web/src/scss/tools/__tests__/_dictionaries.test.scss +++ b/packages/web/src/scss/tools/__tests__/_dictionaries.test.scss @@ -28,16 +28,17 @@ @include dictionaries.generate-alignments( $class-name: 'Test', $dictionary-values: ( - 'space-between', + 'stretch', ), $axis: 'x', + $property: 'justify-items', $infix: 'tablet--' ); } @include test.expect() { - .Test--tablet--alignmentXSpaceBetween { - justify-content: space-between; + .Test--tablet--alignmentXStretch { + justify-items: stretch; } } } diff --git a/packages/web/src/scss/tools/__tests__/_spacing.test.scss b/packages/web/src/scss/tools/__tests__/_spacing.test.scss index 436a4409e6..8dff6badaf 100644 --- a/packages/web/src/scss/tools/__tests__/_spacing.test.scss +++ b/packages/web/src/scss/tools/__tests__/_spacing.test.scss @@ -7,8 +7,8 @@ @include test.output() { .test { @include spacing.create( + $input-property-base-name: '--test-input', $output-property-name: '--test-output', - $responsive-property-base-name: '--test-spacing', $breakpoints: ( 'mobile': 0, 'tablet': 768px, @@ -20,26 +20,64 @@ @include test.expect() { .test { - --test-output: var(--test-spacing, 16px); + --test-output: var(--test-input, 16px); gap: var(--test-output); } @media (width >= 768px) { .test { - --test-output: var(--test-spacing-tablet, var(--test-spacing, 16px)); + --test-output: var(--test-input-tablet, var(--test-input, 16px)); } } } } } - @include test.it('should set up correct prevention of nested spacing') { + @include test.it('should return correct spacing cascade with responsive default spacing') { + @include test.assert() { + @include test.output() { + .test { + @include spacing.create( + $input-property-base-name: '--test-input', + $output-property-name: '--test-output', + $property: 'column-gap', + $breakpoints: ( + 'mobile': 0, + 'tablet': 768px, + ), + $default-spacing: ( + 'mobile': 16px, + 'tablet': 32px, + ) + ); + } + } + + @include test.expect() { + .test { + --test-output-default: 16px; + --test-output: var(--test-input, var(--test-output-default, 16px)); + + column-gap: var(--test-output); + } + + @media (width >= 768px) { + .test { + --test-output-default: 32px; + --test-output: var(--test-input-tablet, var(--test-input, var(--test-output-default, 16px))); + } + } + } + } + } + + @include test.it('should set up correct prevention of spacing inheritance') { @include test.assert() { @include test.output() { .nested { @include spacing.prevent-inheritance( - $responsive-property-base-name: '--test-spacing', + $input-property-base-names: '--test-spacing', $breakpoints: ( 'mobile': 0, 'tablet': 768px, @@ -56,4 +94,32 @@ } } } + + @include test.it('should set up correct prevention of spacing inheritance with multiple properties') { + @include test.assert() { + @include test.output() { + .nested { + @include spacing.prevent-inheritance( + $input-property-base-names: ( + '--test-spacing-x', + '--test-spacing-y', + ), + $breakpoints: ( + 'mobile': 0, + 'tablet': 768px, + ) + ); + } + } + + @include test.expect() { + .nested { + --test-spacing-x: initial; + --test-spacing-y: initial; + --test-spacing-x-tablet: initial; + --test-spacing-y-tablet: initial; + } + } + } + } } diff --git a/packages/web/src/scss/tools/_dictionaries.scss b/packages/web/src/scss/tools/_dictionaries.scss index 0331ca8daf..da60a9d223 100644 --- a/packages/web/src/scss/tools/_dictionaries.scss +++ b/packages/web/src/scss/tools/_dictionaries.scss @@ -16,14 +16,15 @@ // * $class-name: the name of the component class to generate // * $dictionary-values: map of the dictionary values to generate // * $axis: the axis to generate, either 'x' or 'y' +// * $property: the CSS property to apply the alignment to (for the x-axis only) // * $infix: the infix to add to the class name -@mixin generate-alignments($class-name, $dictionary-values, $axis, $infix: '') { +@mixin generate-alignments($class-name, $dictionary-values, $axis, $property: 'justify-content', $infix: '') { $prefix: spirit-string.convert-pascal-case-to-kebab-case($class-name); @each $alignment in $dictionary-values { .#{$class-name}--#{$infix}#{spirit-string.convert-kebab-case-to-camel-case(alignment-#{$axis}-#{$alignment})} { @if $axis == 'x' { - justify-content: string.unquote(alignment.translate-physical-to-logical($alignment)); + #{$property}: string.unquote(alignment.translate-physical-to-logical($alignment)); } @else { align-items: string.unquote(alignment.translate-physical-to-logical($alignment)); } diff --git a/packages/web/src/scss/tools/_spacing.scss b/packages/web/src/scss/tools/_spacing.scss index eeb7695124..7a79fedd7f 100644 --- a/packages/web/src/scss/tools/_spacing.scss +++ b/packages/web/src/scss/tools/_spacing.scss @@ -1,26 +1,38 @@ +@use 'sass:map'; +@use 'sass:meta'; @use 'sass:string'; @use 'breakpoint'; // Mixin to generate responsive spacing // Parameters are: +// * $input-property-base-name: the base name of the responsive CSS custom property set via inline styles (API) // * $output-property-name: the name of the output CSS custom property consumed by CSS -// * $responsive-property-base-name: the base name of the responsive CSS custom property set via inline styles (API) +// * $property: the CSS property to apply the spacing to // * $breakpoints: the breakpoints map -// * $default-spacing: the default spacing value -@mixin create($output-property-name, $responsive-property-base-name, $breakpoints, $default-spacing: 0) { - $fallback: $default-spacing; - $new-property-name: $responsive-property-base-name; +// * $default-spacing: the default spacing value; CSS length or breakpoint-to-length map +@mixin create($input-property-base-name, $output-property-name, $property: 'gap', $breakpoints, $default-spacing: 0) { + $responsive-spacing-default-name: #{$output-property-name}-default; + $fallback: if( + meta.type-of($default-spacing) == 'map', + var(#{$responsive-spacing-default-name}, #{map.get($default-spacing, 'mobile')}), + $default-spacing + ); + $new-property-name: $input-property-base-name; @each $breakpoint-name, $breakpoint-value in $breakpoints { @if $breakpoint-value > 0 { - $new-property-name: '#{$responsive-property-base-name}-#{$breakpoint-name}'; + $new-property-name: '#{$input-property-base-name}-#{$breakpoint-name}'; } @include breakpoint.up($breakpoint-value) { + @if meta.type-of($default-spacing) == 'map' { + #{$responsive-spacing-default-name}: #{map.get($default-spacing, $breakpoint-name)}; + } + #{$output-property-name}: var(#{string.unquote($new-property-name)}, #{$fallback}); @if $breakpoint-value == 0 { - gap: var(#{$output-property-name}); + #{$property}: var(#{$output-property-name}); } } @@ -30,14 +42,16 @@ // Mixin to prevent inheritance of spacing // Parameters are: -// * $spacing-property-base-name: the base name of the spacing CSS custom property +// * $input-property-base-names: list of base names (i.e. without responsive suffix) of the CSS custom properties to reset // * $breakpoints: the breakpoints map -@mixin prevent-inheritance($responsive-property-base-name, $breakpoints) { +@mixin prevent-inheritance($input-property-base-names, $breakpoints) { @each $breakpoint-name, $breakpoint-value in $breakpoints { - @if $breakpoint-value > 0 { - #{$responsive-property-base-name}-#{$breakpoint-name}: initial; - } @else { - #{$responsive-property-base-name}: initial; + @each $property-base-name in $input-property-base-names { + @if $breakpoint-value > 0 { + #{$property-base-name}-#{$breakpoint-name}: initial; + } @else { + #{$property-base-name}: initial; + } } } }