Skip to content

Commit

Permalink
feat: support theme color parse
Browse files Browse the repository at this point in the history
  • Loading branch information
zyyv committed Oct 25, 2024
1 parent 185f152 commit 755e21d
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 17 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@
"prepublishOnly": "nr build",
"release": "bumpp && npm publish",
"start": "esno src/index.ts",
"test": "vitest",
"test": "vitest -u",
"typecheck": "tsc --noEmit",
"prepare": "simple-git-hooks"
},
"dependencies": {
"@magic-color/core": "^2.0.0",
"@types/css-tree": "^2.3.8",
"@unocss/core": "^0.63.6",
"css-tree": "^3.0.0",
Expand Down
32 changes: 32 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions src/maps/border.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { position, positionShort } from './position'
import { createPos4Props } from './position'
import type { PropsAtomicMap } from '../types'

export const border: PropsAtomicMap[] = [
['border', ['border', 'b']],
[/^border-(top|bottom|left|right)(?:-\w+)?$/, ([, p]) => {
const index = position.indexOf(p)
return [`border-${positionShort[index]}`, `b-${positionShort[index]}`]
}],
createPos4Props('border', 'border', 'b'),
]
8 changes: 8 additions & 0 deletions src/maps/color.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { StaticPropAtomicMap } from '../types'

export const colors: StaticPropAtomicMap[] = [
['color', 'c'],
['background', 'bg'],
['background-color', 'bg'],
['border-color', ['border', 'b']],
]
2 changes: 2 additions & 0 deletions src/maps/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { border } from './border'
import { colors } from './color'
import { margin } from './margin'
import { positions } from './position'

export const maps = [
border,
positions,
margin,
colors,
].flat(1)
23 changes: 18 additions & 5 deletions src/maps/position.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import type { PropsAtomicMap } from '../types'
import type { AtomicComposed, DynamicPropAtomicMap, PropsAtomicMap, StaticPropAtomicMap } from '../types'

export const position = ['top', 'bottom', 'left', 'right']
export const positionShort = ['t', 'b', 'l', 'r']

export const positions: PropsAtomicMap[] = position.map((p, i) => [
p,
[p, positionShort[i]],
])
export const positions: PropsAtomicMap[] = [
...position.map<StaticPropAtomicMap>((p, i) => [p, [p, positionShort[i]]]),
createPos4Props('margin', 'margin', 'm'),
createPos4Props('padding', 'padding', 'p'),
]

export function createPos4Props(prop: string, atom: string, shortAtom?: string): DynamicPropAtomicMap {
return [
new RegExp(`^${prop}-(top|bottom|left|right)(?:-\\w+)?$`),
([, p]) => {
const index = position.indexOf(p)
if (index > -1) {
return [atom, shortAtom].filter(Boolean).map(s => `${s}-${positionShort[index]}`) as AtomicComposed
}
},
]
}
85 changes: 81 additions & 4 deletions src/transfromer/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Magicolor } from '@magic-color/core'
import type { UnoGenerator } from '@unocss/core'
import { maps } from '../maps'
import { toArray } from '../utils'
import type { AtomicComposed, CssValueParsed, CssValueParsedMeta, DynamicPropAtomicMap, StaticPropAtomicMap, TransfromOptions } from '../types'
import type { AtomicComposed, Colors, CssValueParsed, CssValueParsedMeta, DynamicPropAtomicMap, StaticPropAtomicMap, TransfromOptions } from '../types'

const atomicCache: Record<string, string[]> = {}

Check failure on line 7 in src/transfromer/index.ts

View workflow job for this annotation

GitHub Actions / lint

'atomicCache' is assigned a value but never used. Allowed unused vars must match /^_/u

Expand All @@ -17,7 +19,7 @@ export function transfrom(metas: CssValueParsedMeta[], options: TransfromOptions
const atomics: string[] = []

Check failure on line 19 in src/transfromer/index.ts

View workflow job for this annotation

GitHub Actions / lint

'atomics' is assigned a value but never used. Allowed unused vars must match /^_/u
}

