Как сгенерировать все ваши служебные классы с помощью Sass Maps

Одна из возможностей служебных классов заключается в предоставлении вам доступа к каждой небольшой концепции вашей дизайн-системы во множестве контекстов. Если ваш основной цвет - королевский синий, вы можете применить его как цвет текста к чему угодно с классом .text-royal-blue, как цвет фона с классом .bg-royal-blue и так далее.

Но как написать их эффективным, последовательным и масштабируемым способом?

TL; DR: в этом посте подробно рассказывается о том, как делать. Если вы хотите понять весь мыслительный процесс, читайте дальше. В противном случае вы можете загрузить код на GitHub или протестировать его на SassMeister.

$royal-blue: #0007ff;
.text-royal-blue {
  color: $royal-blue;
}
.bg-royal-blue {
  background: $royal-blue;
}
...

Это повторяется. Вы не только вручную набираете название и значение цвета каждый раз, но и создаете неподдерживаемую систему. Что произойдет, если у вас есть десять таких утилит для работы с цветом, и вам нужно добавить в схему еще один цвет?

Не следует тратить время на бессмысленные и утомительные дела. Вот для чего нужны языки сценариев. Если вы уже используете Sass, вам нужно использовать его мощь и позволить ему помочь вам.

Что такое карты Sass?

Карты - это тип данных Sass, который представляет связь между ключами и значениями. Если вы знакомы с другими языками сценариев, вы могли бы увидеть это как ассоциативный массив. Он позволяет хранить данные и иметь имя для ссылки на каждую часть.

Списки и карты немного похожи тем, что они хранят набор данных, и их можно итерировать в @each цикле. Но в отличие от списков, карты позволяют легко ссылаться на любую часть информации, называя ее по имени. Это делает его идеальным для группировки логически связанной информации.

$colors: (
  mako-grey: #404145,
  fuel-yellow: #ecaf2d,
  pastel-green: #5ad864
);

Добавим логики

Теперь, когда наши цвета аккуратно хранятся внутри карты, нам нужно выполнить итерацию для создания наших служебных классов. Для этого мы будем использовать директиву @each внутри @mixin, которую мы позже включим в наш базовый класс утилиты.

@mixin color-modifiers {
  // do stuff
}

Теперь давайте воспользуемся директивой @each, чтобы просмотреть $colors и получить нужные данные.

@mixin color-modifiers {
  @each $name, $hex in $colors {
    // do stuff
  }
}

Мы повторяем $colors, и в каждом цикле ссылка на текущий ключ будет в $name, а шестнадцатеричный код цвета будет в $hex. Мы можем приступить к созданию нашего набора правил.

@mixin color-modifiers {
  @each $name, $hex in $colors {
    &-#{$name} {
      color: $hex;
    }
  }
}

Теперь для каждой пары на карте @each будет генерировать набор правил, который ссылается на родительский селектор с символом &, добавляет дефис и имя цвета и устанавливает атрибут color на текущее шестнадцатеричное значение.

Другими словами, делаем это:

.text {
  @include color-modifiers;
}

Сгенерирует это:

.text-mako-grey {
  color: #404145;
}
.text-fuel-yellow {
  color: #ecaf2d;
}
.text-pastel-green {
  color: #5ad864;
}

Довольно аккуратно, а? Собственно, мы почти не поцарапали поверхность. На данный момент наш миксин может выводить правила только с атрибутом color. Что, если мы хотим создать несколько служебных классов для цветов фона?

К счастью, Sass позволяет нам передавать аргументы миксинам.

@mixin color-modifiers($attribute: 'color') {
  @each $name, $hex in $colors {
    &-#{$name} {
      #{$attribute}: $hex;
    }
  }
}

Теперь мы можем точно указать, какой атрибут нам нужен.

Давайте еще немного улучшим наш миксин: сейчас префикс модификатора - это жестко запрограммированный дефис. Это означает, что ваши классы всегда будут иметь форму .base-modifier. Что делать, если вам нужно это изменить? Что, если вы также хотите сгенерировать модификаторы со вкусом БЭМ (два дефиса)? Опять же, этого можно добиться, используя аргументы.

@mixin color-modifiers($attribute: 'color', $prefix: '-') {
  @each $name, $hex in $colors {
    &#{$prefix}#{$name} {
      #{$attribute}: $hex;
    }
  }
}

Теперь мы можем генерировать классы модификаторов с любым желаемым префиксом. Итак, делаем это:

.text {
  @include color-modifiers($prefix: '--');
}

Сгенерирует это:

.text--mako-grey {
  color: #404145;
}
.text--fuel-yellow {
  color: #ecaf2d;
}
.text--pastel-green {
  color: #5ad864;
}

Совет от профессионалов: в Sass вы можете явно указывать аргументы при вызове миксина или функции (как в примере выше). Это позволяет избежать их упорядочивания.

Карты на картах

