image

Для стилизации react компонентов наша команда использует styled-components.

О styled-components уже есть статьи на Хабре, поэтому подробно останавливаться на этом не будем.

Знакомство с Styled components
Лучше, быстрее, мощнее: styled-components v4

Написав много компонентов мы обнаружили, что почти в каждом компоненте мы копируем повторяющиеся свойства.

Например во многих компонентах мы писали так:

  padding-top: ${props => props.paddingTop || '0'};
  padding-bottom: ${props => props.paddingBottom || '0'};
  padding-right: ${props => props.paddingRight || '0'};
  padding-left: ${props => props.paddingLeft || '0'};

Styled system


Копирование повторяющихся свойств начало раздражать, и мы вынесли повторяющиеся куски кода в отдельные переиспользуемые функции. Но я задумался о том, что возможно кто-то уже реализовал подобное до нас и наверняка более красиво и универсально. Начал гуглить и нашел styled system.

Styled-System предоставляет набор стилевых функций. Каждая функция стиля предоставляет собственный набор свойств, которые стилизуют элементы на основе значений, определенных в теме приложения. styled system имеет богатый API с функциями для большинства свойств CSS.

Пример использования styled system на основе styled-components

import { space, width, fontSize, color } from 'styled-system';
import styled, { ThemeProvider } from 'styled-components';
import theme from './theme';

const Box = styled.div`
  ${space}
  ${width}
  ${fontSize}
  ${color}
`;

render(
  <ThemeProvider theme={theme}>
    <Box p={3} bg="whites.10" color="orange">
      This is a Box
    </Box>
  </ThemeProvider>,
);

Основные преимущества


  • Добавляет свойства, которые можно использовать в собственных темах
  • Быстрая установка отзывчивых font-size, margin, padding, width и других свойств css через props
  • Масштабируемость типографики
  • Масштабируемость отступов margin и padding
  • Поддержка любой цветовой палитры
  • Работает с большинством библиотек css-in-js, включая styled-components и emotion
  • Используется в Rebass, Rebass Grid, и Priceline Design System

Подключение темы


Выше я приводил пример кода, в котором используется ThemeProvider. Мы передаем в провайдер нашу тему, а styled system обращается к ней через props.

Пример нашей темы

export const theme = {
  /** Размер шрифтов */
  fontSizes: [
    12, 14, 16, 18, 24, 32, 36, 72, 96
  ],
  /** Отступы и границы */
  space: [
    // margin and padding
    0, 4, 8, 16, 32, 64, 128, 256
  ],
  /** Общие цвета */
  colors: {
    UIClientError: '#ff6c00',
    UIServerError: '#ff0000',
    UITriggerRed: '#fe3d00',
    UITriggerBlue: '#00a9f6',
    UIModalFooterLightBlueGray: '#f3f9ff',
    UIModalTitleDefault: colorToRgba('#5e6670', 0.4),
    UICheckboxIconCopy: colorToRgba('#909cac', 0.2)
  },
  /** Размеры кнопок */
  buttonSizes: {
    xs: `
      height: 16px;
      padding: 0 16px;
      font-size: 10px;
    `,
    sm: `
      height: 24px;
      padding: 0 24px;
      font-size: 13px;
    `,
    md: `
      height: 34px;
      padding: 0 34px;
      font-size: 14px;
      letter-spacing: 0.4px;
    `,
    lg: `
      height: 56px;
      padding: 0 56px;
      font-size: 20px;
    `,
    default: `
      height: 24px;
      padding: 0 30px;
      font-size: 13px;
    `,
  },
  /** Цвета кнопок */
  buttonColors: {
    green: `
      background-color: #a2d628;
      color: ${colorToRgba('#a2d628', 0.5)};
    `,
    blue: `
      background-color: #507bfc;
      color: ${colorToRgba('#507bfc', 0.5)};
    `,
    lightBlue: `
      background-color: #10aee7;
      color: ${colorToRgba('#10aee7', 0.5)};
    `,
    default: `
      background-color: #cccccc;
      color: ${colorToRgba('#cccccc', 0.5)};
    `
  }
}

styled system попытается найти значение в объекте темы на основе переданных свойств компонента. Поддерживается глубокая вложенность свойств, если переданное значение не найдено в теме, то значение интерпретируется как есть.

Например мы передали компоненту color=«red». В объекте темы нет значения color.red, но значение red будет транслироваться в css как red. Таким образом после транспиляции в инспекторе мы увидим

color: red;


Другие примеры использования значений темы
// font-size: 24px (theme.fontSizes[4])
<Box fontSize={4} />

// margin: 16px (theme.space[3])
<Box m={3} />

