- PVSM.RU - https://www.pvsm.ru -
Всем привет! Меня зовут Саша, я сооснователь и по совместительству главный разработчик в Quarkly. В этой заметке я хочу рассказать о том, как концепция атомарного CSS, которой мы придерживаемся, вкупе с недостатками функционала Styled-System (и Rebass, как частного случая использования этой библиотеки) сподвигли нас к созданию своего собственного инструмента, который мы назвали Atomize.
Небольшая преамбула. Наш проект Quarkly [1] — это микс графического редактора (вроде Figma, Sketch) и конструктора сайтов (по типу Webflow) с добавлением функционала, присущего классическим IDE. Про Quarkly мы обязательно напишем отдельный пост, там есть про что рассказать и что показать, ну а сегодня речь пойдет про упомянутый выше Atomize.
Atomize лежит в основе всего проекта и позволяет нам решать задачи, которые было бы невозможно или трудно решить с помощью Styled-System и Rebass. Как минимум, решение было бы гораздо менее изящным.
Если мало времени, чтобы осилить весь пост сейчас, то более лаконично ознакомиться с Atomize можно у нас на GitHub [2].
А чтобы знакомство было приятнее, мы запускаем конкурс по сборке react-компонентов с использованием Atomize. Подробнее об этом в конце поста.
Начиная разрабатывать Quarkly, мы условились, что хотим дать нашему пользователю возможность верстать на компонентах, но без необходимости использовать отдельный CSS-файл. Чтобы код был максимально минималистичен, но сохранял все возможности CSS, в отличие от инлайновых стилей.
Задача не инновационная и, на первый взгляд, вполне решаемая с помощью Styled-System и Rebass. Но этой функциональности нам оказалось недостаточно, а кроме того мы столкнулись со следующими проблемами:
Из ключевых особенностей Atomize мы можем выделить следующие:
При этом у 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);
С полным списком свойств и алиасов можно ознакомиться здесь [3].
Также предусмотрен механизм наследования в 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>
);
С исходным кодом тем можно ознакомиться здесь [4].
Основным отличием 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" }
);
Поля конфигурации для компонента выглядят так:
Как определить пропсы, которые будут выводиться на правой панели (вкладка props):
propInfo: {
yourCustomProps: { // имя свойства
description: { en: "test" }, // описание с учетом локализации
control: "input" // тип контрола
}
}
Возможные варианты контролов:
Ещё один пример. Здесь мы добавили свой компонент в систему и теперь можем редактировать его интерактивно:
Спасибо тем, кто осилил материал до конца! Заранее извиняюсь за сумбур, это первый опыт написания такого рода статей. Буду благодарен за критику.
Дабы слегка подогреть интерес сообщества к более тесному знакомству с Atomize, мы решили пойти по простому и понятному (как и сам Atomize) пути — мы запускаем конкурс!
Вся информация о сроках, правилах и призах доступна на официальном сайте [7] конкурса.
Если коротко: для участия и победы необходимо придумать (или найти готовый) интересный и полезный компонент на React и адаптировать его под требования Atomize. Мы выберем и наградим победителей сразу в нескольких номинациях. Дополнительные призы от нашей команды в случае добавления вашего компонента в Quarkly гарантированы.
Автор: Александр
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/353450
Ссылки в тексте:
[1] Quarkly: https://quarkly.io
[2] GitHub: https://github.com/quarkly/atomize
[3] здесь: https://github.com/quarkly/atomize/blob/master/docs/aliases.md
[4] здесь: https://github.com/quarkly/theme
[5] простой компонент, который показывает все возможности библиотеки: https://codesandbox.io/s/atomize-demo-skhjw?file=/src/Example.js
[6] более сложный пример с карточкой покемона: https://codesandbox.io/s/pokemon-card-atomize-9pwom?file=/src/App.js
[7] официальном сайте: https://quarkly.io/contest-ru
[8] Источник: https://habr.com/ru/post/504064/?utm_source=habrahabr&utm_medium=rss&utm_campaign=504064
Нажмите здесь для печати.