Всем привет! Меня зовут Саша, я сооснователь и по совместительству главный разработчик в 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 (кратко)
Из ключевых особенностей 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 гарантированы.
Автор: Александр