// color: #ff6c00 (theme.colors.UIClientError)
<Box color="UIClientError" />

// background color (theme.colors.UITriggerBlue)
<Box bg="UITriggerBlue" />

// width: 50%
<Box width={1/2} />

Responsive styles


Для быстрого описания отзывчивых свойств достаточно передать массив значений

<Box
  width={[
    1,    // 100% below the smallest breakpoint
    1/2,  // 50% from the next breakpoint and up
    1/4   // 25% from the next breakpoint and up
  ]}
/>

// responsive font size
<Box fontSize={[ 1, 2, 3, 4 ]} />

// responsive margin
<Box m={[ 1, 2, 3, 4 ]} />

// responsive padding
<Box p={[ 1, 2, 3, 4 ]} />

Variants


styled system позволяет нам определять переиспользуемые объекты в нашей теме, которые содержат наборы цветов, стили текста и тп. Например в нашей теме, представленной выше, мы
используем варианты размеров и цветов кнопок.

  /** Размеры кнопок */
  buttonSizes: {
    xs: `
      height: 16px;
      padding: 0 16px;
      font-size: 10px;
    `,
    sm: `
      height: 24px;
      padding: 0 24px;
      font-size: 13px;
    `,
    default: `
      height: 24px;
      padding: 0 30px;
      font-size: 13px;
    `,
  },
  /** Цвета кнопок */
  buttonColors: {
    green: `
      background-color: #a2d628;
      color: ${colorToRgba('#a2d628', 0.5)};
    `,
    blue: `
      background-color: #507bfc;
      color: ${colorToRgba('#507bfc', 0.5)};
    `,
    lightBlue: `
      background-color: #10aee7;
      color: ${colorToRgba('#10aee7', 0.5)};
    `,
    default: `
      background-color: #cccccc;
      color: ${colorToRgba('#cccccc', 0.5)};
    `
  }

Реализация варианта:

/** Для размеров кнопок */
export const buttonSize = variant({
  /** Свойство компонента */
  prop: 'size',
  /** Свойство темы*/
  key: 'buttonSizes'
});

/** Для цветов кнопок */
export const buttonColor = variant({
  /** Свойство компонента */
  prop: 'colors',
  /** Свойство темы*/
  key: 'buttonColors'
});


Компонент Button
/** Описание компонента */
export const Button = styled(Box.withComponent('button'))`
  ${buttonSize}
  ${buttonColor}
`;

Button.propTypes = {
  ...buttonSize.propTypes,
  ...buttonColor.propTypes,
}

Пример использования кнопки размера medium синего цвета

<Button 
   size="md"
   colors="blue"
/>

Более подробное описание и документация styled system на оф. странице в github

UPD: во время написания статьи styled system обзавелся собственной страницей с документацией и примерами https://styled-system.com/.

