Skip to content

Commit c6c4bbd

Browse files
authored
fix(transformer-directive): encode colorstring & support theme color (#4184)
1 parent c9d2e01 commit c6c4bbd

4 files changed

Lines changed: 54 additions & 21 deletions

File tree

docs/transformers/directives.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,14 +277,16 @@ Cause the icon uses `currentColor` as the fill color by default, if you want to
277277
```css
278278
.icon {
279279
background-image: icon('i-carbon-moon', '#fff');
280+
background-image: icon('i-carbon-moon', 'theme("colors.red.500")'); /* use theme color */
280281
}
281282
```
282283

283284
Will be compiled to:
284285

285286
```css
286287
.icon {
287-
background-image: url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='#fff' d='M13.503 5.414a15.076 15.076 0 0 0 11.593 18.194a11.1 11.1 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1 1 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.07 13.07 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3'/%3E%3C/svg%3E");
288+
background-image: url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='%23fff' d='M13.503 5.414a15.076 15.076 0 0 0 11.593 18.194a11.1 11.1 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1 1 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.07 13.07 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3'/%3E%3C/svg%3E");
289+
background-image: url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='%23ef4444' d='M13.503 5.414a15.076 15.076 0 0 0 11.593 18.194a11.1 11.1 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1 1 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.07 13.07 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3'/%3E%3C/svg%3E");
288290
}
289291
```
290292

packages/transformer-directives/src/functions.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { FunctionNode, StringNode } from 'css-tree'
22
import type { TransformerDirectivesContext } from './types'
3-
import { transformThemeString } from '@unocss/rule-utils'
3+
import { transformThemeFn, transformThemeString } from '@unocss/rule-utils'
44
import { transformIconString } from './icon'
55

66
export async function handleFunction({ code, uno, options }: TransformerDirectivesContext, node: FunctionNode) {
@@ -24,7 +24,12 @@ export async function handleFunction({ code, uno, options }: TransformerDirectiv
2424
if (params.length === 0)
2525
throw new Error('icon() expects at least one argument')
2626

27-
const value = await transformIconString(uno, ...(params as [string, string]))
27+
let [icon, color] = params as [string, string?]
28+
if (color) {
29+
color = encodeURIComponent(transformThemeFn(color, uno.config.theme, throwOnMissing))
30+
}
31+
32+
const value = await transformIconString(uno, icon, color)
2833

2934
if (value)
3035
code.overwrite(node.loc!.start.offset, node.loc!.end.offset, value)

packages/transformer-directives/src/icon.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { IconsAPI, IconsOptions } from '@unocss/preset-icons'
44
import { toArray } from '@unocss/core'
55

66
export async function transformIconString(uno: UnoGenerator, icon: string, color?: string) {
7-
const presetIcons = uno.userConfig.presets?.flat()?.findLast(i => i.name === '@unocss/preset-icons') as Preset | undefined
7+
const presetIcons = uno.userConfig.presets?.flat()?.find(i => i.name === '@unocss/preset-icons') as Preset | undefined
88

99
if (!presetIcons) {
1010
console.warn('@unocss/preset-icons not found, icon() directive will be keep as-is')
@@ -15,7 +15,6 @@ export async function transformIconString(uno: UnoGenerator, icon: string, color
1515
scale = 1,
1616
prefix = 'i-',
1717
collections: customCollections,
18-
extraProperties = {},
1918
customizations = {},
2019
autoInstall = false,
2120
collectionsNodeResolvePath,
@@ -34,7 +33,6 @@ export async function transformIconString(uno: UnoGenerator, icon: string, color
3433
warn: undefined,
3534
customizations: {
3635
...customizations,
37-
additionalProps: { ...extraProperties },
3836
trimCustomSvg: true,
3937
async iconCustomizer(collection, icon, props) {
4038
await customizations.iconCustomizer?.(collection, icon, props)

test/transformer-directives.test.ts

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { UnoGenerator } from '@unocss/core'
2+
import type { IconsOptions } from '@unocss/preset-icons'
23
import { readFile } from 'node:fs/promises'
3-
import { createGenerator } from '@unocss/core'
4+
import { createGenerator, mergeDeep } from '@unocss/core'
45
import presetIcons from '@unocss/preset-icons'
56
import presetUno from '@unocss/preset-uno'
67
import MagicString from 'magic-string'
@@ -1361,19 +1362,24 @@ div {
13611362
})
13621363

13631364
describe('icon directive', () => {
1364-
const uno = createGenerator({
1365-
presets: [
1366-
presetIcons({
1367-
collections: {
1368-
ph: {
1369-
check: `<svg xmlns="http://www.w3.org/2000/svg" fill="currentcolor" viewBox="0 0 24 24"><path d="ph:check"/></svg>`,
1370-
},
1365+
function createUno(iconsOptions?: IconsOptions) {
1366+
const defaultOptions = {
1367+
collections: {
1368+
ph: {
1369+
check: `<svg xmlns="http://www.w3.org/2000/svg" fill="currentcolor" viewBox="0 0 24 24"><path d="ph:check"/></svg>`,
13711370
},
1372-
}),
1373-
],
1374-
})
1371+
},
1372+
}
13751373

1376-
async function transform(code: string, _uno: UnoGenerator = uno) {
1374+
return createGenerator({
1375+
presets: [
1376+
presetUno(),
1377+
presetIcons(mergeDeep(defaultOptions, iconsOptions ?? {})),
1378+
],
1379+
})
1380+
}
1381+
1382+
async function transform(code: string, _uno: UnoGenerator) {
13771383
const s = new MagicString(code)
13781384
await transformDirectives(s, _uno, {})
13791385
return prettier.format(s.toString(), {
@@ -1383,24 +1389,46 @@ describe('icon directive', () => {
13831389
}
13841390

13851391
it('icon()', async () => {
1392+
const uno = createUno()
1393+
13861394
const result = await transform(
13871395
`.icon {
13881396
background-image: icon('i-ph-check');
13891397
background-image: icon('i-ph:check', '#fff') no-repeat;
1398+
background-image: icon('i-ph:check', 'theme("colors.red.500")');
13901399
background-image: icon('i-carbon-sun');
1391-
background-image: icon('i-carbon:moon', '#fff');
13921400
}`,
1401+
uno,
13931402
)
13941403

13951404
expect(result).toMatchInlineSnapshot(`
13961405
".icon {
13971406
background-image: url("data:image/svg+xml;utf8,%3Csvg width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' fill='currentcolor' viewBox='0 0 24 24'%3E%3Cpath d='ph:check'/%3E%3C/svg%3E");
1398-
background-image: url("data:image/svg+xml;utf8,%3Csvg width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' fill='#fff' viewBox='0 0 24 24'%3E%3Cpath d='ph:check'/%3E%3C/svg%3E")
1407+
background-image: url("data:image/svg+xml;utf8,%3Csvg width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 24 24'%3E%3Cpath d='ph:check'/%3E%3C/svg%3E")
13991408
no-repeat;
1409+
background-image: url("data:image/svg+xml;utf8,%3Csvg width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' fill='%23ef4444' viewBox='0 0 24 24'%3E%3Cpath d='ph:check'/%3E%3C/svg%3E");
14001410
background-image: url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M16 12.005a4 4 0 1 1-4 4a4.005 4.005 0 0 1 4-4m0-2a6 6 0 1 0 6 6a6 6 0 0 0-6-6M5.394 6.813L6.81 5.399l3.505 3.506L8.9 10.319zM2 15.005h5v2H2zm3.394 10.193L8.9 21.692l1.414 1.414l-3.505 3.506zM15 25.005h2v5h-2zm6.687-1.9l1.414-1.414l3.506 3.506l-1.414 1.414zm3.313-8.1h5v2h-5zm-3.313-6.101l3.506-3.506l1.414 1.414l-3.506 3.506zM15 2.005h2v5h-2z'/%3E%3C/svg%3E");
1401-
background-image: url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='#fff' d='M13.503 5.414a15.076 15.076 0 0 0 11.593 18.194a11.1 11.1 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1 1 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.07 13.07 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3'/%3E%3C/svg%3E");
14021411
}
14031412
"
14041413
`)
14051414
})
1415+
1416+
it('icon() without extra properties', async () => {
1417+
const uno = createUno({
1418+
extraProperties: {
1419+
'display': 'inline-block',
1420+
'vertical-align': 'middle',
1421+
},
1422+
})
1423+
1424+
const result = await transform(
1425+
`.icon {
1426+
background-image: icon('i-ph-check');
1427+
}`,
1428+
uno,
1429+
)
1430+
1431+
expect(result).not.toContain(`display='inline-block'`)
1432+
expect(result).not.toContain(`vertical-align='middle'`)
1433+
})
14061434
})

0 commit comments

Comments
 (0)