From 67efbfe62831314bb666fab60ae80763f53926b7 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 22 Aug 2024 12:19:52 -0400 Subject: [PATCH] Allow configuring dark mode from configs --- .../src/compat/config/resolve-config.ts | 9 ++++ .../tailwindcss/src/compat/config/types.ts | 32 +++++++++++ packages/tailwindcss/src/compat/dark-mode.ts | 53 +++++++++++++++++++ packages/tailwindcss/src/plugin-api.ts | 2 + 4 files changed, 96 insertions(+) create mode 100644 packages/tailwindcss/src/compat/dark-mode.ts diff --git a/packages/tailwindcss/src/compat/config/resolve-config.ts b/packages/tailwindcss/src/compat/config/resolve-config.ts index fe6dd359a..181efac7e 100644 --- a/packages/tailwindcss/src/compat/config/resolve-config.ts +++ b/packages/tailwindcss/src/compat/config/resolve-config.ts @@ -26,6 +26,7 @@ interface ResolutionContext { } let minimal: ResolvedConfig = { + darkMode: null, theme: {}, plugins: [], content: { @@ -52,10 +53,18 @@ export function resolveConfig(design: DesignSystem, files: ConfigFile[]): Resolv resolveInternal(ctx, file) } + // Merge dark mode + for (let config of ctx.configs) { + if ('darkMode' in config && config.darkMode !== undefined) { + ctx.result.darkMode = config.darkMode ?? null + } + } + // Merge themes mergeTheme(ctx) return { + ...ctx.result, content: ctx.content, theme: ctx.theme as ResolvedConfig['theme'], plugins: ctx.plugins, diff --git a/packages/tailwindcss/src/compat/config/types.ts b/packages/tailwindcss/src/compat/config/types.ts index e089420f1..09011a41d 100644 --- a/packages/tailwindcss/src/compat/config/types.ts +++ b/packages/tailwindcss/src/compat/config/types.ts @@ -39,3 +39,35 @@ export interface ResolvedContentConfig { export interface ResolvedConfig { content: ResolvedContentConfig } + +// Dark Mode support +type DarkModeStrategy = + // No dark mode support + | false + + // Use the `media` query strategy. + | 'media' + + // Use the `class` strategy, which requires a `.dark` class on the `html`. + | 'class' + + // Use the `class` strategy with a custom class instead of `.dark`. + | ['class', string] + + // Use the `selector` strategy — same as `class` but uses `:where()` for more predicable behavior + | 'selector' + + // Use the `selector` strategy with a custom selector instead of `.dark`. + | ['selector', string] + + // Use the `variant` strategy, which allows you to completely customize the selector + // It takes a string or an array of strings, which are passed directly to `addVariant()` + | ['variant', string | string[]] + +export interface UserConfig { + darkMode?: DarkModeStrategy +} + +export interface ResolvedConfig { + darkMode: DarkModeStrategy | null +} diff --git a/packages/tailwindcss/src/compat/dark-mode.ts b/packages/tailwindcss/src/compat/dark-mode.ts new file mode 100644 index 000000000..05564a6ff --- /dev/null +++ b/packages/tailwindcss/src/compat/dark-mode.ts @@ -0,0 +1,53 @@ +import type { PluginAPI } from "../plugin-api" +import type { ResolvedConfig } from "./config/types" + +export function darkModePlugin({ addVariant, config }: PluginAPI) { + let darkMode = config('darkMode', null) as ResolvedConfig['darkMode'] + let [mode, selector = '.dark'] = Array.isArray(darkMode) ? darkMode : [darkMode] + + if (mode === 'variant') { + let formats + + if (Array.isArray(selector)) { + formats = selector + } else if (typeof selector === 'function') { + formats = selector + } else if (typeof selector === 'string') { + formats = [selector] + } + + if (Array.isArray(formats)) { + for (let format of formats) { + if (format === '.dark') { + mode = false + console.warn('darkmode-variant-without-selector', [ + 'When using `variant` for `darkMode`, you must provide a selector.', + 'Example: `darkMode: ["variant", ".your-selector &"]`', + ]) + } else if (!format.includes('&')) { + mode = false + console.warn('darkmode-variant-without-ampersand', [ + 'When using `variant` for `darkMode`, your selector must contain `&`.', + 'Example `darkMode: ["variant", ".your-selector &"]`', + ]) + } + } + } + + selector = formats as any + } + + if (mode === null) { + // Do nothing + } else if (mode === 'selector') { + // New preferred behavior + addVariant('dark', `&:where(${selector}, ${selector} *)`) + } else if (mode === 'media') { + addVariant('dark', '@media (prefers-color-scheme: dark)') + } else if (mode === 'variant') { + addVariant('dark', selector) + } else if (mode === 'class') { + // Old behavior + addVariant('dark', `&:is(${selector} *)`) + } +} diff --git a/packages/tailwindcss/src/plugin-api.ts b/packages/tailwindcss/src/plugin-api.ts index 3ad1d0356..18eb92f7c 100644 --- a/packages/tailwindcss/src/plugin-api.ts +++ b/packages/tailwindcss/src/plugin-api.ts @@ -4,6 +4,7 @@ import type { Candidate, NamedUtilityValue } from './candidate' import { createCompatConfig } from './compat/config/create-compat-config' import { resolveConfig, type ConfigFile } from './compat/config/resolve-config' import type { ResolvedConfig, UserConfig } from './compat/config/types' +import { darkModePlugin } from './compat/dark-mode' import type { DesignSystem } from './design-system' import { createThemeFn } from './theme-fn' import { withAlpha, withNegative } from './utilities' @@ -354,6 +355,7 @@ export function registerPlugins( { config: createCompatConfig(designSystem.theme) }, ...configs, { config: { plugins } }, + { config: { plugins: [darkModePlugin] } }, ]) let pluginApi = buildPluginApi(designSystem, ast, resolvedConfig)