Add support for the theme function to plugins

This commit is contained in:
Jordan Pittman 2024-07-26 16:53:33 -04:00
parent 2fe2499635
commit c763a7799c
3 changed files with 143 additions and 0 deletions

View file

@ -14,6 +14,7 @@ import {
import { compileCandidates } from './compile'
import * as CSS from './css-parser'
import { buildDesignSystem, type DesignSystem } from './design-system'
import { buildPluginApi } from './plugin-api'
import { Theme } from './theme'
import { segment } from './utils/segment'
@ -21,6 +22,7 @@ const IS_VALID_UTILITY_NAME = /^[a-z][a-zA-Z0-9/%._-]*$/
type PluginAPI = {
addVariant(name: string, variant: string | string[] | CssInJs): void
theme(path: string, fallback?: any): any
}
type Plugin = (api: PluginAPI) => void
@ -280,6 +282,8 @@ export function compile(
designSystem.variants.fromAst(name, objectToAst(variant))
}
},
...buildPluginApi(designSystem),
}
for (let plugin of plugins) {

View file

@ -0,0 +1,53 @@
import type { DesignSystem } from './design-system'
export function buildPluginApi(designSystem: DesignSystem) {
return {
theme(path: string, fallback?: any) {
if (path.startsWith('--')) {
if (path.endsWith('-*')) {
return Object.fromEntries(
designSystem.theme.namespace(path.slice(0, -2) as any).entries(),
)
}
return designSystem.theme.resolveValue(null, [path] as any) ?? fallback ?? null
}
let original = path
// Escape dots used inside square brackets
.replace(/\[(.*?)\]/g, (_, value) => `-${value.replace('.', '_')}`)
// Replace dots with dashes
.replace(/\./g, '-')
// Replace camelCase with dashes
.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}-${b.toLowerCase()}`)
// Prepend with `--` to match CSS variables
original = `--${original}`
let map = designSystem.theme.namespace(original as any)
// Does the requested value exist in the theme
if (map.has(null)) {
// Yes, and there are multiple values in the requested theme namespace
if (map.size > 1) {
return {
DEFAULT: map.get(null),
...Object.fromEntries(Array.from(map.entries()).filter(([key]) => key !== null)),
}
}
// Nope, just the one
return map.get(null)
}
// There is at least one value in the requested theme namespace
// but no default value
if (map.size > 0) {
return Object.fromEntries(map.entries())
}
return fallback ?? null
},
}
}

View file

@ -15150,3 +15150,89 @@ describe('custom utilities', () => {
).toThrowError(/should be alphanumeric/)
})
})
describe('theme function in plugins', () => {
let input = css`
@plugin "my-plugin";
@theme reference {
--size-2_5: 2.5rem;
--scrollbar-big: 20px;
--scrollbar-big-properties: auto-hidden;
--scrollbar-color-light: white;
--scrollbar-color-dark: black;
},
`
test('CSS property notation', () => {
expect.hasAssertions()
compile(input, {
loadPlugin() {
return ({ theme }) => {
expect(theme('--scrollbar')).toEqual(null)
expect(theme('--scrollbar-*')).toEqual({
big: '20px',
'big-properties': 'auto-hidden',
'color-dark': 'black',
'color-light': 'white',
})
expect(theme('--scrollbar-big')).toEqual('20px')
}
},
})
})
test('legacy dot notation', () => {
expect.hasAssertions()
compile(input, {
loadPlugin() {
return ({ theme }) => {
// Accessing via legacy dot notation
expect(theme('size.2_5')).toEqual('2.5rem')
expect(theme('scrollbar')).toEqual({
big: '20px',
'big-properties': 'auto-hidden',
'color-dark': 'black',
'color-light': 'white',
})
expect(theme('scrollbar.big')).toEqual({
DEFAULT: '20px',
properties: 'auto-hidden',
})
expect(theme('scrollbar.big.properties')).toEqual('auto-hidden')
expect(theme('scrollbar.color')).toEqual({
light: 'white',
dark: 'black',
})
expect(theme('scrollbar.foo', 'nope')).toEqual('nope')
expect(theme('somekey', 'nope')).toEqual('nope')
}
},
})
})
test('legacy square bracket notation', () => {
expect.hasAssertions()
compile(input, {
loadPlugin() {
return ({ theme }) => {
expect(theme('size[2.5]')).toEqual('2.5rem')
expect(theme('scrollbar[big]')).toEqual({
DEFAULT: '20px',
properties: 'auto-hidden',
})
expect(theme('scrollbar[big][properties]')).toEqual('auto-hidden')
}
},
})
})
})