Как мы отказались от использования Styled-System для создания компонентов и изобрели собственный велосипед

Всем привет! Меня зовут Саша, я сооснователь и по совместительству главный разработчик в Quarkly. В этой заметке я хочу рассказать о том, как концепция атомарного CSS, которой мы придерживаемся, вкупе с недостатками функционала Styled-System (и Rebass, как частного случая использования этой библиотеки) сподвигли нас к созданию своего собственного инструмента, который мы назвали Atomize.

Небольшая преамбула. Наш проект Quarkly — это микс графического редактора (вроде Figma, Sketch) и конструктора сайтов (по типу Webflow) с добавлением функционала, присущего классическим IDE. Про Quarkly мы обязательно напишем отдельный пост, там есть про что рассказать и что показать, ну а сегодня речь пойдет про упомянутый выше Atomize.

Atomize лежит в основе всего проекта и позволяет нам решать задачи, которые было бы невозможно или трудно решить с помощью Styled-System и Rebass. Как минимум, решение было бы гораздо менее изящным.

Если мало времени, чтобы осилить весь пост сейчас, то более лаконично ознакомиться с Atomize можно у нас на GitHub.

А чтобы знакомство было приятнее, мы запускаем конкурс по сборке react-компонентов с использованием Atomize. Подробнее об этом в конце поста.

С чего всё началось

Начиная разрабатывать Quarkly, мы условились, что хотим дать нашему пользователю возможность верстать на компонентах, но без необходимости использовать отдельный CSS-файл. Чтобы код был максимально минималистичен, но сохранял все возможности CSS, в отличие от инлайновых стилей.

Задача не инновационная и, на первый взгляд, вполне решаемая с помощью Styled-System и Rebass. Но этой функциональности нам оказалось недостаточно, а кроме того мы столкнулись со следующими проблемами:

  • неудобная работа с брейкпоинтами;
  • отсутствие возможности писать стили на состояние hover, focus etc;
  • механизм работы с темами показался нам недостаточно гибким.

Что представляет собой Atomize (кратко)

image

Из ключевых особенностей Atomize мы можем выделить следующие:

  • возможность использования переменных из темы в составных css-свойствах;
  • поддержка hover и любых других псевдоклассов;
  • короткие алиасы на каждое свойство (как в emmet);
  • возможность указывать стили на конкретный брейкпоинт, сохраняя при этом читаемость разметки;
  • минималистичный интерфейс.

При этом у Atomize есть два основных предназначения:

  • создание компонентов с поддержкой атомарного CSS и тем;
  • создание виджетов для интерактивного редактирования в проекте Quarkly.

Atomize, инструкция по применению

Перед началом работы необходимо установить зависимости:

npm i react react-dom styled-components @quarkly/atomize @quarkly/theme

Atomize является оберткой вокруг styled-component и имеет похожий API. Достаточно вызвать метод с именем необходимого элемента:

import atomize from '@quarkly/atomize';   const MyBox = atomize.div();

На выходе мы получаем react компонент, способный принимать любые CSS в виде пропсов.
Для удобства использования была разработана система алиасов свойств. К примеру bgc === backgroundColor

ReactDOM.render(<MyBox bgc="red" />, root);

С полным списком свойств и алиасов можно ознакомиться здесь.

Также предусмотрен механизм наследования в React:

