From 0b36a603946e4d08ab6402bebea4a3cc1246a701 Mon Sep 17 00:00:00 2001 From: Chris <1633711653@qq.com> Date: Thu, 9 May 2024 03:18:47 +0800 Subject: [PATCH] feat(preset-icons): add svg props customization (#3774) Co-authored-by: Anthony Fu --- packages/preset-icons/src/core.ts | 14 ++++++++-- packages/preset-icons/src/types.ts | 28 +++++++++++++++++-- .../output/preset-icons-propsProcessor.css | 6 ++++ test/preset-icons.test.ts | 18 ++++++++++++ 4 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 test/assets/output/preset-icons-propsProcessor.css diff --git a/packages/preset-icons/src/core.ts b/packages/preset-icons/src/core.ts index 360fa3898d..706f23d078 100644 --- a/packages/preset-icons/src/core.ts +++ b/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, @@ -29,6 +30,7 @@ export function createPresetIcons(lookupIconLoader: (options: IconsOptions) => P collectionsNodeResolvePath, layer = 'icons', unit, + processor, } = options const flags = getEnvFlags() @@ -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 @@ -95,6 +98,7 @@ export function createPresetIcons(lookupIconLoader: (options: IconsOptions) => P return } + let cssObject: CSSObject const url = `url("data:image/svg+xml;utf8,${encodeSvgForCss(svg)}")` if (_mode === 'auto') @@ -102,7 +106,7 @@ export function createPresetIcons(lookupIconLoader: (options: IconsOptions) => P 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', @@ -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 }, ]], diff --git a/packages/preset-icons/src/types.ts b/packages/preset-icons/src/types.ts index c192af08e4..989a9ce29f 100644 --- a/packages/preset-icons/src/types.ts +++ b/packages/preset-icons/src/types.ts @@ -1,7 +1,14 @@ 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). @@ -9,6 +16,7 @@ export interface IconsOptions { * @default 1 */ scale?: number + /** * Mode of generated CSS icons. * @@ -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 + /** * 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 Awaitable) | undefined | CustomIconLoader | InlineCollection> + /** * Rule layer * * @default 'icons' */ layer?: string + /** * Custom icon customizations. */ customizations?: Omit + /** * Auto install icon sources package when the usages is detected * @@ -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 `/` * @@ -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 + + /** + * Processor for the CSS object before stringify + */ + processor?: (cssObject: CSSObject, meta: Required) => void } diff --git a/test/assets/output/preset-icons-propsProcessor.css b/test/assets/output/preset-icons-propsProcessor.css new file mode 100644 index 0000000000..1ea58c24da --- /dev/null +++ b/test/assets/output/preset-icons-propsProcessor.css @@ -0,0 +1,6 @@ +/* layer: icons */ +.dark .dark\:i-carbon-moon, +.dark .dark\:i-carbon-moon\?auto{--un-icon: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='M13.503 5.414a15.076 15.076 0 0 0 11.593 18.194a11.113 11.113 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.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 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");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1em;height:1em;} +.dark .dark\:i-carbon-moon\?bg{background: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='M13.503 5.414a15.076 15.076 0 0 0 11.593 18.194a11.113 11.113 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.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 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") no-repeat;background-size:100% 100%;background-color:transparent;} +.i-carbon-sun{--un-icon: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");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1em;height:1em;} +.i-carbon-sun\?bg{background: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") no-repeat;background-size:100% 100%;background-color:transparent;} \ No newline at end of file diff --git a/test/preset-icons.test.ts b/test/preset-icons.test.ts index a0633fcdfe..0df0755df8 100644 --- a/test/preset-icons.test.ts +++ b/test/preset-icons.test.ts @@ -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') + }) })