@bem-react/classname: Support css-modules [RFC]
Пример использования
Подключаем хелпер и передаем в него название компонента а так же объект со стилями:
// src/components/Button/Button.js
import React from 'react'
import { createCn } from '@bem-react/classname'
import styles from './Button.module.css'
export const { merge, cn } = createCn('Button', styles)
export const Button = ({ children }) => {
return (
<button className={cn()}>
<span className={cn('Text')}>{children}</span>
</button>
)
}
Далее на уровне проекта или эксперимента мы можем переопределить нужные нам стили:
// src/experiments/Button/Button.js
import { merge, cn, Button } from '../../components/Button/Button'
import styles from './Button.module.css'
merge(styles)
export { merge, cn, Button }
Пример реализации
Используем фабрику для генерации классов, в замыкании храним доопределения для компонента и его элементов (в данной реализации не поддержаны модификаторы и миксы):
export function createCn(component, styles) {
const styleOverrides = []
return {
merge(overrides) {
styleOverrides.push(overrides)
},
cn(element) {
const target = element ? `${component}-${element}` : component
const className = [styles]
.concat(styleOverrides)
.map(shape => shape[target])
.join(' ')
return className
}
};
}
В таком кейсе нам не нужно будет докидывать микс на элементы снаружи, т.к. вся логика будет инкапсулирована внутри merge функции.
Возможно стоит делать это как новую фабрику, а не патчить существующую, в таком случае можно будет не нести эту логику в бандл, если она не нужна, т.к. будет три-шейкинг.
Нужно ещё продумать различные кейсы использования, возможно что-то не учли.
После реализации можно будет постепенно компоненты переводить на css-модули.
Для createCn можно использовать тапл, чтобы для каждого компонента можно было назвать cn уникально.
export const [cn, merge] = createCn('Button', styles)
Так же нужно подумать про то, как это будет работать с платформами (desktop, touch) при SSR, не будет ли проблем с тем, что у нас общая память.
Глобально мержить может быть не совсем хорошая идея - стили могут начать применяться там, где не нужно. Я поэкспериментировал и получилось примерно следующее Из плюсов здесь то, что такой подход может позволить гибко переопределять библиотечные стили. А библиотеки компонентов можно будет писать вообще без стилей, а при использовании можно будет прокидывать стили с разными темами и т.д.
В данном случае, в @bem-react/classname достаточно будет реализовать фабрику, которая принимает на входе мапу с классами:
const Button: FC<ButtonProps> = (props) => {
const { className, styles, size, disabled, ...other } = props;
const cn = useMemo(() => createCn('Block', props.styles), [props.styles]);
return <button className={cn({ styles, disabled }, [className])} {...other} />
}