Комментарии (12)


  1. Carduelis
    26.02.2019 13:43

    В своих проектах использую styled-components. Параллельно создается UI-kit по "атомарному" дизайну. Потребовалась подобная же система, чтобы отступы были консистентны, а цвета только из палитры.
    Эта самописная оркестрация очень сильно похожа на упомянутую вами. Но я не готов был ее выкладывать в сообщество по следующим причинам (уже почти год в двух командах разработки используется):


    1. Еще больший порог входа. Нужна хорошая интерактивная (!) документация.
    2. Каждый раз даже я сам забываю ключевые слова. Да, если ты описался, в консоль выпадет ошибка со списком похожих значений, но все же.
    3. Трудно покрыть весь спектр нужд, и на пограничных ситуациях, создается конфликт: либо переизобретать css-свойства в "свои", либо оставлять как есть. В одном случае, нам все равно нужен чистый css, а в другом, получаем переизобретенный css, что еще сильнее увеличивает порог входа.

    Отсюда спрошу:


    1. Как в этом проекте с обработкой ошибок? Что будет, если я напишу pz вместо px? Насколько система дружественна по сравнению с подсказками в реакте?
    2. Как у них с просизводительностью? Вырезают ли они проверки на продакшене?

    Кстати, есть "баг" из документации (который, порой, рубит на корню все эти сокращалки для удобства):


    // font-size of `theme.fontSizes[3]`
    <Text fontSize={3} />
    
    // font-size `32px`
    <Text fontSize={32} />

    А что, если я хочу fontSize={8}, и у меня 9 элементов внутри массива theme.fontSizes? Магия не должна ломаться.


    1. freislot Автор
      26.02.2019 15:35

      Как в этом проекте с обработкой ошибок? Что будет, если я напишу pz вместо px?

      Значения применяются как есть. В css будет значение с pz перечеркнутое в инспекторе.

      Насколько система дружественна по сравнению с подсказками в реакте?

      Подсказок никаких нет, ошибок тоже.

      Как у них с просизводительностью? Вырезают ли они проверки на продакшене?

      Честно говоря мы сами не замеряли производительность, я думаю это уже больше относится к styled-components нежели к styled system, но у них в гите есть страничка с бенчмарками

      А что, если я хочу fontSize={8}, и у меня 9 элементов внутри массива theme.fontSizes? Магия не должна ломаться.

      Не совсем понятно что имелось в виду, «магия» работает так — все что больше 9-го элемента в массиве fontSizes будет интерпретироваться как есть.

      const fontSizes = [ 12, 14, 16, 20, 24, 32, 48, 64, 72 ]

      fontSize={8} вернет 72
      fontSize={9} вернет 9


  1. Carduelis
    26.02.2019 13:54

    Еще есть проблема в использовании констант или элементов в массиве.
    Например, theme.fontSize[3] — это большой размер шрифта или маленький? Сколько всего элементов в массиве, какой шаг? А точно ли они от маленького в большому, или как h1 к h6: от большого к малому? А затем, дизайнер, хорошо подумав, считает, что нужен новый размер шрифта, который между №3 и №4. Что делать? Рефакторить весь проект или добавлять в конец?


    Очень много вопросов, на которые не хотелось бы отвечать и запоминать эту информацию.


    Поэтому в своем решении я отказался от этого. Сначала взял за основу подход к жирноте шрифта в css и к цветам в Material Design: где шкала от 100 до 900. Довольно логично. И CSS-спецификация, и популярная дизайн-система. Порог входа снижается.


    Но затем, подумал и проконсультировался с коллегами (им тоже ведь, код этот писать) ввел систему из человеческих слов, получилось: tiny, small, normal, medium, big, large, huge. Потом добавился micro, а medium в нашей системе запрещен (null), система выкидывает ошибки с подсказками.


    Но тут опять проблема в промежуточных значениях.


    Может быть брать за основу css-спецификацию по размерам шрифта: xx-small, x-small, medium, large, x-large, xx-large? Вроде следование спецификации. А на желания дизайнера ввести 8-ой размер шрифта, аргументированно сказать нет, пусть выбирает жирность, цвет, отступы.


    И такое количество вопросов возникает на каждом шагу.


    1. freislot Автор
      26.02.2019 15:43

      Мы сами находимся в постоянном поиске, т.к серебрянной пули нет и у всех свои требования к используемым инструментам и фреймворкам.

      У нас обычно размеры шрифтов обговариваются на этапе отрисовки дизайна, мы уже получаем их как константы.

      Мы тоже пытались делать шкалу значений и человеческие названия, но все это так и не дало нам понятной и четкой дизайн системы.


  1. AxisPod
    26.02.2019 14:07

    Интересно, почему же в подобных статьях ни слова о тестируемости данных решений?


    1. freislot Автор
      26.02.2019 15:20

      Уточните, пожалуйста, о каких тестах идет речь? Если речь идет о внутреннем устройстве styled system, то у них есть свои тесты.


      1. AxisPod
        27.02.2019 11:53

        Я про тестирование своих компонентов.


        1. freislot Автор
          27.02.2019 12:02

          Мы разрабатываем компоненты в сторибуке, получается как визуальное тестирование. Snapshot не используем


  1. artalar
    26.02.2019 17:26

    Крайне рекомендую этот тред (и сабтреды) к прочтению twitter.com/andrey_sitnik/status/1098115325281869825

    Мои выводы:
    — АПИ styled-components (в плане объединения объявления стилей и компонента) архитектурно очень качественно и удобно
    — Библиотека подустарела и есть более интересные аналоги
    — Зато она стабильна и имеет большую экостистему
    — Есть еще куча мелочей, которые стоит учитывать, особенно чем больше приложение.


    1. freislot Автор
      26.02.2019 17:45

      Да, интересный тред, спасибо. Я считаю, что все как всегда зависит от требований к проекту и решаемых задач. Тут каждый выбирает уже то, что подходит конкретно ему в конкретном проекте. Но за ссылку спасибо, будем иметь в виду, особенно заинтриговал astroturf. Посмотрю в свободное время


  1. mxmvshnvsk
    28.02.2019 16:33

    Достаточно интересное решение, большое спасибо за статью, нужно будет попробовать.


    1. freislot Автор
      01.03.2019 09:07

      Спасибо за отзыв, сами пока пробуем, пока только в одном проекте используем styled system, но вроде нравится. Время покажет.