export function transfromParsed(parsed: CssValueParsed, options: TransfromOptions = {}) {
export function transfromParsed(parsed: CssValueParsed, uno: UnoGenerator, options: TransfromOptions = {}) {
let key: string | undefined

const {
Expand All @@ -33,6 +35,7 @@ export function transfromParsed(parsed: CssValueParsed, options: TransfromOption
atomics = toArray((map as StaticPropAtomicMap)[1]) as AtomicComposed
}
}

else {
const match = prop.match(map[0])
if (match) {
Expand Down Expand Up @@ -62,15 +65,17 @@ export function transfromParsed(parsed: CssValueParsed, options: TransfromOption
prop,
meta,
key,
uno,
})
}

function analyzeMeta(bundle: {
meta: CssValueParsed['meta']
key: string
prop: string
uno: UnoGenerator
}): string[] {
const { meta, key, prop } = bundle
const { meta, key, prop, uno } = bundle
const atomics: string[] = []

for (const m of meta) {
Expand All @@ -80,14 +85,86 @@ function analyzeMeta(bundle: {
else {
if (m.unit === 'px' && /^\d+$/.test(m.value as string)) {
if (nonTransfromPxProps.some(p => prop.includes(p))) {
atomics.push(`${key}-[${m.value}${m.unit}]`)
atomics.push(`${key}-${m.value}${m.unit}`)
}
else {
atomics.push(`${key}-${Number(m.value) / 4}`)
}
}
else if (isColorProp(prop)) {
const colorKey = analyzeColor(m.value as string, uno)
if (colorKey) {
atomics.push(`${key}-${colorKey}`)
}
else {
atomics.push(`${key}-[${m.value}]`)
}
}
}
}

return atomics
}

function isColorProp(prop: string): boolean {
return prop.includes('color') || ['fill', 'stroke'].includes(prop)
}

function analyzeColor(color: string, uno: UnoGenerator): string | undefined {
const hexable = (theme: object) => {
return Object.entries(theme).reduce((acc, [key, value]) => {
if (typeof value === 'string') {
try {
acc[key] = new Magicolor(value).hex()
}
catch {
acc[key] = value
}
}
else {
acc[key] = hexable(value)
}
return acc
}, {} as Colors)
}

if ((uno.config.theme as any).colors) {
const colors = hexable((uno.config.theme as any).colors)
let hexColor
try {
hexColor = new Magicolor(color).hex()
}
catch {
return
}
const paths = findColorPath(colors, hexColor)

if (paths && paths.length > 0) {
const last = paths[paths.length - 1]
if (last === 'DEFAULT') {
paths.pop()
}
return paths.join('-')
}

function findColorPath(colors: Colors, targetValue: string, path: string[] = []): string[] | undefined {
for (const key in colors) {
const value = colors[key]
const currentPath = [...path, key]

if (value === targetValue) {
return currentPath
}

if (typeof value === 'object' && value !== null) {
const result = findColorPath(value, targetValue, currentPath)
if (result) {
return result
}
}
}

return undefined
}
}
}
8 changes: 7 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import type { CssNode } from 'css-tree'

export type Arrayable<T> = T | T[]
export type Objectable<T> = Record<string, T>
export interface Colors {
[key: string]: Colors & { DEFAULT?: string } | string
}

export type Atomic = string
export type AtomicShort = string
export type AtomicComposed = [Atomic, AtomicShort?]
export type StaticPropAtomicMap = [string, Atomic | AtomicComposed]
export type DynamicPropAtomicMap = [RegExp, (match: RegExpMatchArray) => Atomic | [Atomic, AtomicShort] | undefined]
export type DynamicPropAtomicMap = [RegExp, (match: RegExpMatchArray) => Atomic | AtomicComposed | undefined]
export type PropsAtomicMap = StaticPropAtomicMap | DynamicPropAtomicMap

// Parser types
Expand Down
38 changes: 37 additions & 1 deletion test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Magicolor } from '@magic-color/core'

Check failure on line 1 in test/index.test.ts

View workflow job for this annotation

GitHub Actions / lint

'Magicolor' is defined but never used
import { createGenerator } from '@unocss/core'
import { parse } from 'css-tree'
import { describe, expect, it } from 'vitest'
import type { Declaration } from 'css-tree'
Expand Down Expand Up @@ -26,6 +28,26 @@ ul li {
}
`

const uno = createGenerator({
theme: {
colors: {
white: '#fff',
primary: '#007bff',
foo: {
DEFAULT: '#f00',
100: '#f0f',
200: '#0ff',
300: 'rgb(0 0 0)',
},
bar: {
foo: {
test: '#f00',
},
},
},
},
})

describe('should', () => {
function generateParsed(code: string) {
const ast = parse(code, {
Expand Down Expand Up @@ -172,20 +194,34 @@ describe('should', () => {
it('transfromParsed', () => {
expect(transfromParsed(
generateParsed('border-top: 1px solid #eee')!,
uno as any,
{ shortify: true },
)).toMatchInlineSnapshot(`
[
"b-t-[1px]",
"b-t-1px",
]
`)

expect(transfromParsed(
generateParsed('margin: 12px')!,
uno as any,
{ shortify: true },
)).toMatchInlineSnapshot(`
[
"m-3",
]
`)
})

it('color in theme', () => {
expect(transfromParsed(
generateParsed('color: foo')!,
uno as any,
{ shortify: true },
)).toMatchInlineSnapshot(`
[
"c-[foo]",
]
`)
})
})

0 comments on commit 755e21d

Please sign in to comment.