Allow configuring dark mode from configs

This commit is contained in:
Jordan Pittman 2024-08-22 12:19:52 -04:00 • committed by Philipp Spiess
parent cc353d310b
commit 67efbfe628
4 changed files with 96 additions and 0 deletions

View file

@ -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,

View file

@ -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
}

View file

@ -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} *)`)
}
}

View file

@ -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)