Мне нравится использовать немного другую цветовую систему, чтобы управлять тональными вариациями. Вкладывая карты в карты, я получаю чистый и удобочитаемый способ группировки оттенков.

$colors: (
  grey: (
    base: #404145,
    light: #c7c7cd
  ),
  yellow: (
    base: #ecaf2d
  ),
  green: (
    base: #5ad864
  )
);

Если мы хотим работать с такой цветовой системой, нам нужно адаптировать наш миксин, чтобы он повторял более глубокий уровень.

@mixin color-modifiers($attribute: 'color', $prefix: '-', $separator: '-') {
  @each $name, $color in $colors {
    &#{$prefix}#{$name} {
      @each $tone, $hex in $color {
        &#{$separator}#{$tone} {
          #{$attribute}: $hex;
        }
      }
    }
  }
}

Мы добавили новый аргумент $separator, чтобы связать название цвета и тон. Мы могли бы использовать $prefix, но он не имеет той же цели. Использование выделенной переменной со значением по умолчанию - лучший выбор, поскольку это дает нам полную свободу при использовании миксина.

Теперь сделаем это:

.text {
  @include color-modifiers;
}

Сгенерирует это:

.text-grey-base {
  color: #404145;
}
.text-grey-light {
  color: #c7c7cd;
}
.text-yellow-base {
  color: #ecaf2d;
}
.text-green-base {
  color: #5ad864;
}

Большой! Теперь у нас есть помощники, состоящие из базового класса, цвета и тона. Одна вещь, которую нам нужно улучшить, - это то, как выводятся модификаторы базового цвета. На самом деле нам не нужен этот -base суффикс - достаточно базового класса и цвета.

Что мы должны сделать, так это проверить тон во вложенном @each цикле и выводить его и $separator только тогда, когда он не является «базовым». К счастью для нас, в Sass уже есть все, что нам нужно.

@if, @else, если ()

Нашим первым побуждением могло бы стать использование директив @if/@else. Проблема в том, что это заставит нас повторить код и приведет к сложному коду. Вместо этого мы собираемся использовать одно из секретных орудий Sass: if().

if() - это условный (тернарный) оператор Sass. Он принимает три аргумента: условие и два оператора возврата. Если условие выполнено, if() вернет первый оператор. В противном случае вернет второй. Вы можете видеть это как @if/@else сокращение.

@mixin color-modifiers($attribute: 'color', $prefix: '-', $separator: '-', $base: 'base') {
  @each $name, $color in $colors {
    &#{$prefix}#{$name} {
      @each $tone, $hex in $color {
        &#{if($tone != $base, #{$separator}#{$tone}, '')} {
          #{$attribute}: $hex;
        }
      }
    }
  }
}

Каждый раз, когда вложенный цикл @each будет анализировать $tone, отличный от «base», он будет возвращать $separator и $tone в качестве суффикса класса. В противном случае он ничего не вернет, оставив класс как есть.

.text-grey {
  color: #404145;
}
.text-grey-light {
  color: #c7c7cd;
}
.text-yellow {
  color: #ecaf2d;
}
.text-green {
  color: #5ad864;
}

СУХАЯ все это

В реальном проекте вы, скорее всего, захотите использовать различные структуры карт. Например, у вас могут быть одноуровневые глубокие карты для размеров шрифтов и двухуровневые глубокие карты для цветов. Вы не собираетесь писать разные миксины для каждого уровня глубины. Это было бы повторяющимся и недостижимым. Чтобы справиться с этим, вам нужно полагаться на один миксин.

Нам нужен универсальный миксин для генерации всех модификаторов, способный обрабатывать многомерные карты. Если вы сравните два миксина, которые мы придумали в этом руководстве, вы заметите, что они очень похожи. Единственное отличие состоит в том, что перед печатью вычисленного объявления CSS выполняется дополнительный цикл. Это типичная работа для рекурсивного микширования.

Он начнется с директивы @each, в которой мы можем начать создавать наш селектор. Здесь мы проверим, совпадает ли текущий $key с «базовым», чтобы мы могли решить, выводить его или нет. Затем мы проверим, является ли текущий $value картой: если да, нам нужно снова запустить миксин с того места, где мы находимся, и передать ему вложенную карту. В противном случае мы можем распечатать объявление CSS.

@mixin modifiers($map, $attribute, $prefix: '-', $separator: '-', $base: 'base') {
  @each $key, $value in $map {
    &#{if($key != $base, #{$prefix}#{$key}, '')} {
      @if type-of($value) == 'map' {
        @include modifiers($value, $attribute, $separator);
      }
      @else {
        #{$attribute}: $value;
      }
    }
  }
}

И вуаля! Этот миксин будет работать с картами любой глубины. Не стесняйтесь использовать его в своих проектах! Если он вам нравится, вы можете проявить немного любви, поставив его на GitHub. Кроме того, если вы хотите улучшить его, оставьте, пожалуйста, комментарий по сути, чтобы я мог его обновить 👍

Первоначально опубликовано на frontstuff.io.