diff --git a/packages/web/src/scss/foundation/_shared.scss b/packages/web/src/scss/foundation/_shared.scss index 95dfa89236..71f6fda001 100644 --- a/packages/web/src/scss/foundation/_shared.scss +++ b/packages/web/src/scss/foundation/_shared.scss @@ -1,6 +1,6 @@ -@use '@tokens' as tokens; +@use '@global' as global-tokens; // Always declare margins in the same direction. :where(address, blockquote, h1, h2, h3, h4, h5, h6, p, pre, dl, ol, ul, figure, hr, table, fieldset):not(:last-child) { - margin-bottom: tokens.$space-600; + margin-bottom: global-tokens.$space-700; } diff --git a/packages/web/src/scss/foundation/_tokens.scss b/packages/web/src/scss/foundation/_tokens.scss index 8b849cc99e..4c9afd8902 100644 --- a/packages/web/src/scss/foundation/_tokens.scss +++ b/packages/web/src/scss/foundation/_tokens.scss @@ -1,99 +1,60 @@ -@use '@tokens' as tokens; +@use '@global' as global-tokens; :root { // Print all token values as CSS variables // Borders - --spirit-border-style-0: #{tokens.$border-style-0}; - --spirit-border-style-100: #{tokens.$border-style-100}; - --spirit-border-style-200: #{tokens.$border-style-200}; - --spirit-border-width-0: #{tokens.$border-width-0}; - --spirit-border-width-100: #{tokens.$border-width-100}; - --spirit-border-width-200: #{tokens.$border-width-200}; - - // Colors - /// Action colors - @each $color-name, $color-value in tokens.$action-colors { - --spirit-color-action-#{$color-name}: #{$color-value}; - } - - /// Background colors - @each $color-name, $color-value in tokens.$background-colors { - --spirit-color-background-#{$color-name}: #{$color-value}; - } - - /// Border colors - @each $color-name, $color-value in tokens.$border-colors { - --spirit-color-border-#{$color-name}: #{$color-value}; - } - - /// Brand colors - @each $color-name, $color-value in tokens.$brand-colors { - --spirit-color-brand-#{$color-name}: #{$color-value}; - } - - /// Emotion - @each $color-name, $color-value in tokens.$emotion-colors { - --spirit-color-emotion-#{$color-name}: #{$color-value}; - } - - /// Focus colors - @each $color-name, $color-value in tokens.$focus-colors { - --spirit-color-focus-#{$color-name}: #{$color-value}; - } - - /// Text colors - @each $color-name, $color-value in tokens.$text-colors { - --spirit-color-text-#{$color-name}: #{$color-value}; - } + --spirit-border-width-0: #{global-tokens.$border-width-0}; + --spirit-border-width-100: #{global-tokens.$border-width-100}; + --spirit-border-width-200: #{global-tokens.$border-width-200}; // Gradients - @each $gradient-name, $gradient-value in tokens.$gradients { + @each $gradient-name, $gradient-value in global-tokens.$gradients { --spirit-gradient-#{$gradient-name}: #{$gradient-value}; } - // Measures - @each $space-name, $space-value in tokens.$spaces { + // Spacing + @each $space-name, $space-value in global-tokens.$spaces { --spirit-space-#{$space-name}: #{$space-value}; } // Other /// Containers - @each $container-name, $container-value in tokens.$containers { + @each $container-name, $container-value in global-tokens.$containers { --spirit-container-#{$container-name}: #{$container-value}; } /// Grids - @each $grid-name, $grid-value in tokens.$grids { + @each $grid-name, $grid-value in global-tokens.$grids { --spirit-grid-#{$grid-name}: #{$grid-value}; } /// Container paddings - @each $container-padding-name, $container-padding-value in tokens.$container-paddings { + @each $container-padding-name, $container-padding-value in global-tokens.$container-paddings { --spirit-container-padding-#{$container-padding-name}: #{$container-padding-value}; } - /// Grid gutters - @each $grid-gutter-name, $grid-gutter-value in tokens.$grid-gutters { - --spirit-grid-gutter-#{$grid-gutter-name}: #{$grid-gutter-value}; + /// Grid spacings + @each $grid-spacing-name, $grid-spacing-value in global-tokens.$grid-spacings { + --spirit-grid-spacing-#{$grid-spacing-name}: #{$grid-spacing-value}; } /// Breakpoints - @each $breakpoint-name, $breakpoint-value in tokens.$breakpoints { + @each $breakpoint-name, $breakpoint-value in global-tokens.$breakpoints { --spirit-breakpoint-#{$breakpoint-name}: #{$breakpoint-value}; } // Radii - @each $radius-name, $radius-value in tokens.$radii { + @each $radius-name, $radius-value in global-tokens.$radii { --spirit-radius-#{$radius-name}: #{$radius-value}; } // Shadows - @each $shadow-name, $shadow-value in tokens.$shadows { + @each $shadow-name, $shadow-value in global-tokens.$shadows { --spirit-shadow-#{$shadow-name}: #{$shadow-value}; } // Typography - @each $style-name, $style-value in tokens.$styles { + @each $style-name, $style-value in global-tokens.$styles { @each $breakpoint-name, $breakpoint-value in $style-value { @each $property-name, $property-value in $breakpoint-value { --spirit-typography-#{$style-name}-#{$breakpoint-name}-#{$property-name}: #{$property-value}; diff --git a/packages/web/src/scss/foundation/_typography.scss b/packages/web/src/scss/foundation/_typography.scss index 02ea6850cb..37f43154b3 100644 --- a/packages/web/src/scss/foundation/_typography.scss +++ b/packages/web/src/scss/foundation/_typography.scss @@ -1,6 +1,9 @@ // 1. We are able to handle our text sizing ourselves, disable any auto adjustments. +// 2. We need to apply a global color to all elements in order to make the theme work. +// Otherwise, we would have to apply the color to every single element in the project. -@use '@tokens' as tokens; +@use '@global' as global-tokens; +@use '../settings/globals'; @use '../tools/typography'; :where(html) { @@ -8,7 +11,10 @@ } :where(body) { - @include typography.generate(tokens.$body-medium-text-regular); + @include typography.generate(global-tokens.$body-3-regular); +} - color: tokens.$text-primary-default; +// stylelint-disable-next-line selector-max-universal -- 2. +* { + color: var(--#{globals.$prefix}color-text-primary); } diff --git a/packages/web/src/scss/foundation/index.scss b/packages/web/src/scss/foundation/index.scss index a7f8e0c1df..0b5e4da6e7 100644 --- a/packages/web/src/scss/foundation/index.scss +++ b/packages/web/src/scss/foundation/index.scss @@ -1,10 +1,9 @@ -// This code is commented out until we switch it to the new token system -// @forward '@csstools/normalize.css/normalize.css'; -// @forward 'box-sizing'; -// @forward 'images'; -// @forward 'interactions'; -// @forward 'links'; -// @forward 'reset'; -// @forward 'shared'; -// @forward 'tokens'; -// @forward 'typography'; +@forward '@csstools/normalize.css/normalize.css'; +@forward 'box-sizing'; +@forward 'images'; +@forward 'interactions'; +@forward 'links'; +@forward 'reset'; +@forward 'shared'; +@forward 'tokens'; +@forward 'typography'; diff --git a/packages/web/src/scss/helpers/breakout/_breakout.scss b/packages/web/src/scss/helpers/breakout/_breakout.scss index 9644949ef9..79a5602cbb 100644 --- a/packages/web/src/scss/helpers/breakout/_breakout.scss +++ b/packages/web/src/scss/helpers/breakout/_breakout.scss @@ -1,8 +1,8 @@ @use 'sass:map'; -@use '@tokens' as tokens; +@use '@global' as global-tokens; @use '../../tools/breakpoint'; -@each $breakpoint-name, $breakpoint-value in tokens.$breakpoints { +@each $breakpoint-name, $breakpoint-value in global-tokens.$breakpoints { $suffix: breakpoint.get-modifier('suffix', $breakpoint-name, $breakpoint-value); @include breakpoint.up($breakpoint-value) { @@ -17,7 +17,7 @@ $previous-breakpoint: ( value: 0, ); -@each $breakpoint-name, $breakpoint-value in tokens.$breakpoints { +@each $breakpoint-name, $breakpoint-value in global-tokens.$breakpoints { @if map.get($previous-breakpoint, name) != '' { @include breakpoint.up(map.get($previous-breakpoint, value)) { @include breakpoint.down($breakpoint-value) { diff --git a/packages/web/src/scss/helpers/index.scss b/packages/web/src/scss/helpers/index.scss index 1963035cf7..79b48fcc8a 100644 --- a/packages/web/src/scss/helpers/index.scss +++ b/packages/web/src/scss/helpers/index.scss @@ -1,10 +1,9 @@ -// This code is commented out until we switch it to the new token system -// @forward 'accessibility'; -// @forward 'animations'; -// @forward 'breakout'; -// @forward 'images'; -// @forward 'links'; -// @forward 'lists'; -// @forward 'scroll-control'; -// @forward 'text'; -// @forward 'typography'; +@forward 'accessibility'; +@forward 'animations'; +@forward 'breakout'; +@forward 'images'; +@forward 'links'; +@forward 'lists'; +@forward 'scroll-control'; +@forward 'text'; +@forward 'typography'; diff --git a/packages/web/src/scss/helpers/links/_links.scss b/packages/web/src/scss/helpers/links/_links.scss index d305ae2bcc..3afad6d021 100644 --- a/packages/web/src/scss/helpers/links/_links.scss +++ b/packages/web/src/scss/helpers/links/_links.scss @@ -8,23 +8,28 @@ // 8. Disable link underline everywhere. @use 'sass:map'; -@use '@tokens' as tokens; +@use '@global' as global-tokens; @use '../../settings/cursors'; -@use '../../settings/dictionaries' as dictionaries-settings; +@use '../../settings/globals'; + +// @use '../../settings/dictionaries' as dictionaries-settings; @use '../../settings/links' as links-settings; -@use '../../tools/dictionaries'; + +// @use '../../tools/dictionaries'; @use '../../tools/links' as links-tools; +// TODO // 1. -@include dictionaries.prepare-button-links(dictionaries-settings.$action-link-colors); +// @include dictionaries.prepare-button-links(dictionaries-settings.$action-link-colors); +// TODO // 2. -@include dictionaries.generate-link-colors( - '.link', - dictionaries-settings.$action-link-colors, - tokens.$action-colors, - (default, hover, active, disabled) -); +// @include dictionaries.generate-link-colors( +// '.link', +// dictionaries-settings.$action-link-colors, +// tokens.$action-colors, +// (default, hover, active, disabled) +// ); // 3. [class*='typography-heading'] a { @@ -59,16 +64,17 @@ button.link-disabled:not(.link-underlined) { // 7. [class*='typography-heading'] :visited { - color: map.get(tokens.$action-colors, link-primary-visited); + color: var(--#{globals.$prefix}color-action-link-visited-default); } +// TODO // 7. -@include dictionaries.generate-link-colors( - '[class*="typography-heading"] .link', - dictionaries-settings.$text-colors, - tokens.$action-colors, - visited -); +// @include dictionaries.generate-link-colors( +// '[class*="typography-heading"] .link', +// dictionaries-settings.$text-colors, +// tokens.$action-colors, +// visited +// ); // 8. // stylelint-disable selector-no-qualifying-type -- Increase specificity to override button variant styles. diff --git a/packages/web/src/scss/helpers/typography/_typography.scss b/packages/web/src/scss/helpers/typography/_typography.scss index e9a4c846f6..1b38f70c0f 100644 --- a/packages/web/src/scss/helpers/typography/_typography.scss +++ b/packages/web/src/scss/helpers/typography/_typography.scss @@ -1,8 +1,8 @@ @use 'sass:map'; -@use '@tokens' as tokens; +@use '@global' as global-tokens; @use '../../tools/typography'; -@each $style-name, $style-value in tokens.$styles { +@each $style-name, $style-value in global-tokens.$styles { .typography-#{$style-name} { @include typography.generate($style-value); } diff --git a/packages/web/src/scss/index.scss b/packages/web/src/scss/index.scss index b2b504992c..8da891cb4c 100644 --- a/packages/web/src/scss/index.scss +++ b/packages/web/src/scss/index.scss @@ -1,6 +1,7 @@ // ⚠️ Order matters! // Layers are ordered by specificity, from the most generic rules to components to high-specificity overrides. +@forward 'themes'; @forward 'foundation'; @forward 'components'; @forward 'helpers'; diff --git a/packages/web/src/scss/settings/_dictionaries.scss b/packages/web/src/scss/settings/_dictionaries.scss index 7bf3a1f97b..be476659f4 100644 --- a/packages/web/src/scss/settings/_dictionaries.scss +++ b/packages/web/src/scss/settings/_dictionaries.scss @@ -1,5 +1,4 @@ @use 'sass:list'; -@use '@tokens' as tokens; $_alignments-x-extended: stretch; $_alignments-y-extended: stretch; diff --git a/packages/web/src/scss/settings/_globals.scss b/packages/web/src/scss/settings/_globals.scss new file mode 100644 index 0000000000..92f4d59d5e --- /dev/null +++ b/packages/web/src/scss/settings/_globals.scss @@ -0,0 +1 @@ +$prefix: 'spirit-'; diff --git a/packages/web/src/scss/settings/_transitions.scss b/packages/web/src/scss/settings/_transitions.scss index 5a89450d24..3027d5e1d3 100644 --- a/packages/web/src/scss/settings/_transitions.scss +++ b/packages/web/src/scss/settings/_transitions.scss @@ -1,4 +1,4 @@ -@use '@tokens' as tokens; +@use '@global' as global-tokens; $_coefficient: 1; // Tweak this to adjust the duration of all transitions during development. @@ -16,4 +16,4 @@ $timing-eased-in-out-fast: cubic-bezier(0.4, 0, 0.2, 1); $scale-ratio-small-objects: 0.95; $scale-ratio-large-objects: 0.975; -$shift-distance-medium: tokens.$space-600; +$shift-distance-medium: global-tokens.$space-700; diff --git a/packages/web/src/scss/settings/_utilities.scss b/packages/web/src/scss/settings/_utilities.scss index de01d52197..ad8a17f17a 100644 --- a/packages/web/src/scss/settings/_utilities.scss +++ b/packages/web/src/scss/settings/_utilities.scss @@ -1,5 +1,6 @@ @use 'sass:map'; -@use '@tokens' as tokens; +@use '@global' as global-tokens; +@use 'globals'; $utilities: ( 'display': ( @@ -22,7 +23,7 @@ $utilities: ( class: mt, values: map.merge( - tokens.$spaces, + global-tokens.$spaces, ( auto: auto, ) @@ -34,7 +35,7 @@ $utilities: ( class: mr, values: map.merge( - tokens.$spaces, + global-tokens.$spaces, ( auto: auto, ) @@ -46,7 +47,7 @@ $utilities: ( class: mb, values: map.merge( - tokens.$spaces, + global-tokens.$spaces, ( auto: auto, ) @@ -58,7 +59,7 @@ $utilities: ( class: ml, values: map.merge( - tokens.$spaces, + global-tokens.$spaces, ( auto: auto, ) @@ -70,7 +71,7 @@ $utilities: ( class: mx, values: map.merge( - tokens.$spaces, + global-tokens.$spaces, ( auto: auto, ) @@ -82,7 +83,7 @@ $utilities: ( class: my, values: map.merge( - tokens.$spaces, + global-tokens.$spaces, ( auto: auto, ) @@ -92,37 +93,37 @@ $utilities: ( responsive: true, property: padding-top, class: pt, - values: tokens.$spaces, + values: global-tokens.$spaces, ), 'padding-right': ( responsive: true, property: padding-right, class: pr, - values: tokens.$spaces, + values: global-tokens.$spaces, ), 'padding-bottom': ( responsive: true, property: padding-bottom, class: pb, - values: tokens.$spaces, + values: global-tokens.$spaces, ), 'padding-left': ( responsive: true, property: padding-left, class: pl, - values: tokens.$spaces, + values: global-tokens.$spaces, ), 'padding-x': ( responsive: true, property: padding-inline, class: px, - values: tokens.$spaces, + values: global-tokens.$spaces, ), 'padding-y': ( responsive: true, property: padding-block, class: py, - values: tokens.$spaces, + values: global-tokens.$spaces, ), 'text-align': ( responsive: true, @@ -134,31 +135,28 @@ $utilities: ( responsive: false, property: color, class: text, - values: - map.merge( - tokens.$text-colors, - ( - brand-primary: tokens.$brand-primary, - brand-secondary: tokens.$brand-secondary, - ) - ), + values: ( + primary: var(--#{globals.$prefix}color-text-primary), + secondary: var(--#{globals.$prefix}color-text-secondary), + tertiary: var(--#{globals.$prefix}color-text-tertiary), + ), ), 'background-color': ( responsive: false, property: background-color, class: bg, values: ( - basic: tokens.$background-basic, - brand-primary: tokens.$brand-primary, - brand-secondary: tokens.$brand-secondary, - cover: tokens.$background-cover, - inverted: tokens.$background-inverted, + primary: var(--#{globals.$prefix}color-background-primary), + secondary: var(--#{globals.$prefix}color-background-secondary), + tertiary: var(--#{globals.$prefix}color-background-tertiary), + brand-primary: var(--#{globals.$prefix}color-background-brand-primary), + brand-secondary: var(--#{globals.$prefix}color-background-brand-secondary), ), ), 'border-radius': ( responsive: false, property: border-radius, class: rounded, - values: tokens.$radii, + values: global-tokens.$radii, ), ); diff --git a/packages/web/src/scss/themes/index.scss b/packages/web/src/scss/themes/index.scss new file mode 100644 index 0000000000..ac88a60162 --- /dev/null +++ b/packages/web/src/scss/themes/index.scss @@ -0,0 +1,4 @@ +@use '@themes' as themes; +@use '../tools/themes' as themes-tools; + +@include themes-tools.generate(themes.$themes); diff --git a/packages/web/src/scss/tools/__tests__/_string.test.scss b/packages/web/src/scss/tools/__tests__/_string.test.scss index 52dee35549..f30bc2c9e9 100644 --- a/packages/web/src/scss/tools/__tests__/_string.test.scss +++ b/packages/web/src/scss/tools/__tests__/_string.test.scss @@ -28,3 +28,11 @@ @include test.assert-equal(string.replace('no-replacement-here', 'z', 'y'), 'no-replacement-here'); } } + +@include test.describe('singularize function') { + @include test.it('should singularize a string') { + @include test.assert-equal(string.singularize('gradients'), 'gradient'); + @include test.assert-equal(string.singularize('variables'), 'variable'); + @include test.assert-equal(string.singularize('colors'), 'color'); + } +} diff --git a/packages/web/src/scss/tools/__tests__/_themes.test.scss b/packages/web/src/scss/tools/__tests__/_themes.test.scss new file mode 100644 index 0000000000..484b7349ef --- /dev/null +++ b/packages/web/src/scss/tools/__tests__/_themes.test.scss @@ -0,0 +1,68 @@ +@use 'true' as test; +@use '../themes'; + +$test-theme-light: ( + action: ( + button: ( + active: #fff, + ), + ), +); + +$test-theme-light-inverted: ( + action: ( + button: ( + active: #000, + ), + ), +); + +$test-themes: ( + theme-light: ( + colors: $test-theme-light, + ), + theme-light-inverted: ( + colors: $test-theme-light, + ), +); + +@include test.describe('generate mixin') { + @include test.it('should generate default and other themes') { + @include test.assert() { + @include test.output() { + :root { + @include themes.generate($test-themes); + } + } + + @include test.expect() { + :root, + .spirit-theme-light { + --spirit-color-action-button-active: #fff; + } + + .spirit-theme-light-inverted { + --spirit-color-action-button-active: #000; + } + } + } + } +} + +@include test.describe('generate-css-variables mixin') { + @include test.it('should generate CSS variables based on the provided map') { + @include test.assert() { + @include test.output() { + :root { + @include themes.generate-css-variables($test-theme-light, 'test-'); + } + } + + @include test.expect() { + :root { + --test-action-button-active: #fff; + } + } + } + } +} diff --git a/packages/web/src/scss/tools/_links.scss b/packages/web/src/scss/tools/_links.scss index db1d3e59ff..c0f6120bfd 100644 --- a/packages/web/src/scss/tools/_links.scss +++ b/packages/web/src/scss/tools/_links.scss @@ -1,4 +1,5 @@ -@use '@tokens' as tokens; +@use '@global' as global-tokens; +@use '../settings/globals'; @use '../settings/links'; @mixin base($set-color: false) { @@ -6,7 +7,7 @@ text-underline-offset: links.$text-underline-offset; @if $set-color { - color: tokens.$action-link-primary-default; + color: var(--#{globals.$prefix}color-action-link-primary-default); } @media (hover: hover) { @@ -14,7 +15,7 @@ text-decoration: links.$text-decoration-hover; @if $set-color { - color: tokens.$action-link-primary-hover; + color: var(--#{globals.$prefix}color-action-link-primary-hover); } } } @@ -23,7 +24,7 @@ text-decoration: links.$text-decoration-hover; @if $set-color { - color: tokens.$action-link-primary-active; + color: var(--#{globals.$prefix}color-action-link-primary-active); } } } diff --git a/packages/web/src/scss/tools/_string.scss b/packages/web/src/scss/tools/_string.scss index c8fa445a5d..cea6cd6da1 100644 --- a/packages/web/src/scss/tools/_string.scss +++ b/packages/web/src/scss/tools/_string.scss @@ -54,3 +54,9 @@ @return $string; } + +// Singularize a string +// Example: singularize('gradients') will return 'gradient' +@function singularize($string) { + @return string.slice($string, 1, string.length($string) - 1); +} diff --git a/packages/web/src/scss/tools/_themes.scss b/packages/web/src/scss/tools/_themes.scss new file mode 100644 index 0000000000..4e6b6b8f56 --- /dev/null +++ b/packages/web/src/scss/tools/_themes.scss @@ -0,0 +1,33 @@ +@use 'sass:meta'; +@use '../settings/globals'; +@use 'string' as string-tools; + +@mixin generate($themes) { + // The default theme will be applied to the :root element. + // As in Figma we make the first theme the default one. + $is-default-theme: true; + + @each $key, $value in $themes { + $selector: if($is-default-theme, ':root, .#{globals.$prefix}#{$key}', '.#{globals.$prefix}#{$key}'); + + #{$selector} { + @each $token-type-key, $token-type-value in $value { + $token-type-name: string-tools.singularize($token-type-key); + + @include generate-css-variables($token-type-value, '#{globals.$prefix}#{$token-type-name}'); + } + } + + $is-default-theme: false; + } +} + +@mixin generate-css-variables($map, $prefix: '') { + @each $key, $value in $map { + @if meta.type-of($value) == map { + @include generate-css-variables($value, $prefix + '-' + $key); + } @else { + --#{$prefix + '-' + $key}: #{$value}; + } + } +} diff --git a/packages/web/src/scss/tools/_typography.scss b/packages/web/src/scss/tools/_typography.scss index 541b1e6cb8..b5f7305a36 100644 --- a/packages/web/src/scss/tools/_typography.scss +++ b/packages/web/src/scss/tools/_typography.scss @@ -1,10 +1,10 @@ @use 'sass:map'; @use 'sass:string'; @use 'breakpoint'; -@use '@tokens' as tokens; +@use '@global' as global-tokens; @mixin generate($token) { - @each $breakpoint-name, $breakpoint-value in tokens.$breakpoints { + @each $breakpoint-name, $breakpoint-value in global-tokens.$breakpoints { @include breakpoint.up($breakpoint-value) { @if map.has-key($token, $breakpoint-name, font-style) { font-style: map.get($token, $breakpoint-name, font-style); diff --git a/packages/web/src/scss/utilities/_utilities.scss b/packages/web/src/scss/utilities/_utilities.scss index c9b7f20ef8..75f9fae8a6 100644 --- a/packages/web/src/scss/utilities/_utilities.scss +++ b/packages/web/src/scss/utilities/_utilities.scss @@ -1,11 +1,11 @@ @use 'sass:map'; @use 'sass:meta'; -@use '@tokens' as tokens; +@use '@global' as global-tokens; @use '../settings/utilities' as utilities-settings; @use '../tools/breakpoint'; @use '../tools/utilities'; -@each $breakpoint-name, $breakpoint-value in tokens.$breakpoints { +@each $breakpoint-name, $breakpoint-value in global-tokens.$breakpoints { @include breakpoint.up($breakpoint-value) { $infix: if($breakpoint-value == 0, '', '-#{$breakpoint-name}'); diff --git a/packages/web/src/scss/utilities/index.scss b/packages/web/src/scss/utilities/index.scss index bbb4d31f80..e611d65b2b 100644 --- a/packages/web/src/scss/utilities/index.scss +++ b/packages/web/src/scss/utilities/index.scss @@ -1,2 +1 @@ -// This code is commented out until we switch it to the new token system -// @forward 'utilities'; +@forward 'utilities';