diff --git a/_index.scss b/_index.scss index 19934b82..e9aa2dd6 100644 --- a/_index.scss +++ b/_index.scss @@ -1,3 +1,4 @@ @forward './sass/color'; @forward './sass/typography'; +@forward './sass/elevations'; @forward './sass/utils'; diff --git a/sass/_index.scss b/sass/_index.scss index 12432a40..ea7597ec 100644 --- a/sass/_index.scss +++ b/sass/_index.scss @@ -1,3 +1,4 @@ @forward 'color'; +@forward 'elevations'; @forward 'typography'; @forward 'utils'; diff --git a/sass/elevations/_functions.scss b/sass/elevations/_functions.scss new file mode 100644 index 00000000..1a64a798 --- /dev/null +++ b/sass/elevations/_functions.scss @@ -0,0 +1,87 @@ +@use 'sass:meta'; +@use 'sass:map'; +@use 'sass:list'; + +//// +/// @group elevations +//// + +$factor: var(--ig-elevation-factor, 1); + +/// Checks if the passed argument is a valid box-shadow. +/// @access public +/// @param {List} $shadow - A list of shadow properties. +/// @return {Boolean} - Returns a boolean depending on if the passed argument is a valid shadow. +@function _is-shadow($shadow) { + $lastIndex: list.length($shadow); + + @if type-of($shadow) != list { + @return false; + } + + @if $lastIndex < 2 or $lastIndex > 5 { + @return false; + } + + @if list.separator($shadow) != space { + @return false; + } + + @return true; +} + +/// Transforms the passed box-shadow list according to the elevation factor value. +/// @access public +/// @param {List} $shadow - A list of shadow properties. +/// @return {List} - The transformed shadow list. +@function _transform-shadow($shadow) { + $result: (); + + @each $value in $shadow { + @if meta.type-of($value) != color and $value != 0 and $value != inset { + $result: list.append($result, calc($factor * $value)); + } @else { + $result: list.append($result, $value); + } + } + + @return $result; +} + +/// Sorts out a list of valid only box-shadows. +/// @access public +/// @param {List} $shadows - A list of shadow values. +/// @return {List} - The transformed shadow list. +/// @requires {function} _is-shadow +/// @requires {function} _transform-shadow +@function box-shadow($shadows) { + $result: (); + $s: (); + + @if meta.type-of($shadows) != list { + @error 'shadow expects a list of box-shadow values. ex.: shadow((0 2px 2px black))'; + } + + @if list.separator($shadows) == space { + $s: list.append($s, $shadows); + } + + @if list.separator($shadows) == comma { + $s: $shadows; + } + + @each $shadow in $s { + @if _is-shadow($shadow) { + $result: list.append($result, _transform-shadow($shadow), $separator: comma); + } + } + + @return $result; +} + +/// Gets a CSS elevation variable by name. +/// @access public +/// @param {String} $name - The name of the shadow. +@function elevation($name) { + @return var(--ig-elevation-#{$name}); +} diff --git a/sass/elevations/_index.scss b/sass/elevations/_index.scss new file mode 100644 index 00000000..fcecd8c9 --- /dev/null +++ b/sass/elevations/_index.scss @@ -0,0 +1,2 @@ +@forward 'functions'; +@forward 'mixins'; diff --git a/sass/elevations/_mixins.scss b/sass/elevations/_mixins.scss new file mode 100644 index 00000000..f1c59ffc --- /dev/null +++ b/sass/elevations/_mixins.scss @@ -0,0 +1,34 @@ +@use '../utils' as *; + +//// +/// @group elevations +//// + +/// Generates CSS variables for a given elevations map. +/// @access public +/// @param {Map} $elevations - The elevations map to use to generate CSS variables. +/// +/// @example scss Generate CSS variables for elevations. +/// $elevations: ( +/// small: box-shadow(0 .125rem .25rem rgba(0 0 0 / 75%)), +/// medium: box-shadow(0 .25rem .5rem rgba(0 0 0 / 85%)), +/// large: box-shadow(0 .75rem 1rem rgba(0 0 0 / 95%)), +/// ); +/// @include elevations($elevations); +/// +@mixin elevations($elevations) { + $scope: if(is-root(), ':root', '&'); + + #{$scope} { + @each $level, $shadow in $elevations { + --ig-elevation-#{$level}: #{$shadow}; + } + } +} + +/// Includes box-shadow styles for an elevation by name +/// @access public +/// @param {String} $name - The name of the shadow. +@mixin elevation($name) { + box-shadow: var(--ig-elevation-#{$name}); +} diff --git a/sass/elevations/presets/_material.scss b/sass/elevations/presets/_material.scss new file mode 100644 index 00000000..ffc18cf5 --- /dev/null +++ b/sass/elevations/presets/_material.scss @@ -0,0 +1,47 @@ +@use '../index' as *; + +//// +/// @group elevations +/// @author Simeon Simeonoff +//// + +/// Level 1 - Umbra Shadows +/// @type {Color} $color-1 - The color used to generate umbra shadows. +$color-1: rgba(0 0 0 / 26%); + +/// Level 2 - Penumbra Shadows +/// @type {Color} $color-2 - The color used to generate penumbra shadows. +$color-2: rgba(0 0 0 / 12%); + +/// Level 3 - Ambient Shadows +/// @type {Color} $color-3 - The color used to generate ambient shadows. +$color-3: rgba(0 0 0 / 8%); + +/// @type {Map} $elevations - А map of 24 shadow elevations with the umbra, penumbra and ambient shadows. +$elevations: ( + 0: none, + 1: box-shadow((0 1px 3px 0 $color-1, 0 1px 1px 0 $color-2, 0 2px 1px -1px $color-3)), + 2: box-shadow((0 1px 5px 0 $color-1, 0 2px 2px 0 $color-2, 0 3px 1px -2px $color-3)), + 3: box-shadow((0 1px 8px 0 $color-1, 0 3px 4px 0 $color-2, 0 3px 3px -2px $color-3)), + 4: box-shadow((0 2px 4px -1px $color-1, 0 4px 5px 0 $color-2, 0 1px 10px 0 $color-3)), + 5: box-shadow((0 3px 5px -1px $color-1, 0 5px 8px 0 $color-2, 0 1px 14px 0 $color-3)), + 6: box-shadow((0 3px 5px -1px $color-1, 0 6px 10px 0 $color-2, 0 1px 18px 0 $color-3)), + 7: box-shadow((0 4px 5px -2px $color-1, 0 7px 10px 1px $color-2, 0 2px 16px 1px $color-3)), + 8: box-shadow((0 5px 5px -3px $color-1, 0 8px 10px 1px $color-2, 0 3px 14px 2px $color-3)), + 9: box-shadow((0 5px 6px -3px $color-1, 0 9px 12px 1px $color-2, 0 3px 16px 2px $color-3)), + 10: box-shadow((0 6px 6px -3px $color-1, 0 10px 14px 1px $color-2, 0 4px 18px 3px $color-3)), + 11: box-shadow((0 6px 7px -4px $color-1, 0 11px 15px 1px $color-2, 0 4px 20px 3px $color-3)), + 12: box-shadow((0 7px 8px -4px $color-1, 0 12px 17px 2px $color-2, 0 5px 22px 4px $color-3)), + 13: box-shadow((0 7px 8px -4px $color-1, 0 13px 19px 2px $color-2, 0 5px 24px 4px $color-3)), + 14: box-shadow((0 7px 9px -4px $color-1, 0 14px 21px 2px $color-2, 0 5px 26px 4px $color-3)), + 15: box-shadow((0 8px 9px -5px $color-1, 0 15px 22px 2px $color-2, 0 6px 28px 5px $color-3)), + 16: box-shadow((0 8px 10px -5px $color-1, 0 16px 24px 2px $color-2, 0 6px 30px 5px $color-3)), + 17: box-shadow((0 8px 11px -5px $color-1, 0 17px 26px 2px $color-2, 0 6px 32px 5px $color-3)), + 18: box-shadow((0 9px 11px -5px $color-1, 0 18px 28px 2px $color-2, 0 7px 34px 6px $color-3)), + 19: box-shadow((0 9px 12px -6px $color-1, 0 19px 29px 2px $color-2, 0 7px 36px 6px $color-3)), + 20: box-shadow((0 10px 13px -6px $color-1, 0 20px 31px 3px $color-2, 0 8px 38px 7px $color-3)), + 21: box-shadow((0 10px 13px -6px $color-1, 0 21px 33px 3px $color-2, 0 8px 40px 7px $color-3)), + 22: box-shadow((0 10px 14px -6px $color-1, 0 22px 35px 3px $color-2, 0 8px 42px 7px $color-3)), + 23: box-shadow((0 11px 14px -7px $color-1, 0 23px 36px 3px $color-2, 0 9px 44px 8px $color-3)), + 24: box-shadow((0 11px 15px -7px $color-1, 0 24px 38px 3px $color-2, 0 9px 46px 8px $color-3)) +); diff --git a/test/_elevations.spec.scss b/test/_elevations.spec.scss new file mode 100644 index 00000000..92a8931b --- /dev/null +++ b/test/_elevations.spec.scss @@ -0,0 +1,69 @@ +@use 'sass:meta'; +@use 'sass:list'; +@use '../node_modules/sass-true/' as *; +@use '../index' as *; + +@include describe('Elevations') { + @include it('should return an empty list for a list of non box-shadow values') { + $raw: (pink, yellow, 9px, 'black'); + $transformed: box-shadow($raw); + $expected: (); + + @include assert-equal($transformed, $expected); + } + + @include it('should produce a box shadow that reacts to CSS --ig-elevation-factor changes') { + $raw: (0 0 2px 3px black, 0 6px 9px orange); + $transformed: box-shadow($raw); + $expected: ( + 0 0 calc($factor * 2px) calc($factor * 3px) black, + 0 calc($factor * 6px) calc($factor * 9px) orange + ); + + @include assert-equal($transformed, $expected); + } + + @include it('should produce CSS variables for a map of shadows') { + $shadows: ( + small: box-shadow(0 .125rem .25rem rgba(0 0 0 / 75%)), + medium: box-shadow(0 .25rem .5rem rgba(0 0 0 / 85%)), + large: box-shadow(0 .75rem 1rem rgba(0 0 0 / 95%)), + ); + + @include assert() { + @include output() { + @include elevations($shadows); + } + + @include expect() { + @each $key, $value in $shadows { + --ig-elevation-#{$key}: #{$value}; + } + } + } + } + + @include it('should retrieve a CSS variable elevation by name') { + @include assert() { + @include output() { + box-shadow: elevation(small); + } + + @include expect() { + box-shadow: var(--ig-elevation-small); + } + } + } + + @include it('should include box-shadow styles for an elevation level') { + @include assert() { + @include output() { + @include elevation(small); + } + + @include expect() { + box-shadow: var(--ig-elevation-small); + } + } + } +} diff --git a/test/_index.scss b/test/_index.scss index df883586..f806a912 100644 --- a/test/_index.scss +++ b/test/_index.scss @@ -1,3 +1,4 @@ @use '../node_modules/sass-true/' as *; @use 'color.spec' as *; @use 'typography.spec' as *; +@use 'elevations.spec' as *; diff --git a/test/e2e/theme.scss b/test/e2e/theme.scss index 5b361409..9b16aa34 100644 --- a/test/e2e/theme.scss +++ b/test/e2e/theme.scss @@ -1,4 +1,5 @@ @use '../../index' as *; +@use '../../sass/elevations/presets/material' as *; $h1: (font-size: 96px); $h2: (font-size: 70px); @@ -34,3 +35,4 @@ $palette: palette($primary: #9fe, $secondary: orange, $surface: #fff); @include palette($palette); @include typography("'Roboto', sans-serif", $scale); +@include elevations($elevations);