Для стилизации 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)
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-ой размер шрифта, аргументированно сказать нет, пусть выбирает жирность, цвет, отступы.
И такое количество вопросов возникает на каждом шагу.
freislot Автор
26.02.2019 15:43Мы сами находимся в постоянном поиске, т.к серебрянной пули нет и у всех свои требования к используемым инструментам и фреймворкам.
У нас обычно размеры шрифтов обговариваются на этапе отрисовки дизайна, мы уже получаем их как константы.
Мы тоже пытались делать шкалу значений и человеческие названия, но все это так и не дало нам понятной и четкой дизайн системы.
AxisPod
26.02.2019 14:07Интересно, почему же в подобных статьях ни слова о тестируемости данных решений?
artalar
26.02.2019 17:26Крайне рекомендую этот тред (и сабтреды) к прочтению twitter.com/andrey_sitnik/status/1098115325281869825
Мои выводы:
— АПИ styled-components (в плане объединения объявления стилей и компонента) архитектурно очень качественно и удобно
— Библиотека подустарела и есть более интересные аналоги
— Зато она стабильна и имеет большую экостистему
— Есть еще куча мелочей, которые стоит учитывать, особенно чем больше приложение.freislot Автор
26.02.2019 17:45Да, интересный тред, спасибо. Я считаю, что все как всегда зависит от требований к проекту и решаемых задач. Тут каждый выбирает уже то, что подходит конкретно ему в конкретном проекте. Но за ссылку спасибо, будем иметь в виду, особенно заинтриговал astroturf. Посмотрю в свободное время
mxmvshnvsk
28.02.2019 16:33Достаточно интересное решение, большое спасибо за статью, нужно будет попробовать.
freislot Автор
01.03.2019 09:07Спасибо за отзыв, сами пока пробуем, пока только в одном проекте используем styled system, но вроде нравится. Время покажет.
Carduelis
В своих проектах использую styled-components. Параллельно создается UI-kit по "атомарному" дизайну. Потребовалась подобная же система, чтобы отступы были консистентны, а цвета только из палитры.
Эта самописная оркестрация очень сильно похожа на упомянутую вами. Но я не готов был ее выкладывать в сообщество по следующим причинам (уже почти год в двух командах разработки используется):
Отсюда спрошу:
pz
вместоpx
? Насколько система дружественна по сравнению с подсказками в реакте?Кстати, есть "баг" из документации (который, порой, рубит на корню все эти сокращалки для удобства):
А что, если я хочу
fontSize={8}
, и у меня9
элементов внутри массиваtheme.fontSizes
? Магия не должна ломаться.freislot Автор
Значения применяются как есть. В css будет значение с pz перечеркнутое в инспекторе.
Подсказок никаких нет, ошибок тоже.
Честно говоря мы сами не замеряли производительность, я думаю это уже больше относится к styled-components нежели к styled system, но у них в гите есть страничка с бенчмарками
Не совсем понятно что имелось в виду, «магия» работает так — все что больше 9-го элемента в массиве fontSizes будет интерпретироваться как есть.
fontSize={8}
вернет 72fontSize={9}
вернет 9