From 4c40045f063e8b45940acc798227a274031cdd85 Mon Sep 17 00:00:00 2001 From: Adam Kudrna Date: Sat, 31 Aug 2024 23:30:33 +0200 Subject: [PATCH] Use collections to generate styles of `Button` color variants --- src/components/Alert/Alert.module.scss | 2 +- src/components/Badge/Badge.module.scss | 4 +- src/components/Button/Button.jsx | 3 +- src/components/Button/Button.module.scss | 185 +++++++++++++++++++++- src/components/Button/README.md | 14 +- src/components/Button/_base.scss | 156 ------------------- src/components/Button/_priorities.scss | 149 ------------------ src/components/Button/_settings.scss | 11 +- src/components/Button/_theme.scss | 3 - src/components/Button/_tools.scss | 78 +--------- src/components/Card/Card.module.scss | 2 +- src/styles/tools/_collections.scss | 187 +++++++++++++++++++++-- 12 files changed, 383 insertions(+), 411 deletions(-) delete mode 100644 src/components/Button/_base.scss delete mode 100644 src/components/Button/_priorities.scss diff --git a/src/components/Alert/Alert.module.scss b/src/components/Alert/Alert.module.scss index b2a8f9b9..cd8c2b72 100644 --- a/src/components/Alert/Alert.module.scss +++ b/src/components/Alert/Alert.module.scss @@ -67,7 +67,7 @@ } @each $color in settings.$colors { - @include collections.generate-properties( + @include collections.generate-class( $prefix: "rui-", $component-name: "Alert", $variant-name: "color", diff --git a/src/components/Badge/Badge.module.scss b/src/components/Badge/Badge.module.scss index 7e57d903..d63d1ec6 100644 --- a/src/components/Badge/Badge.module.scss +++ b/src/components/Badge/Badge.module.scss @@ -28,7 +28,7 @@ } @each $color in settings.$colors { - @include collections.generate-properties( + @include collections.generate-class( $prefix: "rui-", $component-name: "Badge", $modifier-name: "priority", @@ -51,7 +51,7 @@ } @each $color in settings.$colors { - @include collections.generate-properties( + @include collections.generate-class( $prefix: "rui-", $component-name: "Badge", $modifier-name: "priority", diff --git a/src/components/Button/Button.jsx b/src/components/Button/Button.jsx index 3fa732fc..c934f13e 100644 --- a/src/components/Button/Button.jsx +++ b/src/components/Button/Button.jsx @@ -133,7 +133,8 @@ Button.propTypes = { */ block: PropTypes.bool, /** - * [Color variant](/docs/foundation/colors#component-colors) to clarify importance and meaning of the button. + * Color variant to clarify importance and meaning of the alert. Implements + * [Action, Feedback and Neutral color collections](/docs/foundation/collections#colors). */ color: PropTypes.oneOf( ['primary', 'secondary', 'selected', 'success', 'warning', 'danger', 'help', 'info', 'note', 'light', 'dark'], diff --git a/src/components/Button/Button.module.scss b/src/components/Button/Button.module.scss index 91d5b61f..7eb9b06b 100644 --- a/src/components/Button/Button.module.scss +++ b/src/components/Button/Button.module.scss @@ -1,2 +1,183 @@ -@use "base"; -@use "priorities"; +// 1. ButtonGroup gap is implemented using the `margin` property so the buttons can overlap and reduce duplicate +// borders. + +@use "sass:map"; +@use "../../styles/tools/breakpoint"; +@use "../../styles/tools/collections"; +@use "settings"; +@use "theme"; +@use "tools"; + +@layer components.button { + .root { + @include tools.button(); + } + + .label { + display: block; + } + + .beforeLabel, + .afterLabel, + .startCorner, + .endCorner, + .feedbackIcon { + display: flex; + align-items: baseline; + justify-content: center; + } + + .startCorner, + .endCorner { + position: absolute; + top: -0.35rem; + z-index: 2; + } + + .startCorner { + left: 0; + margin-left: -0.35rem; + } + + .endCorner { + right: 0; + margin-right: -0.35rem; + } + + .feedbackIcon { + position: absolute; + inset: 0; + z-index: 1; + align-items: center; + } + + .isRootSizeSmall { + @include tools.button-size(small); + } + + .isRootSizeMedium { + @include tools.button-size(medium); + } + + .isRootSizeLarge { + @include tools.button-size(large); + } + + .isRootBlock { + width: 100%; + } + + .hasRootFeedback:disabled { + opacity: theme.$feedback-opacity; + cursor: theme.$feedback-cursor; + } + + .hasRootFeedback .label, + .hasRootFeedback .beforeLabel, + .hasRootFeedback .afterLabel { + color: transparent; + } + + .isRootInButtonGroup, + .isRootInInputGroup { + z-index: map.get(settings.$group-z-indexes, button); + + &:not(:first-child) { + border-start-start-radius: var(--rui-local-inner-border-radius); + border-end-start-radius: var(--rui-local-inner-border-radius); + } + + &:not(:last-child) { + border-start-end-radius: var(--rui-local-inner-border-radius); + border-end-end-radius: var(--rui-local-inner-border-radius); + } + } + + .isRootInButtonGroup:not(:first-child) { + margin-inline-start: var(--rui-local-gap); // 1. + + &::before { + content: ""; + position: absolute; + top: calc(-1 * #{theme.$border-width}); + bottom: calc(-1 * #{theme.$border-width}); + left: calc(-1 * #{theme.$border-width}); + z-index: map.get(settings.$group-z-indexes, separator); + border-left: var(--rui-local-separator-width) solid var(--rui-local-separator-color); + transform: translateX(calc(-0.5 * var(--rui-local-gap) - 50%)); + } + } + + .isRootInButtonGroup:focus, + .isRootInButtonGroup:not(:disabled):hover { + z-index: map.get(settings.$group-z-indexes, button-hover); + } + + .isRootInButtonGroup .startCorner, + .isRootInInputGroup .startCorner, + .isRootInButtonGroup .endCorner, + .isRootInInputGroup .endCorner { + z-index: map.get(settings.$group-z-indexes, button-overflowing-elements); + } + + .hasLabelHidden, + .hasLabelVisibleSm, + .hasLabelVisibleMd, + .hasLabelVisibleLg, + .hasLabelVisibleXl, + .hasLabelVisibleX2l, + .hasLabelVisibleX3l { + @include tools.hide-label(); + } + + .hasLabelVisibleSm { + @include breakpoint.up(sm) { + @include tools.show-label(); + } + } + + .hasLabelVisibleMd { + @include breakpoint.up(md) { + @include tools.show-label(); + } + } + + .hasLabelVisibleLg { + @include breakpoint.up(lg) { + @include tools.show-label(); + } + } + + .hasLabelVisibleXl { + @include breakpoint.up(xl) { + @include tools.show-label(); + } + } + + .hasLabelVisibleX2l { + @include breakpoint.up(x2l) { + @include tools.show-label(); + } + } + + .hasLabelVisibleX3l { + @include breakpoint.up(x3l) { + @include tools.show-label(); + } + } + + @each $priority in map.keys(settings.$themeable-properties) { + @each $color in settings.$colors { + @include collections.generate-class( + $prefix: "rui-", + $component-name: "Button", + $modifier-name: "priority", + $modifier-value: $priority, + $variant-name: "color", + $variant-value: $color, + $generate-interaction-states: true, + $properties: map.get(settings.$themeable-properties, $priority), + ); + } + } +} diff --git a/src/components/Button/README.md b/src/components/Button/README.md index 7e43d509..be732e6a 100644 --- a/src/components/Button/README.md +++ b/src/components/Button/README.md @@ -40,8 +40,9 @@ lowest: 2. outline 3. flat -All priorities come in supported -[component colors](/docs/foundation/colors#component-colors). +All priorities are available in colors from supported +[color collections](/docs/foundation/collections#colors). +Check [API](#api) to see which collections are supported. ### Filled @@ -257,7 +258,7 @@ Disabled state makes the action unavailable. When user's action triggers an asynchronous process on background, the button's feedback state (not to be mistaken with -[feedback colors](/docs/foundation/colors#component-colors)) can be indicated by +[feedback colors](/docs/foundation/colors#feedback-colors)) can be indicated by showing an icon. The icon replaces button's label while retaining original dimensions of the button. Buttons in feedback state are automatically disabled to prevent unwanted interaction. @@ -431,9 +432,10 @@ Where: - `` is one of `filled`, `outline`, or `flat` (see [Priorities](#priorities) and [API](#api)), -- `` is one of supported - [component colors](/docs/foundation/colors#component-colors) - (see color variants of [each priority](#priorities) and [API](#api)), +- `` is a value from supported + [color collections](/docs/foundation/collections#colors) + (check color variants of [each priority](#priorities) and [API](#api) to see + which collections are supported), - `` is one of `default`, `hover`, `active`, or `disabled` (the last one being optional), - `` is one of: diff --git a/src/components/Button/_base.scss b/src/components/Button/_base.scss deleted file mode 100644 index 7c2648ac..00000000 --- a/src/components/Button/_base.scss +++ /dev/null @@ -1,156 +0,0 @@ -// 1. ButtonGroup gap is implemented using the `margin` property so the buttons can overlap and reduce duplicate -// borders. - -@use "sass:map"; -@use "../../styles/tools/breakpoint"; -@use "settings"; -@use "theme"; -@use "tools"; - -@layer components.button { - .root { - @include tools.button(); - } - - .label { - display: block; - } - - .beforeLabel, - .afterLabel, - .startCorner, - .endCorner, - .feedbackIcon { - display: flex; - align-items: baseline; - justify-content: center; - } - - .startCorner, - .endCorner { - position: absolute; - top: -0.35rem; - z-index: 2; - } - - .startCorner { - left: 0; - margin-left: -0.35rem; - } - - .endCorner { - right: 0; - margin-right: -0.35rem; - } - - .feedbackIcon { - position: absolute; - inset: 0; - z-index: 1; - align-items: center; - } - - .isRootSizeSmall { - @include tools.button-size(small); - } - - .isRootSizeMedium { - @include tools.button-size(medium); - } - - .isRootSizeLarge { - @include tools.button-size(large); - } - - .isRootBlock { - width: 100%; - } - - .hasRootFeedback:disabled { - opacity: theme.$feedback-opacity; - cursor: theme.$feedback-cursor; - } - - .hasRootFeedback .label, - .hasRootFeedback .beforeLabel, - .hasRootFeedback .afterLabel { - color: transparent; - } - - .isRootInButtonGroup, - .isRootInInputGroup { - z-index: map.get(settings.$group-z-indexes, button); - - &:not(:first-child) { - border-start-start-radius: var(--rui-local-inner-border-radius); - border-end-start-radius: var(--rui-local-inner-border-radius); - } - - &:not(:last-child) { - border-start-end-radius: var(--rui-local-inner-border-radius); - border-end-end-radius: var(--rui-local-inner-border-radius); - } - } - - .isRootInButtonGroup:not(:first-child) { - margin-inline-start: var(--rui-local-gap); // 1. - } - - .isRootInButtonGroup:focus, - .isRootInButtonGroup:not(:disabled):hover { - z-index: map.get(settings.$group-z-indexes, button-hover); - } - - .isRootInButtonGroup .startCorner, - .isRootInInputGroup .startCorner, - .isRootInButtonGroup .endCorner, - .isRootInInputGroup .endCorner { - z-index: map.get(settings.$group-z-indexes, button-overflowing-elements); - } - - .hasLabelHidden, - .hasLabelVisibleSm, - .hasLabelVisibleMd, - .hasLabelVisibleLg, - .hasLabelVisibleXl, - .hasLabelVisibleX2l, - .hasLabelVisibleX3l { - @include tools.hide-label(); - } - - .hasLabelVisibleSm { - @include breakpoint.up(sm) { - @include tools.show-label(); - } - } - - .hasLabelVisibleMd { - @include breakpoint.up(md) { - @include tools.show-label(); - } - } - - .hasLabelVisibleLg { - @include breakpoint.up(lg) { - @include tools.show-label(); - } - } - - .hasLabelVisibleXl { - @include breakpoint.up(xl) { - @include tools.show-label(); - } - } - - .hasLabelVisibleX2l { - @include breakpoint.up(x2l) { - @include tools.show-label(); - } - } - - .hasLabelVisibleX3l { - @include breakpoint.up(x3l) { - @include tools.show-label(); - } - } -} diff --git a/src/components/Button/_priorities.scss b/src/components/Button/_priorities.scss deleted file mode 100644 index eb94ce6a..00000000 --- a/src/components/Button/_priorities.scss +++ /dev/null @@ -1,149 +0,0 @@ -@use "sass:map"; -@use "settings"; -@use "theme"; -@use "tools"; - -@layer components.button { - .isRootPriorityFilled.isRootColorPrimary { - @include tools.button-color(filled, primary); - } - - .isRootPriorityFilled.isRootColorSecondary { - @include tools.button-color(filled, secondary); - } - - .isRootPriorityFilled.isRootColorSelected { - @include tools.button-color(filled, selected); - } - - .isRootPriorityFilled.isRootColorSuccess { - @include tools.button-color(filled, success); - } - - .isRootPriorityFilled.isRootColorWarning { - @include tools.button-color(filled, warning); - } - - .isRootPriorityFilled.isRootColorDanger { - @include tools.button-color(filled, danger); - } - - .isRootPriorityFilled.isRootColorHelp { - @include tools.button-color(filled, help); - } - - .isRootPriorityFilled.isRootColorInfo { - @include tools.button-color(filled, info); - } - - .isRootPriorityFilled.isRootColorNote { - @include tools.button-color(filled, note); - } - - .isRootPriorityFilled.isRootColorLight { - @include tools.button-color(filled, light); - } - - .isRootPriorityFilled.isRootColorDark { - @include tools.button-color(filled, dark); - } - - .isRootPriorityOutline.isRootColorPrimary { - @include tools.button-color(outline, primary); - } - - .isRootPriorityOutline.isRootColorSecondary { - @include tools.button-color(outline, secondary); - } - - .isRootPriorityOutline.isRootColorSelected { - @include tools.button-color(outline, selected); - } - - .isRootPriorityOutline.isRootColorSuccess { - @include tools.button-color(outline, success); - } - - .isRootPriorityOutline.isRootColorWarning { - @include tools.button-color(outline, warning); - } - - .isRootPriorityOutline.isRootColorDanger { - @include tools.button-color(outline, danger); - } - - .isRootPriorityOutline.isRootColorHelp { - @include tools.button-color(outline, help); - } - - .isRootPriorityOutline.isRootColorInfo { - @include tools.button-color(outline, info); - } - - .isRootPriorityOutline.isRootColorNote { - @include tools.button-color(outline, note); - } - - .isRootPriorityOutline.isRootColorLight { - @include tools.button-color(outline, light); - } - - .isRootPriorityOutline.isRootColorDark { - @include tools.button-color(outline, dark); - } - - .isRootPriorityFlat.isRootColorPrimary { - @include tools.button-color(flat, primary); - } - - .isRootPriorityFlat.isRootColorSecondary { - @include tools.button-color(flat, secondary); - } - - .isRootPriorityFlat.isRootColorSelected { - @include tools.button-color(flat, selected); - } - - .isRootPriorityFlat.isRootColorSuccess { - @include tools.button-color(flat, success); - } - - .isRootPriorityFlat.isRootColorWarning { - @include tools.button-color(flat, warning); - } - - .isRootPriorityFlat.isRootColorDanger { - @include tools.button-color(flat, danger); - } - - .isRootPriorityFlat.isRootColorHelp { - @include tools.button-color(flat, help); - } - - .isRootPriorityFlat.isRootColorInfo { - @include tools.button-color(flat, info); - } - - .isRootPriorityFlat.isRootColorNote { - @include tools.button-color(flat, note); - } - - .isRootPriorityFlat.isRootColorLight { - @include tools.button-color(flat, light); - } - - .isRootPriorityFlat.isRootColorDark { - @include tools.button-color(flat, dark); - } - - .isRootInButtonGroup:not(:first-child)::before { - content: ""; - position: absolute; - top: calc(-1 * #{theme.$border-width}); - bottom: calc(-1 * #{theme.$border-width}); - left: calc(-1 * #{theme.$border-width}); - z-index: map.get(settings.$group-z-indexes, separator); - border-left: var(--rui-local-separator-width) solid var(--rui-local-separator-color); - transform: translateX(calc(-0.5 * var(--rui-local-gap) - 50%)); - } -} diff --git a/src/components/Button/_settings.scss b/src/components/Button/_settings.scss index 9065491f..71f3908b 100644 --- a/src/components/Button/_settings.scss +++ b/src/components/Button/_settings.scss @@ -1,3 +1,5 @@ +@use "sass:list"; +@use "../../styles/settings/collections"; @use "../../styles/theme/typography"; @use "../../styles/tools/spacing"; @@ -12,9 +14,12 @@ $group-z-indexes: ( button-overflowing-elements: 3, ); -$themeable-variants: (primary, secondary, selected, success, warning, danger, help, info, note, light, dark); -$themeable-states: (default, hover, active, disabled); -$themeable-priority-properties: ( +$colors: + list.join( + list.join(collections.$action-colors, collections.$feedback-colors), + collections.$neutral-colors + ); +$themeable-properties: ( filled: (color, border-color, background, box-shadow), outline: (color, border-color, background), flat: (color, background), diff --git a/src/components/Button/_theme.scss b/src/components/Button/_theme.scss index ec62d574..c5814f95 100644 --- a/src/components/Button/_theme.scss +++ b/src/components/Button/_theme.scss @@ -1,6 +1,3 @@ -// Priority and variant specific theme options are obtained dynamically because there is way too -// many of them to maintain manually. See `_tools.scss` for details. - $font-weight: var(--rui-Button__font-weight); $letter-spacing: var(--rui-Button__letter-spacing); $text-transform: var(--rui-Button__text-transform); diff --git a/src/components/Button/_tools.scss b/src/components/Button/_tools.scss index 4d193257..70796fab 100644 --- a/src/components/Button/_tools.scss +++ b/src/components/Button/_tools.scss @@ -1,71 +1,23 @@ -// 1. Due to complexity, it's easier and safer to resolve variant custom properties dynamically with -// a mixin. This way it's also easier to make theming adjustments to all supported modifications -// in one place. This applies to the following button priorities: +// 1. Get ready for position of corner elements and group separator. // -// - filled -// - outline -// - flat +// 2. Icon buttons should appear as squares, that's why width and height is equal here. // -// 2. Disabled state theming is optional so `default` theme options are used if no `disabled` -// styling is provided for specific variant. -// -// 3. Get ready for position of corner elements and group separator. -// -// 4. Icon buttons should appear as squares, that's why width and height is equal here. -// -// 5. Use original padding to restore square buttons back to their size. +// 3. Use original padding to restore square buttons back to their size. @use "sass:list"; @use "sass:map"; -@use "sass:math"; @use "../../styles/tools/accessibility"; -@use "../../styles/tools/breakpoint"; @use "../../styles/tools/transition"; @use "settings"; @use "theme"; -// 1. -@mixin _get-themeable-properties($priority, $color, $state) { - @if not list.index(map.keys(settings.$themeable-priority-properties), $priority) { - @error "Invalid priority specified! #{$priority} doesn't exist. " - + "Choose one of #{settings.$themeable-priority-properties}."; - } - - @if not list.index(settings.$themeable-variants, $color) { - @error "Invalid variant specified! #{$color} doesn't exist. " - + "Choose one of #{settings.$themeable-variants}."; - } - - @if not list.index(settings.$themeable-states, $state) { - @error "Invalid state specified! #{$state} doesn't exist. " - + "Choose one of #{settings.$themeable-states}."; - } - - $properties: map.get(settings.$themeable-priority-properties, $priority); - - // 2. - @if $state == "disabled" { - @each $property in $properties { - --rui-local-#{$property}: - var( - --rui-Button--#{$priority}--#{$color}--#{$state}__#{$property}, - var(--rui-Button--#{$priority}--#{$color}--default__#{$property}) - ); - } - } @else { - @each $property in $properties { - --rui-local-#{$property}: var(--rui-Button--#{$priority}--#{$color}--#{$state}__#{$property}); - } - } -} - -// 4. +// 2. @mixin _button-square() { --rui-local-padding: 0; --rui-local-width: var(--rui-local-height); } -// 5. +// 3. @mixin _button-size-restore() { --rui-local-padding: var(--rui-local-padding-original); --rui-local-width: unset; @@ -74,7 +26,7 @@ @mixin button() { @include transition.add((opacity, color, border-color, background-color, box-shadow)); - position: relative; // 3. + position: relative; // 1. display: inline-flex; column-gap: settings.$icon-spacing; align-items: center; @@ -112,26 +64,10 @@ --rui-local-height: #{map.get($properties, height)}; --rui-local-padding: #{map.get($properties, padding-y)} #{map.get($properties, padding-x)}; - --rui-local-padding-original: #{map.get($properties, padding-y)} #{map.get($properties, padding-x)}; // 5. + --rui-local-padding-original: #{map.get($properties, padding-y)} #{map.get($properties, padding-x)}; // 3. --rui-local-font-size: #{map.get($properties, font-size)}; } -@mixin button-color($priority, $color) { - @include _get-themeable-properties($priority, $color, default); - - &:disabled { - @include _get-themeable-properties($priority, $color, disabled); - } - - &:not(:disabled):hover { - @include _get-themeable-properties($priority, $color, hover); - } - - &:not(:disabled):active { - @include _get-themeable-properties($priority, $color, active); - } -} - @mixin hide-label() { @include _button-square(); diff --git a/src/components/Card/Card.module.scss b/src/components/Card/Card.module.scss index da432403..03befec5 100644 --- a/src/components/Card/Card.module.scss +++ b/src/components/Card/Card.module.scss @@ -35,7 +35,7 @@ } @each $color in settings.$colors { - @include collections.generate-properties( + @include collections.generate-class( $prefix: "rui-", $component-name: "Card", $variant-name: "color", diff --git a/src/styles/tools/_collections.scss b/src/styles/tools/_collections.scss index 2d557398..f506a08f 100644 --- a/src/styles/tools/_collections.scss +++ b/src/styles/tools/_collections.scss @@ -1,36 +1,191 @@ @use "string" as rui-string; +// Mixin to generate CSS custom properties for a set of visual properties. +// +// 1. Generates a CSS custom property for each property in the `$properties` list. +// 2. Theming of the disabled state is optional, so the `default` theme options are used (via CSS custom property +// fallback) if no `disabled` styling for the specific variant is provided by user. +// +// @param {String} $prefix - The prefix for the CSS custom properties. +// @param {String} $component-name - The name of the component. +// @param {String} $modifier-value - The value of the modifier. +// @param {String} $variant-value - The value of the variant. +// @param {String} $interaction-state - The interaction state. +// @param {List} $properties - The list of properties to generate CSS custom properties for. +// +// Example: +// +// @include generate-properties( +// $prefix: "rui-", +// $component-name: "Card", +// $variant-name: "color", +// $variant-value: "success", +// $properties: color, border-color, background-color, +// ); +// +// … will output: +// +// --rui-local-color: var(--rui-Card--success__color); +// --rui-local-border-color: var(--rui-Card--success__border-color); +// --rui-local-background-color: var(--rui-Card--success__background-color); + @mixin generate-properties( + $prefix, + $component-name, + $modifier-value: null, + $variant-value, + $interaction-state: null, + $properties, +) { + @each $property in $properties { + $modifier: if($modifier-value, "--" + $modifier-value, ""); + $state: if($interaction-state, "--" + $interaction-state, ""); + + // 1. + --#{$prefix}local-#{$property}: + var( + #{ + "--" + + $prefix + + $component-name + + $modifier + + "--" + + $variant-value + + $state + + "__" + + $property + } + #{if( + $interaction-state and $interaction-state == "disabled", + ", var(--" + + $prefix + + $component-name + + $modifier + + "--" + + $variant-value + + "--default__" + + $property + + ")", + "" + )} + ); // 2. + } +} + +// Mixin to generate CSS classes for a component variant. +// +// @param {String} $prefix - The prefix for the CSS custom properties. +// @param {String} $component-name - The name of the component. +// @param {String} $modifier-name - Optional name of the class name modifier. +// @param {String} $modifier-name - Optional value of the class name modifier. +// @param {String} $variant-name - The name of the variant. +// @param {String} $variant-value - The value of the variant. +// @param {Boolean} $generate-interaction-states - Whether to generate interaction states (disabled, hover, active). +// @param {List} $properties - The list of properties to generate CSS custom properties for. +// +// Examples: +// +// @include collections.generate-class( +// $prefix: "rui-", +// $component-name: "Card", +// $variant-name: "color", +// $variant-value: "success", +// $properties: color, border-color, background-color, +// ); +// +// … will output: +// +// .isRootColorSuccess { +// --rui-local-color: var(--rui-Card--success__color); +// --rui-local-border-color: var(--rui-Card--success__border-color); +// --rui-local-background-color: var(--rui-Card--success__background-color); +// } +// +// @include collections.generate-class( +// $prefix: "rui-", +// $component-name: "Button", +// $modifier-name: "priority", +// $modifier-value: "flat", +// $variant-name: "color", +// $variant-value: "success", +// $generate-interaction-states: true, +// $properties: color, background, +// ); +// +// … will output: +// +// .isRootPriorityFlat.isRootColorSuccess { +// --rui-local-color: var(--rui-Button--flat--success--default__color); +// --rui-local-background: var(--rui-Button--flat--success--default__background); +// } +// .isRootPriorityFlat.isRootColorSuccess:disabled { +// --rui-local-color: +// var( +// --rui-Button--flat--success--disabled__color, +// var(--rui-Button--flat--success--default__color) +// ); +// --rui-local-background: +// var( +// --rui-Button--flat--success--disabled__background, +// var(--rui-Button--flat--success--default__background) +// ); +// } +// .isRootPriorityFlat.isRootColorSuccess:not(:disabled):hover { +// --rui-local-color: var(--rui-Button--flat--success--hover__color); +// --rui-local-background: var(--rui-Button--flat--success--hover__background); +// } +// .isRootPriorityFlat.isRootColorSuccess:not(:disabled):active { +// --rui-local-color: var(--rui-Button--flat--success--active__color); +// --rui-local-background: var(--rui-Button--flat--success--active__background); +// } + +@mixin generate-class( $prefix, $component-name, $modifier-name: null, $modifier-value: null, $variant-name, $variant-value, + $generate-interaction-states: false, $properties, ) { - $combined-class-name: + $modifier-class-name: if( $modifier-name and $modifier-value, ".isRoot#{rui-string.capitalize($modifier-name)}#{rui-string.capitalize($modifier-value)}", "" ); + $variant-class-name: ".isRoot#{rui-string.capitalize($variant-name)}#{rui-string.capitalize($variant-value)}"; + + #{$modifier-class-name}#{$variant-class-name} { + @if $generate-interaction-states { + $interaction-state-selector-map: ( + default: "&", + disabled: "&:disabled", + hover: "&:not(:disabled):hover", + active: "&:not(:disabled):active", + ); - #{$combined-class-name}.isRoot#{rui-string.capitalize($variant-name)}#{rui-string.capitalize($variant-value)} { - @each $property in $properties { - --#{$prefix}local-#{$property}: - var( - #{ - "--" - + $prefix - + $component-name - + if($modifier-value, "--" + $modifier-value, "") - + "--" - + $variant-value - + "__" - + $property - } - ); + @each $interaction-state, $interaction-state-selector in $interaction-state-selector-map { + #{$interaction-state-selector} { + @include generate-properties( + $prefix: $prefix, + $component-name: $component-name, + $modifier-value: $modifier-value, + $variant-value: $variant-value, + $interaction-state: $interaction-state, + $properties: $properties, + ); + } + } + } @else { + @include generate-properties( + $prefix: $prefix, + $component-name: $component-name, + $modifier-value: $modifier-value, + $variant-value: $variant-value, + $properties: $properties, + ); } } }