const MySuperComponent = ({ className }) => {    // some logic here    return <div className={className} />; };   const MyWrappedComponent = atomize(MySuperComponent);

Работа с темами

Про это, как мне представляется, следует рассказать подробнее. Темы в Quarkly базируются на CSS-переменных. Ключевой особенностью является возможность переиспользования переменных из тем как в пропсах, так и в самой теме, без необходимости использования дополнительных абстракций в виде template-функций и последующей дополнительной обработки со стороны пользователя.

Чтобы использовать переменные из темы, достаточно описать свойство в теме и обратиться к этому свойству, используя префикс "—".

Переменные можно использовать как в JSX:

import Theme from "@quarkly/theme";   const theme = {    colors: {        dark: "#04080C",    }, }; export const MyComp = () => (    <Theme>        <Box bgc="--colors-dark" height="100px" width="100px" />    </Theme> );

(Цвет #04080C доступен через свойство —colors-dark)

Так и в самой теме:

import Theme from "@quarkly/theme";   const theme = {    colors: {        dark: "#04080C",    },    borders: {        dark: "5px solid --colors-dark",    }, }; export const MyComp = () => (    <Theme>        <Box border="--borders-dark" height="100px" width="100px" />    </Theme> );

(Мы переиспользовали переменную из цветов, подключив её в тему borders)

Для цветов в JSX-разметке предусмотрен упрощенный синтаксис:

import Theme from "@quarkly/theme";   const theme = {    colors: {        dark: "#04080C",    }, }; export const MyComp = () => (    <Theme>        <Box bgc="--dark" height="100px" width="100px" />    </Theme> );

Для работы с медиа-выражениями в темах предусмотрен breakpoint.
К любому свойству можно добавить префикс в виде имени ключа breakpoint’а.

import Theme from "@quarkly/theme";   const theme = {    breakpoints: {        sm: [{ type: "max-width", value: 576 }],        md: [{ type: "max-width", value: 768 }],        lg: [{ type: "max-width", value: 992 }],    },    colors: {        dark: "#04080C",    },    borders: {        dark: "5px solid --colors-dark",    }, }; export const MyComp = () => (    <Theme>        <Box            md-bgc="--dark"            border="--borders-dark"            height="100px"            width="100px"        />    </Theme> ); 

С исходным кодом тем можно ознакомиться здесь.

Эффекты

Основным отличием Atomize от Styled-System являются «effects». Что это и зачем это нужно?
Давайте представим, что вы создаете компонент Button, меняете у него color и border, но как назначить стили на hover, focus etc? Тут на помощь приходят эффекты.

При создании компонента достаточно передать объект с конфигурацией:

const MySuperButton = atomize.button({  effects: {    hover: ":hover",    focus: ":focus",    active: ":active",    disabled: ":disabled",  }, });

Ключом является префикс в имени пропса, а значением — CSS-селектор. Таким образом мы закрыли потребность во всех псевдо-классах.

Теперь если мы укажем префикс hover к любому CSS-свойству, то оно будет применено при определенном эффекте. Например, при наведении курсора:

ReactDOM.render(<MySuperButton hover-bgc="blue" />, root);

Также эффекты можно сочетать с медиа-выражениями:

ReactDOM.render(<MySuperButton md-hover-bgc="blue" />, root);

Несколько примеров

Чтобы визуализировать информацию выше, давайте теперь соберем какой-нибудь интересный компонент. Мы приготовили два примера:

Во втором примере мы задействовали большую часть функционала, а также внешний API.

Но это не всё

Второе предназначение Atomize, как вы упомянули выше, это создание виджетов в Quarkly на основе пользовательских react-компонентов.

Для этого достаточно обернуть ваш компонент в Atomize и описать его конфигурацию, чтобы Quarkly смог понять, какие свойства можно интерактивно редактировать:

export default atomize(PokemonCard)(  {    name: "PokeCard",    effects: {      hover: ":hover",    },    description: {      // past here description for your component      en: "PokeCard — my awesome component",    },    propInfo: {      // past here props description for your component      name: {        control: "input",      },    },  },  { name: "pikachu" } );

Поля конфигурации для компонента выглядят так:

  • effects — определяет браузерные псевдоклассы (hover, focus, etc);
  • description — описание компонента, которое будет появляться при наведении курсора на его название;
  • propInfo — конфигурация контролов, которые будут отображаться в правой панели (вкладка props).

Как определить пропсы, которые будут выводиться на правой панели (вкладка props):

propInfo: {    yourCustomProps: { // имя свойства        description: { en: "test" }, // описание с учетом локализации        control: "input" // тип контрола    } }

Возможные варианты контролов:

  • input,
  • select,
  • color,
  • font,
  • shadow,
  • transition,
  • transform,
  • filter,
  • background,
  • checkbox-icon,
  • radio-icon,
  • radio-group,
  • checkbox.

Ещё один пример. Здесь мы добавили свой компонент в систему и теперь можем редактировать его интерактивно:

Спасибо тем, кто осилил материал до конца! Заранее извиняюсь за сумбур, это первый опыт написания такого рода статей. Буду благодарен за критику.

А теперь конкурс!

Дабы слегка подогреть интерес сообщества к более тесному знакомству с Atomize, мы решили пойти по простому и понятному (как и сам Atomize) пути — мы запускаем конкурс!

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

Если коротко: для участия и победы необходимо придумать (или найти готовый) интересный и полезный компонент на React и адаптировать его под требования Atomize. Мы выберем и наградим победителей сразу в нескольких номинациях. Дополнительные призы от нашей команды в случае добавления вашего компонента в Quarkly гарантированы.

ссылка на оригинал статьи https://habr.com/ru/company/quarkly/blog/504064/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *