From 518be75d35e77016f8f8429e8cb7dc9ef73b6c82 Mon Sep 17 00:00:00 2001 From: yue Date: Thu, 19 Dec 2024 14:38:01 +0900 Subject: [PATCH] feat: enabling css variables v1 via tailwind --- .storybook/tailwind.config.js | 1 + .../src/__snapshots__/index.test.ts.snap | 81 +++++++++++++++ .../tailwind-config/src/_lib/TailwindBuild.ts | 6 +- packages/tailwind-config/src/colors/plugin.ts | 23 +++-- .../src/colors/pluginTokenV1.ts | 99 +++++++++++++++++++ packages/tailwind-config/src/index.ts | 4 +- 6 files changed, 204 insertions(+), 10 deletions(-) create mode 100644 packages/tailwind-config/src/colors/pluginTokenV1.ts diff --git a/.storybook/tailwind.config.js b/.storybook/tailwind.config.js index bcf9d3ae9..774a779c7 100644 --- a/.storybook/tailwind.config.js +++ b/.storybook/tailwind.config.js @@ -14,6 +14,7 @@ module.exports = { ':root': light, '[data-dark="true"]': dark, }, + cssVariablesV1: false, unstableTokenV2: true, }), ], diff --git a/packages/tailwind-config/src/__snapshots__/index.test.ts.snap b/packages/tailwind-config/src/__snapshots__/index.test.ts.snap index 51ae5c87e..bf1e7b165 100644 --- a/packages/tailwind-config/src/__snapshots__/index.test.ts.snap +++ b/packages/tailwind-config/src/__snapshots__/index.test.ts.snap @@ -4641,6 +4641,87 @@ exports[`tailwind.config.js > list of css variables 1`] = ` "--tailwind-color-border--hover", "--tailwind-color-border--press", "--tailwind-color-border--outline", + "--charcoal-transparent", + "--charcoal-transparent-hover", + "--charcoal-transparent-press", + "--charcoal-background1", + "--charcoal-background1-hover", + "--charcoal-background1-press", + "--charcoal-background2", + "--charcoal-background2-hover", + "--charcoal-background2-press", + "--charcoal-icon6", + "--charcoal-icon6-hover", + "--charcoal-icon6-press", + "--charcoal-link1", + "--charcoal-link1-hover", + "--charcoal-link1-press", + "--charcoal-link2", + "--charcoal-link2-hover", + "--charcoal-link2-press", + "--charcoal-surface1", + "--charcoal-surface1-hover", + "--charcoal-surface1-press", + "--charcoal-surface2", + "--charcoal-surface2-hover", + "--charcoal-surface2-press", + "--charcoal-surface3", + "--charcoal-surface3-hover", + "--charcoal-surface3-press", + "--charcoal-surface4", + "--charcoal-surface4-hover", + "--charcoal-surface4-press", + "--charcoal-surface6", + "--charcoal-surface6-hover", + "--charcoal-surface6-press", + "--charcoal-surface7", + "--charcoal-surface7-hover", + "--charcoal-surface7-press", + "--charcoal-surface8", + "--charcoal-surface8-hover", + "--charcoal-surface8-press", + "--charcoal-surface9", + "--charcoal-surface9-hover", + "--charcoal-surface9-press", + "--charcoal-surface10", + "--charcoal-surface10-hover", + "--charcoal-surface10-press", + "--charcoal-text1", + "--charcoal-text1-hover", + "--charcoal-text1-press", + "--charcoal-text2", + "--charcoal-text2-hover", + "--charcoal-text2-press", + "--charcoal-text3", + "--charcoal-text3-hover", + "--charcoal-text3-press", + "--charcoal-text4", + "--charcoal-text4-hover", + "--charcoal-text4-press", + "--charcoal-text5", + "--charcoal-text5-hover", + "--charcoal-text5-press", + "--charcoal-brand", + "--charcoal-brand-hover", + "--charcoal-brand-press", + "--charcoal-assertive", + "--charcoal-assertive-hover", + "--charcoal-assertive-press", + "--charcoal-warning", + "--charcoal-warning-hover", + "--charcoal-warning-press", + "--charcoal-success", + "--charcoal-success-hover", + "--charcoal-success-press", + "--charcoal-updatedItem", + "--charcoal-updatedItem-hover", + "--charcoal-updatedItem-press", + "--charcoal-border", + "--charcoal-border-hover", + "--charcoal-border-press", + "--charcoal-border-default", + "--charcoal-border-default-hover", + "--charcoal-border-default-press", "--tailwind-gradient-surface5-top", "--tailwind-gradient-surface5-top-disabled", "--tailwind-gradient-surface5-top-hover", diff --git a/packages/tailwind-config/src/_lib/TailwindBuild.ts b/packages/tailwind-config/src/_lib/TailwindBuild.ts index 458d6ad6f..b8b7d55fe 100644 --- a/packages/tailwind-config/src/_lib/TailwindBuild.ts +++ b/packages/tailwind-config/src/_lib/TailwindBuild.ts @@ -89,9 +89,9 @@ export class TailwindBuild { const cssVariables = new Set() /** - * 独自に生成する CSS 変数は必ず --tailwind で始まるはず + * 独自に生成する CSS 変数は必ず --(tailwind|charcoal) で始まるはず */ - this.result.root.walkDecls(/^--tailwind/u, (decl) => { + this.result.root.walkDecls(/^--(tailwind|charcoal)/u, (decl) => { cssVariables.add(decl.prop) }) @@ -101,7 +101,7 @@ export class TailwindBuild { getCssVariable(varName: `--${string}`) { const values: string[] = [] - this.result.root.walkDecls(/^--tailwind/u, (decl) => { + this.result.root.walkDecls(/^--(tailwind|charcoal)/u, (decl) => { if (decl.prop === varName) { values.push(decl.value) } diff --git a/packages/tailwind-config/src/colors/plugin.ts b/packages/tailwind-config/src/colors/plugin.ts index 64095dcca..edd9ca46d 100644 --- a/packages/tailwind-config/src/colors/plugin.ts +++ b/packages/tailwind-config/src/colors/plugin.ts @@ -10,19 +10,30 @@ import plugin, { TailwindPlugin } from 'tailwindcss/plugin' import { mergeEffect } from '../foundation' import { CSSVariableName, CSSVariables, Definition, ThemeMap } from '../types' import { COLOR_PREFIX, isSingleColor } from './utils' +import { defineCssVariablesV1 } from './pluginTokenV1' /** - * `:root` 以外のケースで各 CSS Variable がどういう値を取るかを定義する + * --tailwind-* また --charcoal-* を生成する + * TODO: --tailwindをやめる */ -export default function cssVariableColorPlugin({ - ':root': _defaultTheme, - ...themes -}: ThemeMap): TailwindPlugin { - const definitions = defineCssVariables(themes) +export default function cssVariableColorPlugin( + themeMap: ThemeMap, + cssVariablesV1: boolean +): TailwindPlugin { + // `:root` 以外のケースで各 CSS Variable がどういう値を取るかを定義する + const { ':root': _defaultTheme, ...otherThemes } = themeMap + const definitions = defineCssVariables(otherThemes) return plugin(({ addBase }) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-call addBase(definitions) + + // styledのTokenInjector移植(background処理除く) + if (cssVariablesV1) { + const cssVariablesV1 = defineCssVariablesV1(themeMap) + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + addBase(cssVariablesV1) + } }) } diff --git a/packages/tailwind-config/src/colors/pluginTokenV1.ts b/packages/tailwind-config/src/colors/pluginTokenV1.ts new file mode 100644 index 000000000..c9f35870b --- /dev/null +++ b/packages/tailwind-config/src/colors/pluginTokenV1.ts @@ -0,0 +1,99 @@ +import { + applyEffect, + customPropertyToken, + filterObject, + flatMapObject, + mapObject, +} from '@charcoal-ui/utils' +import { ThemeMap } from '../types' +import { + CharcoalAbstractTheme, + EffectType, + Key, + CharcoalTheme as Theme, +} from '@charcoal-ui/theme' + +export function defineCssVariablesV1(themeMap: ThemeMap) { + // @ts-expect-error FIXME + return mapObject(themeMap, (key, theme) => { + if (key.startsWith('@media')) { + return [ + key, + { + ':root': defineColorVariableCSS(theme), + }, + ] + } else { + return [key, defineColorVariableCSS(theme)] + } + }) +} + +export const defineColorVariableCSS = (theme: Theme) => { + const borders = mapObject(theme.border, (name, { color }) => [ + // REVIEW: もしtheme.colorにたまたまborder-〇〇で始まる色名がいたら被りうる + withPrefixes('border', name), + color, + ]) + + const colors = defineThemeVariables({ ...theme.color, ...borders })({ theme }) + return colors +} + +/** + * Check whether a value is non-null and non-undefined + * + * @param value nullable + */ +export const isPresent = (value: T): value is NonNullable => value != null + +/** + * 子孫要素で使われるカラーテーマの CSS Variables を上書きする + * + * @params colorParams - 上書きしたい色の定義( `theme.color` の一部だけ書けば良い ) + * @params effectParams - effect の定義を上書きしたい場合は渡す(必須ではない) + * + * @example + * ```tsx + * const LocalTheme = styled.div` + * ${defineThemeVariables({ text1: '#ff0000' })} + * // `text1` is now defined as red + * ${theme((o) => [o.font.text1])} + * ` + * ``` + */ +export function defineThemeVariables( + colorParams: Partial, + effectParams?: Partial +) { + return function toCssObject(props: { + theme: Pick + }) { + const colors = filterObject(colorParams, isPresent) + + // flatMapObject の中で毎回 Object.entries を呼ぶのは無駄なので外で呼ぶ + const effects = Object.entries({ + ...props.theme.effect, + ...effectParams, + }) + + return flatMapObject(colors, (colorKey, color) => [ + [customPropertyToken(colorKey), color], + + ...effects.map<[string, string]>(([effectKey, effect]) => [ + customPropertyToken(colorKey, [effectKey]), + applyEffect(color, [effect]), + ]), + ]) + } +} + +export function isSupportedEffect(effect: Key): effect is EffectType { + return ['hover', 'press', 'disabled'].includes(effect as string) +} + +export const variable = (value: string) => `var(${value})` + +export function withPrefixes(...parts: string[]) { + return parts.join('-') +} diff --git a/packages/tailwind-config/src/index.ts b/packages/tailwind-config/src/index.ts index 4a1866fd2..44fa0915e 100644 --- a/packages/tailwind-config/src/index.ts +++ b/packages/tailwind-config/src/index.ts @@ -27,12 +27,14 @@ export { unstable_createTailwindConfigTokenV2 } interface Options { version?: TailwindVersion theme?: ThemeMap + cssVariablesV1?: boolean unstableTokenV2?: boolean } export function createTailwindConfig({ theme = { ':root': light }, version = 'v3', + cssVariablesV1 = true, unstableTokenV2 = false, }: Options): TailwindConfig { assertAllThemeHaveSameKeys(theme) @@ -151,7 +153,7 @@ export function createTailwindConfig({ }, plugins: [ typographyPlugin, - cssVariableColorPlugin(theme), + cssVariableColorPlugin(theme, Boolean(cssVariablesV1)), ...Object.entries(theme).map(([selectorOrMediaQuery, theme]) => cssVariableGradientPlugin(