Skip to content

Commit

Permalink
feat(preset-icons): add svg props customization (#3774)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <github@antfu.me>
  • Loading branch information
zyyv and antfu committed May 8, 2024
1 parent 249fe73 commit 0b36a60
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 5 deletions.
14 changes: 11 additions & 3 deletions packages/preset-icons/src/core.ts
@@ -1,3 +1,4 @@
import type { CSSObject } from '@unocss/core'
import { definePreset, warnOnce } from '@unocss/core'
import type {
IconifyLoaderOptions,
Expand Down Expand Up @@ -29,6 +30,7 @@ export function createPresetIcons(lookupIconLoader: (options: IconsOptions) => P
collectionsNodeResolvePath,
layer = 'icons',
unit,
processor,
} = options

const flags = getEnvFlags()
Expand Down Expand Up @@ -66,7 +68,8 @@ export function createPresetIcons(lookupIconLoader: (options: IconsOptions) => P
layers: { icons: -30 },
rules: [[
/^([a-z0-9:_-]+)(?:\?(mask|bg|auto))?$/,
async ([full, body, _mode = mode]) => {
async (matcher) => {
let [full, body, _mode = mode] = matcher as [string, string, IconsOptions['mode']]
let collection = ''
let name = ''
let svg: string | undefined
Expand Down Expand Up @@ -95,14 +98,15 @@ export function createPresetIcons(lookupIconLoader: (options: IconsOptions) => P
return
}

let cssObject: CSSObject
const url = `url("data:image/svg+xml;utf8,${encodeSvgForCss(svg)}")`

if (_mode === 'auto')
_mode = svg.includes('currentColor') ? 'mask' : 'bg'

if (_mode === 'mask') {
// Thanks to https://codepen.io/noahblon/post/coloring-svgs-in-css-background-images
return {
cssObject = {
'--un-icon': url,
'-webkit-mask': 'var(--un-icon) no-repeat',
'mask': 'var(--un-icon) no-repeat',
Expand All @@ -115,13 +119,17 @@ export function createPresetIcons(lookupIconLoader: (options: IconsOptions) => P
}
}
else {
return {
cssObject = {
'background': `${url} no-repeat`,
'background-size': '100% 100%',
'background-color': 'transparent',
...usedProps,
}
}

processor?.(cssObject, { collection, icon: name, svg, mode: _mode })

return cssObject
},
{ layer, prefix },
]],
Expand Down
28 changes: 26 additions & 2 deletions packages/preset-icons/src/types.ts
@@ -1,14 +1,22 @@
import type { CustomIconLoader, IconCustomizations, InlineCollection } from '@iconify/utils/lib/loader/types'
import type { Awaitable } from '@unocss/core'
import type { Awaitable, CSSObject } from '@unocss/core'
import type { IconifyJSON } from '@iconify/types'

interface IconMeta {
collection: string
icon: string
svg: string
mode?: IconsOptions['mode']
}

export interface IconsOptions {
/**
* Scale related to the current font size (1em).
*
* @default 1
*/
scale?: number

/**
* Mode of generated CSS icons.
*
Expand All @@ -19,40 +27,47 @@ export interface IconsOptions {
* @default 'auto'
* @see https://antfu.me/posts/icons-in-pure-css
*/
mode?: 'mask' | 'background-img' | 'auto'
mode?: 'mask' | 'bg' | 'auto'

/**
* Class prefix for matching icon rules.
*
* @default `i-`
*/
prefix?: string | string[]

/**
* Extra CSS properties applied to the generated CSS
*
* @default {}
*/
extraProperties?: Record<string, string>

/**
* Emit warning when missing icons are matched
*
* @default false
*/
warn?: boolean

/**
* In Node.js environment, the preset will search for the installed iconify dataset automatically.
* When using in the browser, this options is provided to provide dataset with custom loading mechanism.
*/
collections?: Record<string, (() => Awaitable<IconifyJSON>) | undefined | CustomIconLoader | InlineCollection>

/**
* Rule layer
*
* @default 'icons'
*/
layer?: string

/**
* Custom icon customizations.
*/
customizations?: Omit<IconCustomizations, 'additionalProps' | 'trimCustomSvg'>

/**
* Auto install icon sources package when the usages is detected
*
Expand All @@ -61,18 +76,21 @@ export interface IconsOptions {
* @default false
*/
autoInstall?: boolean

/**
* Path to resolve the iconify collections in Node.js environment.
*
* @default process.cwd()
*/
collectionsNodeResolvePath?: string

/**
* Custom icon unit.
*
* @default `em`
*/
unit?: string

/**
* Load icons from CDN. Should starts with `https://` and ends with `/`
*
Expand All @@ -81,8 +99,14 @@ export interface IconsOptions {
* - https://cdn.skypack.dev/
*/
cdn?: string

/**
* Custom fetch function to provide the icon data.
*/
customFetch?: (url: string) => Promise<any>

/**
* Processor for the CSS object before stringify
*/
processor?: (cssObject: CSSObject, meta: Required<IconMeta>) => void
}
6 changes: 6 additions & 0 deletions test/assets/output/preset-icons-propsProcessor.css

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

18 changes: 18 additions & 0 deletions test/preset-icons.test.ts
Expand Up @@ -62,4 +62,22 @@ describe('preset-icons', () => {
expect(css).toContain('data:image/svg+xml;utf8,%3Csvg')
await expect(css).toMatchFileSnapshot('./assets/output/preset-icons-unit-svg-prologue.css')
})

it('custom the usedProps in propsProcessor', async () => {
const uno = createGenerator({
presets: [
presetUno(),
presetIcons({
processor(props, { mode }) {
if (mode === 'bg') {
delete props.width
delete props.height
}
},
}),
],
})
const { css } = await uno.generate(fixtures.join(' '), { preflights: false })
await expect(css).toMatchFileSnapshot('./assets/output/preset-icons-propsProcessor.css')
})
})

0 comments on commit 0b36a60

Please sign in to comment.