From 4e260ee2f2817b9a547dae135ab5ac707bfbbd1b Mon Sep 17 00:00:00 2001 From: xelaint Date: Fri, 14 Aug 2026 10:16:02 -0400 Subject: [PATCH 1/3] feat(design): create `daff-color-shift` function --- .../scss/theming/_configure-palette.scss | 6 +- libs/design/scss/theming/_index.scss | 3 +- .../theming/color-shift/_color-shift.scss | 56 +++++++++ .../color-shift/_color-shift.spec.scss | 107 ++++++++++++++++++ .../scss/theming/color-shift/_index.scss | 1 + 5 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 libs/design/scss/theming/color-shift/_color-shift.scss create mode 100644 libs/design/scss/theming/color-shift/_color-shift.spec.scss create mode 100644 libs/design/scss/theming/color-shift/_index.scss diff --git a/libs/design/scss/theming/_configure-palette.scss b/libs/design/scss/theming/_configure-palette.scss index 0171fdb14b..a1a573585c 100644 --- a/libs/design/scss/theming/_configure-palette.scss +++ b/libs/design/scss/theming/_configure-palette.scss @@ -7,6 +7,9 @@ /// as the palette’s default color. The palette can then be used /// to configure themes by setting them as primary, secondary, or tertiary colors. /// +/// The hue is recorded alongside the color as `default-hue`, so that colors can +/// be selected relative to the default with `daff-color-shift`. +/// /// @group Theming /// /// @param {Map} $color-palette — A color palette map (e.g., `$daff-blue`, `$daff-purple`). @@ -21,7 +24,8 @@ @return map.merge( $color-palette, ( - 'default': get-color.daff-color($color-palette, $hue) + 'default': get-color.daff-color($color-palette, $hue), + 'default-hue': $hue ) ); } diff --git a/libs/design/scss/theming/_index.scss b/libs/design/scss/theming/_index.scss index 11003560c0..9b808cf00d 100644 --- a/libs/design/scss/theming/_index.scss +++ b/libs/design/scss/theming/_index.scss @@ -1,8 +1,9 @@ @forward 'color-palettes'; @forward 'get-color'; +@forward 'color-shift'; @forward 'contrast'; @forward 'configure-palette'; -@forward 'create-theme/create-theme'; +@forward 'create-theme'; @forward 'light-dark'; @forward 'get-palette'; @forward 'get-base-color'; diff --git a/libs/design/scss/theming/color-shift/_color-shift.scss b/libs/design/scss/theming/color-shift/_color-shift.scss new file mode 100644 index 0000000000..50473716b4 --- /dev/null +++ b/libs/design/scss/theming/color-shift/_color-shift.scss @@ -0,0 +1,56 @@ +// stylelint-disable @stylistic/max-line-length +@use 'sass:map'; +@use 'get-color'; +@use '../core/error/error-to-string'; + +/// @access private +/// The distance between two adjacent hues in a color palette. +$hue-step: 10; + +/// Retrieves a color relative to a palette's default hue. +/// +/// Positive steps move toward the darker end of the palette, negative steps +/// toward the lighter end. Use this instead of a hardcoded hue when a color is +/// meant to sit a fixed distance from a palette's default, such as the hover +/// and active states of a component. +/// +/// The palette must be configured with `daff-configure-palette`, which records +/// the hue that the default was set from. +/// +/// @group Theming +/// +/// @param {Map} $palette — The palette to retrieve a color from. +/// @param {Number} $steps [0] — How many hue steps to shift from the default. +/// +/// @return {Color} The color `$steps` away from the palette's default hue. +/// +/// @throws Will throw an error if `$palette` was not configured with +/// `daff-configure-palette`, or if the shifted hue does not exist in `$palette`. +/// +/// @example scss +/// // $primary configured at hue 60 +/// .custom-content { +/// background-color: daff-color($primary); // hue 60 +/// border-color: daff-color-shift($primary, 1); // hue 70 +/// color: daff-color-shift($primary, -1); // hue 50 +/// } +/// +@function daff-color-shift($palette, $steps: 0) { + $default-hue: map.get($palette, 'default-hue'); + + @if ($default-hue == null) { + @return error-to-string.error-to-string( + 'Palette has no `default-hue`. Configure it with `daff-configure-palette`.' + ); + } + + $hue: $default-hue + ($steps * $hue-step); + + @if (not map.has-key($palette, $hue)) { + @return error-to-string.error-to-string( + 'Cannot shift `#{$steps}` step(s) from hue `#{$default-hue}`. `#{$hue}` does not exist in palette.' + ); + } + + @return get-color.daff-color($palette, $hue); +} diff --git a/libs/design/scss/theming/color-shift/_color-shift.spec.scss b/libs/design/scss/theming/color-shift/_color-shift.spec.scss new file mode 100644 index 0000000000..c6619d3b04 --- /dev/null +++ b/libs/design/scss/theming/color-shift/_color-shift.spec.scss @@ -0,0 +1,107 @@ +@use 'true' as *; +@use 'sass:map'; +@use './color-shift' as *; +@use './color-palettes' as palette; +@use './configure-palette' as *; +@use './get-color' as *; +@use '../core/error/error-to-string'; + +@include describe('daff-color-shift') { + $default: daff-configure-palette(palette.$daff-blue); + $light: daff-configure-palette(palette.$daff-blue, 30); + $dark: daff-configure-palette(palette.$daff-blue, 50); + $lightest: daff-configure-palette(palette.$daff-blue, 10); + $darkest: daff-configure-palette(palette.$daff-blue, 100); + + @include it('returns the default color when there is no shift') { + @include assert-equal(daff-color-shift($default), daff-color($default)); + @include assert-equal(daff-color-shift($default, 0), daff-color($default, 60)); + } + + @include it('returns a darker color for positive steps') { + @include assert-equal(daff-color-shift($default, 1), daff-color($default, 70)); + @include assert-equal(daff-color-shift($default, 2), daff-color($default, 80)); + } + + @include it('returns a lighter color for negative steps') { + @include assert-equal(daff-color-shift($dark, -1), daff-color($dark, 40)); + @include assert-equal(daff-color-shift($dark, -2), daff-color($dark, 30)); + } + + @include it('shifts relative to the hue the palette was configured with') { + @include assert-equal(daff-color-shift($light, 1), daff-color($light, 40)); + @include assert-equal(daff-color-shift($light, 2), daff-color($light, 50)); + } + + @include it('shifts across the whole palette') { + @include assert-equal(daff-color-shift($lightest, 9), daff-color($lightest, 100)); + @include assert-equal(daff-color-shift($darkest, -9), daff-color($darkest, 10)); + } + + @include it('shifts relative to the configured hue, not a duplicate color earlier in the palette') { + $duplicate: daff-configure-palette((10: #ffffff, 20: #cccccc, 30: #cccccc, 40: #999999), 30); + + @include assert-equal(daff-color-shift($duplicate, 1), #999999); + } + + @include error-to-string.set-use-string(true); + + @include it('errors when the shift falls past the dark end of the palette') { + @include assert-equal( + daff-color-shift($darkest, 1), + 'Cannot shift `1` step(s) from hue `100`. `110` does not exist in palette.' + ); + } + + @include it('errors when the shift falls past the light end of the palette') { + @include assert-equal( + daff-color-shift($lightest, -1), + 'Cannot shift `-1` step(s) from hue `10`. `0` does not exist in palette.' + ); + } + + @include it('errors when the shift lands between hues') { + @include assert-equal( + daff-color-shift($default, 0.5), + 'Cannot shift `0.5` step(s) from hue `60`. `65` does not exist in palette.' + ); + } + + @include it('errors when the palette was not configured with daff-configure-palette') { + @include assert-equal( + daff-color-shift(palette.$daff-blue, 1), + 'Palette has no `default-hue`. Configure it with `daff-configure-palette`.' + ); + } + + @include it('errors when the palette has a default color but no default hue') { + $manual: map.merge(palette.$daff-blue, ('default': daff-color(palette.$daff-blue, 60))); + + @include assert-equal( + daff-color-shift($manual, 1), + 'Palette has no `default-hue`. Configure it with `daff-configure-palette`.' + ); + } + + @include error-to-string.set-use-string(false); +} + +@include describe('daff-configure-palette') { + @include it('records the hue the default was configured from') { + @include assert-equal( + map.get(daff-configure-palette(palette.$daff-blue), 'default-hue'), + 60 + ); + @include assert-equal( + map.get(daff-configure-palette(palette.$daff-blue, 30), 'default-hue'), + 30 + ); + } + + @include it('replaces the recorded hue when a palette is reconfigured') { + $reconfigured: daff-configure-palette(daff-configure-palette(palette.$daff-blue, 30), 80); + + @include assert-equal(map.get($reconfigured, 'default-hue'), 80); + @include assert-equal(map.get($reconfigured, 'default'), daff-color(palette.$daff-blue, 80)); + } +} diff --git a/libs/design/scss/theming/color-shift/_index.scss b/libs/design/scss/theming/color-shift/_index.scss new file mode 100644 index 0000000000..92e84fa7af --- /dev/null +++ b/libs/design/scss/theming/color-shift/_index.scss @@ -0,0 +1 @@ +@forward 'color-shift'; From 694a7b9219252ff5e2fa41cedbd09a91e2bd90ae Mon Sep 17 00:00:00 2001 From: xelaint Date: Fri, 14 Aug 2026 10:27:06 -0400 Subject: [PATCH 2/3] fix file path --- libs/design/scss/theming/color-shift/_color-shift.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/design/scss/theming/color-shift/_color-shift.scss b/libs/design/scss/theming/color-shift/_color-shift.scss index 50473716b4..44bf6aafdf 100644 --- a/libs/design/scss/theming/color-shift/_color-shift.scss +++ b/libs/design/scss/theming/color-shift/_color-shift.scss @@ -1,7 +1,7 @@ // stylelint-disable @stylistic/max-line-length @use 'sass:map'; -@use 'get-color'; -@use '../core/error/error-to-string'; +@use '../get-color'; +@use '../../core/error/error-to-string'; /// @access private /// The distance between two adjacent hues in a color palette. From 17b91bfdafb1d7a09a0dc321da247b469f269f39 Mon Sep 17 00:00:00 2001 From: xelaint Date: Mon, 17 Aug 2026 09:09:38 -0400 Subject: [PATCH 3/3] fix import paths --- .../scss/theming/color-shift/_color-shift.spec.scss | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/design/scss/theming/color-shift/_color-shift.spec.scss b/libs/design/scss/theming/color-shift/_color-shift.spec.scss index c6619d3b04..0f8453beb0 100644 --- a/libs/design/scss/theming/color-shift/_color-shift.spec.scss +++ b/libs/design/scss/theming/color-shift/_color-shift.spec.scss @@ -1,10 +1,10 @@ @use 'true' as *; @use 'sass:map'; -@use './color-shift' as *; -@use './color-palettes' as palette; -@use './configure-palette' as *; -@use './get-color' as *; -@use '../core/error/error-to-string'; +@use '../color-shift' as *; +@use '../color-palettes' as palette; +@use '../configure-palette' as *; +@use '../get-color' as *; +@use '../../core/error/error-to-string'; @include describe('daff-color-shift') { $default: daff-configure-palette(palette.$daff-blue);