diff --git a/README.md b/README.md index f72a656c..e5280bd9 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Access thousands of icons as components **on-demand** universally. - ๐Ÿคน **Any** icon sets - 100+ popular sets with over 10,000 icons, logos, emojis, etc. Powered by [Iconify](https://github.com/iconify/iconify). - ๐Ÿ“ฆ **Major** build tools - Vite, Webpack, Rollup, Nuxt, etc. Powered by [unplugin](https://github.com/unjs/unplugin). - ๐Ÿชœ **Major** frameworks - Vanilla, Web Components, React, Vue 3, Vue 2, Solid, Svelte, and more. [Contribute](./src/core/compiles). + - ๐Ÿ—ƒ๏ธ **CSS** icons - Use icons as css stylesheets with or without any framework (experimental). - ๐Ÿฑ **Any** combinations of them! - โ˜๏ธ On-demand - Only bundle the icons you really use, while having all the options. - ๐Ÿ–จ SSR / SSG friendly - Ship the icons with your page, no more FOUC. @@ -650,6 +651,23 @@ IconsResolver({ ``` +### CSS Icons + +You can use svg icons as a `css stylesheet` (no framework required): +```ts +import '~icons/{collection}/{icon}.css' +``` + +then add an `html` element with a `class` with a name following the convention: `{collection}-{icon}`. + +For example: +```html + + +``` + ### Collection Aliases When using component resolver, you have to use the name of the collection that can be long or redundant: for example, diff --git a/examples/sveltekit/src/routes/index.svelte b/examples/sveltekit/src/routes/index.svelte index 3c390674..292bc088 100644 --- a/examples/sveltekit/src/routes/index.svelte +++ b/examples/sveltekit/src/routes/index.svelte @@ -1,4 +1,5 @@ + + diff --git a/examples/vite-web-components/index.html b/examples/vite-web-components/index.html index 75b9b4f7..2b7c63eb 100644 --- a/examples/vite-web-components/index.html +++ b/examples/vite-web-components/index.html @@ -6,10 +6,12 @@ + diff --git a/src/core/compilers/css.ts b/src/core/compilers/css.ts new file mode 100644 index 00000000..dda336dc --- /dev/null +++ b/src/core/compilers/css.ts @@ -0,0 +1,27 @@ +import { ResolvedOptions } from '../../types' +import { Compiler } from './types' + +export const CssCompiler = (( + svg: string, + collection: string, + icon: string, + options: ResolvedOptions, +) => { + let inlineSvg = svg.replace(/"/g, '\'') + + // we need to add the svg xml namespace + if (!inlineSvg.includes('http://www.w3.org/2000/svg')) + inlineSvg = inlineSvg.replace(' = { + 'css': CssCompiler, 'vue2': Vue2Compiler, 'vue3': Vue3Compiler, 'solid': SolidCompiler, diff --git a/src/core/custom.ts b/src/core/custom.ts index d19af723..16c491d0 100644 --- a/src/core/custom.ts +++ b/src/core/custom.ts @@ -25,8 +25,20 @@ export async function getCustomIcon( const scale = options?.scale || 1 if (result) { - if (!result.startsWith(' 0) console.warn(`Custom icon "${icon}" in "${collection}" is not a valid SVG`) + // prevent adding width and height twice on css + if (options?.compiler === 'css' && startSvg > -1) { + // if width or height are set we assume both are set + const closeSvg = result.indexOf('>', startSvg + 5) + let idx = result.indexOf('width') + if (idx > -1 && closeSvg > idx) + return result + idx = result.indexOf('height', startSvg + 5) + if (idx > -1 && closeSvg > idx) + return result + } return result.replace(' jsx = guessJSX(), customCollections = {}, autoInstall = false, + css = {}, } = options const webComponents = Object.assign({ @@ -31,6 +32,7 @@ export async function resolveOptions(options: Options): Promise jsx, webComponents, autoInstall, + css, } } diff --git a/src/index.ts b/src/index.ts index 67a3f875..16dc8d8c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,9 @@ const unplugin = createUnplugin((options = {}) => { enforce: 'pre', resolveId(id) { if (isIconPath(id)) { + if (id.endsWith('.css')) + return id + const res = normalizeIconPath(id) .replace(/\.\w+$/i, '') .replace(/^\//, '') @@ -32,7 +35,22 @@ const unplugin = createUnplugin((options = {}) => { async load(id) { const config = await resolved if (isIconPath(id)) { - const code = await generateComponentFromPath(id, config) || null + let code: string | null = null + const css = id.endsWith('.css') + if (css) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + code = await generateComponentFromPath(id, { + ...config, + compiler: 'css', + scale: options.css?.scale ?? 1.2, + defaultStyle: options.css?.defaultStyles || '', + defaultClass: '', + }) || null + } + else { + code = await generateComponentFromPath(id, config) || null + } + if (code) { return { code, diff --git a/src/types.ts b/src/types.ts index 1a2d7a24..5227795b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -45,7 +45,7 @@ export interface Options { * * @default (detect automatically, fallback to 'vue3') */ - compiler?: 'vue2' | 'vue3' | 'jsx' | 'solid' | 'svelte' | 'web-components' | 'marko' | 'none' | 'raw' + compiler?: 'vue2' | 'vue3' | 'jsx' | 'solid' | 'svelte' | 'web-components' | 'marko' | 'css' | 'none' | 'raw' /** * JSX style, works only when compiler set to `jsx` @@ -71,6 +71,24 @@ export interface Options { iconPrefix?: string } + /** + * Config for CSS compiler + */ + css?: { + /** + * Scale of icons against 1em + * + * @default 1.2 + */ + scale?: number + /** + * Styles to apply to CSS icons by default + * + * @default '' + */ + defaultStyles?: string + } + /** * @deprecated no longer needed */ diff --git a/types/css.d.ts b/types/css.d.ts new file mode 100644 index 00000000..8b590316 --- /dev/null +++ b/types/css.d.ts @@ -0,0 +1,9 @@ +declare module 'virtual:icons/*.css' { + const css: string + export default css +} + +declare module '~icons/*.css' { + const css: string + export default css +}