mirror of
https://github.com/iconify/iconify.git
synced 2025-01-04 14:35:20 +00:00
feat: dynamic selectors plugin for Tailwind CSS 4
This commit is contained in:
parent
bfdd3f021d
commit
fdb8263c46
3
plugins-demo/tailwind-demo/.gitignore
vendored
3
plugins-demo/tailwind-demo/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
dist
|
@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "@iconify-demo/tailwind",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "tailwindcss -i ./src/input.css -o ./dist/output.css"
|
||||
},
|
||||
"keywords": [],
|
||||
"devDependencies": {
|
||||
"@iconify-json/fa6-regular": "^1.2.2",
|
||||
"@iconify-json/mdi-light": "^1.2.1",
|
||||
"@iconify-json/vscode-icons": "^1.2.3",
|
||||
"@iconify/tailwind": "workspace:*",
|
||||
"@iconify/tools": "3.0.0-beta.3",
|
||||
"tailwindcss": "^3.4.16"
|
||||
}
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link href="../dist/output.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body class="m-0 p-2">
|
||||
<section class="mb-2">
|
||||
<h1 class="text-2xl">Main plugin</h1>
|
||||
<p>
|
||||
Monotone icons (changes color, blue):
|
||||
<span class="text-2xl demo">
|
||||
<span class="iconify mdi-light--home"></span>
|
||||
<span
|
||||
class="iconify mdi-light--arrow-left text-blue-600"
|
||||
></span>
|
||||
<span
|
||||
class="iconify vscode-icons--file-type-light-ini text-blue-600"
|
||||
></span>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Color icons:
|
||||
<span class="text-2xl demo">
|
||||
<span class="iconify-color mdi-light--home"></span>
|
||||
<span
|
||||
class="iconify-color vscode-icons--file-type-firebase"
|
||||
></span>
|
||||
<span
|
||||
class="iconify-color vscode-icons--file-type-js-official"
|
||||
></span>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Using width/height to resize icon:
|
||||
<span
|
||||
class="iconify-nosize mdi-light--home h-12 w-12 text-blue-600"
|
||||
></span>
|
||||
<span
|
||||
class="iconify mdi-light--home h-12 w-12 text-red-600"
|
||||
></span>
|
||||
</p>
|
||||
<p>
|
||||
Scaled non-square icons:
|
||||
<span
|
||||
class="fa6-mask fa6-regular-bookmark text-blue-600"
|
||||
></span>
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-2">
|
||||
<h1 class="text-2xl">Dynamic selectors plugin, custom options</h1>
|
||||
<p>Icons should scale to 1.5em, which is about 24px</p>
|
||||
<p>
|
||||
Monotone icons (red, blue):
|
||||
<span
|
||||
class="custom-monotone i-mdi-light-home text-red-600"
|
||||
></span>
|
||||
<span
|
||||
class="custom-monotone i-custom-spinner1 text-blue-600"
|
||||
></span>
|
||||
</p>
|
||||
<p>
|
||||
Failed icon (should not be pre-rendered):
|
||||
<span
|
||||
class="custom-monotone i-mdi-light-arrow-left text-red-600"
|
||||
></span>
|
||||
</p>
|
||||
<p>
|
||||
Colored icons:
|
||||
<span class="custom-background i-mdi-light-home"></span>
|
||||
<span class="custom-background i-custom-spinner2"></span>
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-2">
|
||||
<h1 class="text-2xl">Dynamic selectors plugin</h1>
|
||||
<p>
|
||||
Few icons that change color on hover (first icon also changes
|
||||
icon on hover):
|
||||
<span class="text-2xl demo">
|
||||
<span
|
||||
class="icon-[mdi-light--arrow-left] hover:icon-hover-[mdi-light--arrow-right]"
|
||||
></span>
|
||||
<span class="icon-[mdi-light--forum]"></span>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Custom icons, imported from "svg" directory (first icon's
|
||||
animation slowed down using "customise" option):
|
||||
<span class="text-2xl demo">
|
||||
<span class="icon-[custom--spinner1]"></span>
|
||||
<span class="icon-[custom--spinner2]"></span>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Icons with hardcoded palette:
|
||||
<span class="text-3xl demo">
|
||||
<span
|
||||
class="icon-[vscode-icons--file-type-access] hover:icon-hover-[vscode-icons--file-type-access2]"
|
||||
></span>
|
||||
<span class="icon-[vscode-icons--file-type-vue]"></span>
|
||||
</span>
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-2">
|
||||
<h1 class="text-2xl">Clean selectors plugin (deprecated)</h1>
|
||||
<p>
|
||||
Monotone icon: 1em size (changing color), text-2xl size (red):
|
||||
<span class="icon--mdi-light icon--mdi-light--home demo"></span>
|
||||
<span
|
||||
class="icon--mdi-light icon--mdi-light--home text-2xl text-red-600"
|
||||
></span>
|
||||
</p>
|
||||
<p>
|
||||
Custom size set via width/height:
|
||||
<span
|
||||
class="h-12 w-12 scaled-icon-[mdi-light--forum] text-red-600"
|
||||
></span>
|
||||
</p>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
@ -1,11 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.demo {
|
||||
color: #16a34a;
|
||||
vertical-align: -6px;
|
||||
}
|
||||
.demo:hover {
|
||||
color: #b91c1c;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<style>
|
||||
.spin-path {
|
||||
animation: 0.75s linear infinite rotate;
|
||||
transform-origin: center;
|
||||
}
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg)
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg)
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<path d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25"/>
|
||||
<path class="spin-path" d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z" />
|
||||
</svg>
|
Before Width: | Height: | Size: 725 B |
@ -1,14 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="3.5" r="1.5" fill="currentColor" opacity="0">
|
||||
<animateTransform attributeName="transform" calcMode="discrete" dur="2.4s" repeatCount="indefinite" type="rotate" values="0 12 12;90 12 12;180 12 12;270 12 12"/>
|
||||
<animate attributeName="opacity" dur="0.6s" keyTimes="0;0.5;1" repeatCount="indefinite" values="1;1;0"/>
|
||||
</circle>
|
||||
<circle cx="12" cy="3.5" r="1.5" fill="currentColor" opacity="0">
|
||||
<animateTransform attributeName="transform" begin="0.2s" calcMode="discrete" dur="2.4s" repeatCount="indefinite" type="rotate" values="30 12 12;120 12 12;210 12 12;300 12 12"/>
|
||||
<animate attributeName="opacity" begin="0.2s" dur="0.6s" keyTimes="0;0.5;1" repeatCount="indefinite" values="1;1;0"/>
|
||||
</circle>
|
||||
<circle cx="12" cy="3.5" r="1.5" fill="currentColor" opacity="0">
|
||||
<animateTransform attributeName="transform" begin="0.4s" calcMode="discrete" dur="2.4s" repeatCount="indefinite" type="rotate" values="60 12 12;150 12 12;240 12 12;330 12 12"/>
|
||||
<animate attributeName="opacity" begin="0.4s" dur="0.6s" keyTimes="0;0.5;1" repeatCount="indefinite" values="1;1;0"/>
|
||||
</circle>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,132 +0,0 @@
|
||||
const {
|
||||
addCleanIconSelectors,
|
||||
addDynamicIconSelectors,
|
||||
addIconSelectors,
|
||||
} = require('@iconify/tailwind');
|
||||
const {
|
||||
importDirectorySync,
|
||||
cleanupSVG,
|
||||
parseColorsSync,
|
||||
runSVGO,
|
||||
isEmptyColor,
|
||||
} = require('@iconify/tools');
|
||||
|
||||
// Import icons from directory 'svg'
|
||||
const customSet = importDirectorySync('svg');
|
||||
|
||||
// Clean up all icons
|
||||
customSet.forEachSync((name, type) => {
|
||||
if (type !== 'icon') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get SVG object for icon
|
||||
const svg = customSet.toSVG(name);
|
||||
if (!svg) {
|
||||
// Invalid icon
|
||||
customSet.remove(name);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Clean up icon
|
||||
cleanupSVG(svg);
|
||||
|
||||
// This is a monotone icon, change color to `currentColor`, add it if missing
|
||||
// Skip this step if icons have palette
|
||||
parseColorsSync(svg, {
|
||||
defaultColor: 'currentColor',
|
||||
callback: (attr, colorStr, color) => {
|
||||
return !color || isEmptyColor(color)
|
||||
? colorStr
|
||||
: 'currentColor';
|
||||
},
|
||||
});
|
||||
|
||||
// Optimise icon
|
||||
runSVGO(svg);
|
||||
} catch (err) {
|
||||
// Something went wrong when parsing icon: remove it
|
||||
console.error(`Error parsing ${name}:`, err);
|
||||
customSet.remove(name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update icon in icon set from SVG object
|
||||
customSet.fromSVG(name, svg);
|
||||
});
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./src/*.html'],
|
||||
plugins: [
|
||||
// Main plugin, default options
|
||||
addIconSelectors(['mdi-light', 'vscode-icons']),
|
||||
// Main plugin, custom options
|
||||
addIconSelectors({
|
||||
maskSelector: '.custom-monotone',
|
||||
backgroundSelector: '.custom-background',
|
||||
// Like UnoCSS
|
||||
iconSelector: '.i-{prefix}-{name}',
|
||||
scale: 1.5,
|
||||
prefixes: [
|
||||
{
|
||||
prefix: 'mdi-light',
|
||||
icons: ['home'],
|
||||
customise: (content) =>
|
||||
content.replace(/currentColor/g, '#40f'),
|
||||
},
|
||||
{
|
||||
prefix: 'custom',
|
||||
source: customSet.export(),
|
||||
customise: (content) =>
|
||||
content.replace(/currentColor/g, '#f20'),
|
||||
},
|
||||
],
|
||||
}),
|
||||
// Main plugin, no size
|
||||
addIconSelectors({
|
||||
maskSelector: '.iconify-nosize',
|
||||
backgroundSelector: '',
|
||||
scale: 0,
|
||||
// No prefixes list: reusing data from plugin above
|
||||
}),
|
||||
// Main plugin, no square and scale
|
||||
addIconSelectors({
|
||||
maskSelector: '.fa6-mask',
|
||||
backgroundSelector: '.fa6-bg', // unused
|
||||
iconSelector: '.{prefix}-{name}',
|
||||
square: false,
|
||||
scale: 2,
|
||||
prefixes: ['fa6-regular'],
|
||||
}),
|
||||
// Plugin with clean selectors: requires writing all used icons in first parameter
|
||||
addCleanIconSelectors(['mdi-light:home']),
|
||||
// Plugin with dynamic selectors
|
||||
addDynamicIconSelectors({
|
||||
iconSets: {
|
||||
custom: customSet.export(),
|
||||
},
|
||||
customise: (content, name, prefix) => {
|
||||
switch (name) {
|
||||
case 'spinner1':
|
||||
return content.replace(
|
||||
'animation:0.75s',
|
||||
'animation:5s'
|
||||
);
|
||||
}
|
||||
return content;
|
||||
},
|
||||
}),
|
||||
// Plugin with dynamic selectors that contains only css for overriding icon
|
||||
addDynamicIconSelectors({
|
||||
prefix: 'icon-hover',
|
||||
overrideOnly: true,
|
||||
}),
|
||||
// Icons without size
|
||||
addDynamicIconSelectors({
|
||||
prefix: 'scaled-icon',
|
||||
scale: 0,
|
||||
}),
|
||||
],
|
||||
};
|
30
plugins-demo/tailwind4-demo/.gitignore
vendored
Normal file
30
plugins-demo/tailwind4-demo/.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
3
plugins-demo/tailwind4-demo/.vscode/extensions.json
vendored
Normal file
3
plugins-demo/tailwind4-demo/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
33
plugins-demo/tailwind4-demo/README.md
Normal file
33
plugins-demo/tailwind4-demo/README.md
Normal file
@ -0,0 +1,33 @@
|
||||
# tailwind4-demo
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
1
plugins-demo/tailwind4-demo/env.d.ts
vendored
Normal file
1
plugins-demo/tailwind4-demo/env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
13
plugins-demo/tailwind4-demo/index.html
Normal file
13
plugins-demo/tailwind4-demo/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tailwind CSS Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
33
plugins-demo/tailwind4-demo/package.json
Normal file
33
plugins-demo/tailwind4-demo/package.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "tailwind4-demo",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/tailwind4": "workspace:*",
|
||||
"@iconify-json/fa6-regular": "^1.2.2",
|
||||
"@iconify-json/mdi-light": "^1.2.1",
|
||||
"@iconify-json/vscode-icons": "^1.2.6",
|
||||
"@tailwindcss/vite": "4.0.0-beta.8",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/node": "^22.9.3",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"npm-run-all2": "^7.0.1",
|
||||
"tailwindcss": "4.0.0-beta.8",
|
||||
"typescript": "~5.6.3",
|
||||
"vite": "^6.0.1",
|
||||
"vite-plugin-vue-devtools": "^7.6.5",
|
||||
"vue-tsc": "^2.1.10"
|
||||
}
|
||||
}
|
BIN
plugins-demo/tailwind4-demo/public/favicon.ico
Normal file
BIN
plugins-demo/tailwind4-demo/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
27
plugins-demo/tailwind4-demo/src/App.vue
Normal file
27
plugins-demo/tailwind4-demo/src/App.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div class="container mx-auto">
|
||||
<h1 class="text-4xl font-bold text-left mt-10">
|
||||
<span class="icon-[vscode-icons--file-type-tailwind]"></span>
|
||||
Tailwind CSS test file
|
||||
</h1>
|
||||
<section class="mt-2 mb-2">
|
||||
<h1 class="text-2xl">Dynamic selectors plugin</h1>
|
||||
<p>
|
||||
Monotone icons, arrow changes on hover:
|
||||
<span class="text-2xl demo">
|
||||
<span
|
||||
class="icon-[mdi-light--arrow-left] hover:icon-[mdi-light--arrow-right]"
|
||||
></span>
|
||||
<span class="icon-[mdi-light--forum]"></span>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Icons with hardcoded palette:
|
||||
<span class="text-3xl demo">
|
||||
<span class="icon-[vscode-icons--file-type-access]"></span>
|
||||
<span class="icon-[vscode-icons--file-type-vue]"></span>
|
||||
</span>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
10
plugins-demo/tailwind4-demo/src/assets/main.css
Normal file
10
plugins-demo/tailwind4-demo/src/assets/main.css
Normal file
@ -0,0 +1,10 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@plugin "@iconify/tailwind4";
|
||||
|
||||
.demo {
|
||||
color: var(--color-blue-600);
|
||||
}
|
||||
.demo:hover {
|
||||
color: var(--color-red-600);
|
||||
}
|
6
plugins-demo/tailwind4-demo/src/main.ts
Normal file
6
plugins-demo/tailwind4-demo/src/main.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import './assets/main.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
13
plugins-demo/tailwind4-demo/tsconfig.app.json
Normal file
13
plugins-demo/tailwind4-demo/tsconfig.app.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
11
plugins-demo/tailwind4-demo/tsconfig.json
Normal file
11
plugins-demo/tailwind4-demo/tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
19
plugins-demo/tailwind4-demo/tsconfig.node.json
Normal file
19
plugins-demo/tailwind4-demo/tsconfig.node.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
15
plugins-demo/tailwind4-demo/vite.config.ts
Normal file
15
plugins-demo/tailwind4-demo/vite.config.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import vueDevTools from 'vite-plugin-vue-devtools';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), vue(), vueDevTools()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
});
|
@ -1,98 +0,0 @@
|
||||
/* eslint-disable */
|
||||
const fs = require('fs');
|
||||
const child_process = require('child_process');
|
||||
|
||||
// List of commands to run
|
||||
const commands = [];
|
||||
|
||||
// Parse command line
|
||||
const compile = {
|
||||
lib: true,
|
||||
dist: true,
|
||||
api: true,
|
||||
};
|
||||
process.argv.slice(2).forEach((cmd) => {
|
||||
if (cmd.slice(0, 2) !== '--') {
|
||||
return;
|
||||
}
|
||||
const parts = cmd.slice(2).split('-');
|
||||
if (parts.length === 2) {
|
||||
// Parse 2 part commands like --with-lib
|
||||
const key = parts.pop();
|
||||
if (compile[key] === void 0) {
|
||||
return;
|
||||
}
|
||||
switch (parts.shift()) {
|
||||
case 'with':
|
||||
// enable module
|
||||
compile[key] = true;
|
||||
break;
|
||||
|
||||
case 'without':
|
||||
// disable module
|
||||
compile[key] = false;
|
||||
break;
|
||||
|
||||
case 'only':
|
||||
// disable other modules
|
||||
Object.keys(compile).forEach((key2) => {
|
||||
compile[key2] = key2 === key;
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Check if required modules in same monorepo are available
|
||||
const fileExists = (file) => {
|
||||
try {
|
||||
fs.statSync(file);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (compile.dist && !fileExists('./lib/index.js')) {
|
||||
compile.lib = true;
|
||||
}
|
||||
|
||||
if (compile.api && !fileExists('./lib/index.d.ts')) {
|
||||
compile.lib = true;
|
||||
}
|
||||
|
||||
// Compile packages
|
||||
Object.keys(compile).forEach((key) => {
|
||||
if (compile[key]) {
|
||||
commands.push({
|
||||
cmd: 'npm',
|
||||
args: ['run', 'build:' + key],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Run next command
|
||||
*/
|
||||
const next = () => {
|
||||
const item = commands.shift();
|
||||
if (item === void 0) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (item.cwd === void 0) {
|
||||
item.cwd = __dirname;
|
||||
}
|
||||
|
||||
const result = child_process.spawnSync(item.cmd, item.args, {
|
||||
cwd: item.cwd,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
if (result.status === 0) {
|
||||
process.nextTick(next);
|
||||
} else {
|
||||
process.exit(result.status);
|
||||
}
|
||||
};
|
||||
next();
|
@ -1,7 +0,0 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/tests/*-test.ts'],
|
||||
};
|
@ -1,8 +1,9 @@
|
||||
{
|
||||
"name": "@iconify/tailwind",
|
||||
"name": "@iconify/tailwind4",
|
||||
"description": "Iconify plugin for Tailwind CSS",
|
||||
"type": "module",
|
||||
"author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)",
|
||||
"version": "1.2.0",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/plugin.js",
|
||||
"types": "./dist/plugin.d.ts",
|
||||
@ -18,11 +19,11 @@
|
||||
"clean": "rimraf lib dist tsconfig.tsbuildinfo",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"prebuild": "pnpm run lint && pnpm run clean",
|
||||
"build": "node build",
|
||||
"build": "npm run build:lib && npm run build:dist && npm run build:api",
|
||||
"build:api": "api-extractor run --local --verbose",
|
||||
"build:lib": "tsc -b",
|
||||
"build:dist": "rollup -c rollup.config.mjs",
|
||||
"test": "jest --runInBand"
|
||||
"build:dist": "rollup -c rollup.config.js",
|
||||
"test": "vitest --config vitest.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/types": "workspace:^"
|
||||
@ -42,11 +43,10 @@
|
||||
"@typescript-eslint/eslint-plugin": "^8.17.0",
|
||||
"eslint": "^9.16.0",
|
||||
"globals": "^15.13.0",
|
||||
"jest": "^29.7.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.28.1",
|
||||
"tailwindcss": "^3.4.16",
|
||||
"ts-jest": "^29.2.5",
|
||||
"typescript": "^5.7.2"
|
||||
"tailwindcss": "4.0.0-beta.8",
|
||||
"typescript": "^5.7.2",
|
||||
"vitest": "^2.1.8"
|
||||
}
|
||||
}
|
||||
|
@ -1,50 +0,0 @@
|
||||
import { getIconsCSSData } from '@iconify/utils/lib/css/icons';
|
||||
import { loadIconSet } from './helpers/loader';
|
||||
import { getIconNames } from './helpers/names';
|
||||
import type { CleanIconifyPluginOptions } from './helpers/options';
|
||||
|
||||
/**
|
||||
* Get CSS rules for icons list
|
||||
*/
|
||||
export function getCSSRulesForIcons(
|
||||
icons: string[] | string,
|
||||
options: CleanIconifyPluginOptions = {}
|
||||
): Record<string, Record<string, string>> {
|
||||
const rules = Object.create(null) as Record<string, Record<string, string>>;
|
||||
|
||||
// Get all icons
|
||||
const prefixes = getIconNames(icons);
|
||||
|
||||
// Parse all icon sets
|
||||
for (const prefix in prefixes) {
|
||||
const iconSet = loadIconSet(options.iconSets?.[prefix] || prefix);
|
||||
if (!iconSet) {
|
||||
throw new Error(
|
||||
`Cannot load icon set for "${prefix}". Install "@iconify-json/${prefix}" as dev dependency?`
|
||||
);
|
||||
}
|
||||
|
||||
const generated = getIconsCSSData(
|
||||
iconSet,
|
||||
Array.from(prefixes[prefix]),
|
||||
{
|
||||
...options,
|
||||
customise: (content, name) =>
|
||||
options.customise?.(content, name, prefix) ?? content,
|
||||
}
|
||||
);
|
||||
|
||||
const result = generated.common
|
||||
? [generated.common, ...generated.css]
|
||||
: generated.css;
|
||||
result.forEach((item) => {
|
||||
const selector =
|
||||
item.selector instanceof Array
|
||||
? item.selector.join(', ')
|
||||
: item.selector;
|
||||
rules[selector] = item.rules;
|
||||
});
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { getIconsCSSData } from '@iconify/utils/lib/css/icons';
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||
import { loadIconSet } from './helpers/loader';
|
||||
import type { DynamicIconifyPluginOptions } from './helpers/options';
|
||||
import { loadIconSet } from './helpers/loader.js';
|
||||
import type { DynamicIconifyPluginOptions } from './helpers/options.js';
|
||||
|
||||
/**
|
||||
* Get dynamic CSS rules
|
||||
@ -10,7 +10,7 @@ export function getDynamicCSSRules(
|
||||
icon: string,
|
||||
options: DynamicIconifyPluginOptions = {}
|
||||
): Record<string, string> {
|
||||
const nameParts = icon.split(/--|\:/);
|
||||
const nameParts = icon.split(/--|:/);
|
||||
if (nameParts.length !== 2) {
|
||||
throw new Error(`Invalid icon name: "${icon}"`);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import type { IconifyJSON } from '@iconify/types';
|
||||
import { IconifyIconSetSource } from './options';
|
||||
import type { IconifyIconSetSource } from './options.js';
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||
|
||||
/**
|
||||
|
@ -1,73 +0,0 @@
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||
|
||||
/**
|
||||
* Get icon names from list
|
||||
*/
|
||||
export function getIconNames(
|
||||
icons: string[] | string
|
||||
): Record<string, Set<string>> | undefined {
|
||||
const prefixes = Object.create(null) as Record<string, Set<string>>;
|
||||
|
||||
// Add entry
|
||||
const add = (prefix: string, name: string) => {
|
||||
if (
|
||||
typeof prefix === 'string' &&
|
||||
prefix.match(matchIconName) &&
|
||||
typeof name === 'string' &&
|
||||
name.match(matchIconName)
|
||||
) {
|
||||
(prefixes[prefix] || (prefixes[prefix] = new Set())).add(name);
|
||||
}
|
||||
};
|
||||
|
||||
// Comma or space separated string
|
||||
let iconNames: string[] | undefined;
|
||||
if (typeof icons === 'string') {
|
||||
iconNames = icons.split(/[\s,.]/);
|
||||
} else if (icons instanceof Array) {
|
||||
iconNames = [];
|
||||
// Split each array entry
|
||||
icons.forEach((item) => {
|
||||
item.split(/[\s,.]/).forEach((name) => iconNames.push(name));
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse array
|
||||
if (iconNames?.length) {
|
||||
iconNames.forEach((icon) => {
|
||||
if (!icon.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt prefix:name split
|
||||
const nameParts = icon.split(':');
|
||||
if (nameParts.length === 2) {
|
||||
add(nameParts[0], nameParts[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt icon class: .icon--{prefix}--{name}
|
||||
// with or without dot
|
||||
const classParts = icon.split('--');
|
||||
if (classParts[0].match(/^\.?icon$/)) {
|
||||
if (classParts.length === 3) {
|
||||
add(classParts[1], classParts[2]);
|
||||
return;
|
||||
}
|
||||
if (classParts.length === 2) {
|
||||
// Partial match
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Throw error
|
||||
throw new Error(`Cannot resolve icon: "${icon}"`);
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
return prefixes;
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import type { IconCSSIconSetOptions } from '@iconify/utils/lib/css/types';
|
||||
import type { IconifyJSON } from '@iconify/types';
|
||||
|
||||
// Source for icon set: icon set, filename, or synchronous callback that loads icon set
|
||||
@ -16,15 +15,6 @@ export interface CommonIconifyPluginOptions {
|
||||
customise?: (content: string, name: string, prefix: string) => string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for clean class names
|
||||
*/
|
||||
export interface CleanIconifyPluginOptions
|
||||
extends CommonIconifyPluginOptions,
|
||||
Omit<IconCSSIconSetOptions, 'customise'> {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for dynamic class names
|
||||
*/
|
||||
@ -39,72 +29,3 @@ export interface DynamicIconifyPluginOptions
|
||||
// Sets the default height/width value (ex. scale: 2 = 2em)
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for main plugin
|
||||
*/
|
||||
|
||||
// Icons to include: array of names or callback
|
||||
export type IconsListOption = string[] | ((name: string) => boolean);
|
||||
|
||||
// Source filename or icon set
|
||||
type IconSetSource = string | IconifyJSON;
|
||||
|
||||
// Full icon set options
|
||||
interface IconSetOptions {
|
||||
// Prefix, required if `source` is not set
|
||||
// If both `source` and `prefix` are set, `prefix` will be used
|
||||
prefix?: string;
|
||||
|
||||
// Source
|
||||
source?: IconSetSource;
|
||||
|
||||
// Icons to load
|
||||
icons?: IconsListOption;
|
||||
|
||||
// Customise callback. If set, it will be used instead of global customise callback
|
||||
customise?: (content: string, name: string) => string;
|
||||
}
|
||||
|
||||
// Array of icon sets to load
|
||||
type IconifyPluginListOptions = (string | IconSetOptions)[];
|
||||
|
||||
// Full object
|
||||
export interface IconifyPluginOptionsObject {
|
||||
// Selector for mask, defaults to ".iconify"
|
||||
// If empty string, mask selector will not be generated
|
||||
maskSelector?: string;
|
||||
|
||||
// Extra rules to add to mask selector
|
||||
extraMaskRules?: Record<string, string>;
|
||||
|
||||
// Selector for background, defaults to ".iconify-color"
|
||||
// If empty string, background selector will not be generated
|
||||
backgroundSelector?: string;
|
||||
|
||||
// Extra rules to add to background selector
|
||||
extraBackgroundRules?: Record<string, string>;
|
||||
|
||||
// Selector for icons, default is `.{prefix}--{name}`
|
||||
iconSelector?: string;
|
||||
|
||||
// Variable name that contains icon, defaults to "svg"
|
||||
varName?: string;
|
||||
|
||||
// Scale for icons, defaults to 1
|
||||
scale?: number;
|
||||
|
||||
// Make icons square, defaults to true
|
||||
square?: boolean;
|
||||
|
||||
// Prefixes to load
|
||||
prefixes?: IconifyPluginListOptions;
|
||||
|
||||
// Customise callback
|
||||
customise?: (content: string, name: string, prefix: string) => string;
|
||||
}
|
||||
|
||||
// Full options
|
||||
export type IconifyPluginOptions =
|
||||
| IconifyPluginOptionsObject
|
||||
| IconifyPluginListOptions;
|
||||
|
@ -1,80 +1,29 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import plugin from 'tailwindcss/plugin';
|
||||
import { getCSSRulesForIcons } from './clean';
|
||||
import { getDynamicCSSRules } from './dynamic';
|
||||
import type {
|
||||
CleanIconifyPluginOptions,
|
||||
DynamicIconifyPluginOptions,
|
||||
IconifyPluginOptions,
|
||||
} from './helpers/options';
|
||||
import {
|
||||
cleanupIconifyPluginOptions,
|
||||
getCSSComponentsForPlugin,
|
||||
getCSSRulesForPlugin,
|
||||
} from './preparsed';
|
||||
import { getDynamicCSSRules } from './dynamic.js';
|
||||
|
||||
/**
|
||||
* Generate styles for dynamic selector
|
||||
*
|
||||
* Usage in HTML: <span class="icon-[mdi-light--home]" />
|
||||
*/
|
||||
export function addDynamicIconSelectors(options?: DynamicIconifyPluginOptions) {
|
||||
const prefix = options?.prefix || 'icon';
|
||||
function addDynamicIconSelectors(): ReturnType<typeof plugin> {
|
||||
const prefix = 'icon';
|
||||
return plugin(({ matchComponents }) => {
|
||||
matchComponents({
|
||||
[prefix]: (icon: string) => {
|
||||
try {
|
||||
return getDynamicCSSRules(icon, options);
|
||||
return getDynamicCSSRules(icon);
|
||||
} catch (err) {
|
||||
// Log error, but do not throw it
|
||||
console.error((err as Error).message);
|
||||
return {};
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate rules for mask, background and selected icon sets
|
||||
*
|
||||
* Icons should combine either mask or background selector and icon selector
|
||||
*
|
||||
* This plugin generates only square icons. Icons that are not square will be resized to fit square.
|
||||
*
|
||||
* Usage in HTML: <span class="iconify mdi-light--home" />
|
||||
*/
|
||||
export function addIconSelectors(options: IconifyPluginOptions) {
|
||||
const fullOptions = cleanupIconifyPluginOptions(options);
|
||||
|
||||
return plugin(({ addComponents, addUtilities }) => {
|
||||
addComponents(getCSSComponentsForPlugin(fullOptions));
|
||||
addUtilities(getCSSRulesForPlugin(fullOptions));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate styles for preset list of icons
|
||||
*
|
||||
* Requires knowing full list of icons
|
||||
*
|
||||
* Usage in HTML: <span class="icon--mdi-light icon--mdi-light--home" />
|
||||
*
|
||||
* @deprecated Use addIconSelectors instead
|
||||
*/
|
||||
export function addCleanIconSelectors(
|
||||
icons: string[] | string,
|
||||
options?: CleanIconifyPluginOptions
|
||||
) {
|
||||
const rules = getCSSRulesForIcons(icons, options);
|
||||
return plugin(({ addUtilities }) => {
|
||||
addUtilities(rules);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Export types
|
||||
*/
|
||||
export type {
|
||||
CleanIconifyPluginOptions,
|
||||
DynamicIconifyPluginOptions,
|
||||
IconifyPluginOptions,
|
||||
};
|
||||
// Export generated plugin. No TypeScript support yet, so export as unknown
|
||||
const exportedPlugin = addDynamicIconSelectors() as unknown;
|
||||
export default exportedPlugin;
|
||||
|
@ -1,193 +0,0 @@
|
||||
import type { IconifyJSON } from '@iconify/types';
|
||||
import {
|
||||
generateItemCSSRules,
|
||||
getCommonCSSRules,
|
||||
} from '@iconify/utils/lib/css/common';
|
||||
import { defaultIconProps } from '@iconify/utils/lib/icon/defaults';
|
||||
import { parseIconSet } from '@iconify/utils/lib/icon-set/parse';
|
||||
import { calculateSize } from '@iconify/utils/lib/svg/size';
|
||||
import type {
|
||||
IconifyPluginOptions,
|
||||
IconifyPluginOptionsObject,
|
||||
IconsListOption,
|
||||
} from './helpers/options';
|
||||
import { loadIconSet } from './helpers/loader';
|
||||
|
||||
/**
|
||||
* Convert plugin options to object
|
||||
*/
|
||||
export function cleanupIconifyPluginOptions(
|
||||
options: IconifyPluginOptions
|
||||
): IconifyPluginOptionsObject {
|
||||
return Array.isArray(options)
|
||||
? {
|
||||
prefixes: options,
|
||||
}
|
||||
: options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSS rules for main plugin (components)
|
||||
*/
|
||||
export function getCSSComponentsForPlugin(options: IconifyPluginOptionsObject) {
|
||||
const rules = Object.create(null) as Record<string, Record<string, string>>;
|
||||
|
||||
// Variable name, default to 'svg' (cannot be empty string)
|
||||
const varName = options.varName || 'svg';
|
||||
|
||||
// Scale icons
|
||||
const scale = options.scale ?? 1;
|
||||
const adjustScale = (obj: Record<string, string>) => {
|
||||
if (!scale) {
|
||||
// Delete width and height
|
||||
delete obj['width'];
|
||||
delete obj['height'];
|
||||
} else if (scale !== 1) {
|
||||
// Set custom width and height
|
||||
obj['width'] = scale + 'em';
|
||||
obj['height'] = scale + 'em';
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
// Add common rules
|
||||
const maskSelector = options.maskSelector ?? '.iconify';
|
||||
const backgroundSelector = options.backgroundSelector ?? '.iconify-color';
|
||||
if (maskSelector) {
|
||||
rules[maskSelector] = Object.assign(
|
||||
adjustScale(
|
||||
getCommonCSSRules({
|
||||
mode: 'mask',
|
||||
varName,
|
||||
})
|
||||
),
|
||||
options.extraMaskRules || {}
|
||||
);
|
||||
}
|
||||
if (backgroundSelector) {
|
||||
rules[backgroundSelector] = Object.assign(
|
||||
adjustScale(
|
||||
getCommonCSSRules({
|
||||
mode: 'background',
|
||||
varName,
|
||||
})
|
||||
),
|
||||
options.extraBackgroundRules || {}
|
||||
);
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSS rules for main plugin (utilities)
|
||||
*/
|
||||
export function getCSSRulesForPlugin(options: IconifyPluginOptionsObject) {
|
||||
const rules = Object.create(null) as Record<string, Record<string, string>>;
|
||||
|
||||
// Variable name, default to 'svg' (cannot be empty string)
|
||||
const varName = options.varName || 'svg';
|
||||
|
||||
// Add icon sets
|
||||
const iconSelector = options.iconSelector || '.{prefix}--{name}';
|
||||
|
||||
// Make icons square
|
||||
const square = options.square !== false;
|
||||
|
||||
// Scale
|
||||
const scale = options.scale ?? 1;
|
||||
|
||||
options.prefixes?.forEach((item) => {
|
||||
let prefix: string;
|
||||
let iconSet: IconifyJSON | undefined;
|
||||
let iconsList: IconsListOption | undefined;
|
||||
let customise: ((content: string, name: string) => string) | undefined;
|
||||
|
||||
// Load icon set
|
||||
if (typeof item === 'string') {
|
||||
// Prefix
|
||||
prefix = item;
|
||||
iconSet = loadIconSet(prefix);
|
||||
} else if (item.source) {
|
||||
// Source, possibly with prefix
|
||||
iconSet = loadIconSet(item.source);
|
||||
prefix = item.prefix || iconSet?.prefix;
|
||||
iconsList = item.icons;
|
||||
customise = item.customise;
|
||||
if (!prefix) {
|
||||
throw new Error(
|
||||
'Custom icon set does not have a prefix. Please set "prefix" property'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Prefix
|
||||
prefix = item.prefix || iconSet?.prefix;
|
||||
iconSet = prefix ? loadIconSet(prefix) : undefined;
|
||||
iconsList = item.icons;
|
||||
customise = item.customise;
|
||||
}
|
||||
|
||||
// Validate it
|
||||
if (!iconSet) {
|
||||
throw new Error(
|
||||
`Cannot load icon set for "${prefix}". Install "@iconify-json/${prefix}" as dev dependency?`
|
||||
);
|
||||
}
|
||||
if (!prefix) {
|
||||
throw new Error(
|
||||
'Bad icon set entry, must have either "prefix" or "source" set'
|
||||
);
|
||||
}
|
||||
|
||||
// Load icons
|
||||
parseIconSet(iconSet, (name, data) => {
|
||||
// Check if icon should be rendered
|
||||
if (iconsList) {
|
||||
if (Array.isArray(iconsList)) {
|
||||
if (!iconsList.includes(name)) {
|
||||
return;
|
||||
}
|
||||
} else if (!iconsList(name)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Customise icon
|
||||
const body = customise
|
||||
? customise(data.body, name)
|
||||
: options.customise
|
||||
? options.customise(data.body, name, prefix)
|
||||
: data.body;
|
||||
|
||||
// Generate CSS
|
||||
const iconRules = generateItemCSSRules(
|
||||
{
|
||||
...defaultIconProps,
|
||||
...data,
|
||||
body,
|
||||
},
|
||||
{
|
||||
mode: 'mask', // not used because varName is set, but required
|
||||
varName,
|
||||
forceSquare: square,
|
||||
}
|
||||
);
|
||||
|
||||
// Generate selector
|
||||
const selector = iconSelector
|
||||
.replace('{prefix}', prefix)
|
||||
.replace('{name}', name);
|
||||
|
||||
// Scale non-square icons
|
||||
if (!square && scale > 0 && scale !== 1 && iconRules.width) {
|
||||
iconRules.width = calculateSize(iconRules.width, scale);
|
||||
}
|
||||
|
||||
// Add to rules
|
||||
rules[selector] = iconRules;
|
||||
});
|
||||
});
|
||||
|
||||
// Return
|
||||
return rules;
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
import { getCSSRulesForIcons } from '../src/clean';
|
||||
|
||||
describe('Testing clean CSS rules', () => {
|
||||
it('One icon', () => {
|
||||
const data = getCSSRulesForIcons('mdi-light:home');
|
||||
expect(Object.keys(data)).toEqual([
|
||||
'.icon--mdi-light',
|
||||
'.icon--mdi-light--home',
|
||||
]);
|
||||
expect(Object.keys(data['.icon--mdi-light--home'])).toEqual(['--svg']);
|
||||
});
|
||||
|
||||
it('Multiple icons from same icon set', () => {
|
||||
const data = getCSSRulesForIcons([
|
||||
// By name
|
||||
'mdi-light:home',
|
||||
// By selector
|
||||
'.icon--mdi-light--arrow-left',
|
||||
'.icon--mdi-light.icon--mdi-light--arrow-down',
|
||||
// By class name
|
||||
'icon--mdi-light--file',
|
||||
'icon--mdi-light icon--mdi-light--format-clear',
|
||||
]);
|
||||
expect(Object.keys(data)).toEqual([
|
||||
'.icon--mdi-light',
|
||||
'.icon--mdi-light--home',
|
||||
'.icon--mdi-light--arrow-left',
|
||||
'.icon--mdi-light--arrow-down',
|
||||
'.icon--mdi-light--file',
|
||||
'.icon--mdi-light--format-clear',
|
||||
]);
|
||||
});
|
||||
|
||||
it('Multiple icon sets', () => {
|
||||
const data = getCSSRulesForIcons([
|
||||
// MDI Light
|
||||
'mdi-light:home',
|
||||
// Line MD
|
||||
'line-md:home',
|
||||
]);
|
||||
expect(Object.keys(data)).toEqual([
|
||||
'.icon--mdi-light',
|
||||
'.icon--mdi-light--home',
|
||||
'.icon--line-md',
|
||||
'.icon--line-md--home',
|
||||
]);
|
||||
});
|
||||
|
||||
it('Bad class name', () => {
|
||||
let threw = false;
|
||||
try {
|
||||
getCSSRulesForIcons(['icon--mdi-light--home test']);
|
||||
} catch {
|
||||
threw = true;
|
||||
}
|
||||
expect(threw).toBe(true);
|
||||
});
|
||||
|
||||
it('Bad icon set', () => {
|
||||
let threw = false;
|
||||
try {
|
||||
getCSSRulesForIcons('test123:home');
|
||||
} catch {
|
||||
threw = true;
|
||||
}
|
||||
expect(threw).toBe(true);
|
||||
});
|
||||
});
|
@ -1,4 +1,4 @@
|
||||
import { getDynamicCSSRules } from '../src/dynamic';
|
||||
import { getDynamicCSSRules } from '../lib/dynamic.js';
|
||||
|
||||
describe('Testing dynamic CSS rules', () => {
|
||||
it('One icon', () => {
|
||||
|
@ -3,6 +3,8 @@
|
||||
"compilerOptions": {
|
||||
"types": ["node", "jest"],
|
||||
"rootDir": ".",
|
||||
"outDir": "../tests-compiled"
|
||||
"outDir": "../tests-compiled",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
"sourceMap": false,
|
||||
"composite": true,
|
||||
"strict": false,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true
|
||||
|
@ -4,6 +4,8 @@
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "nodenext",
|
||||
"lib": ["ESNext", "DOM"]
|
||||
}
|
||||
}
|
||||
|
9
plugins/tailwind/vitest.config.js
Normal file
9
plugins/tailwind/vitest.config.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
watch: false,
|
||||
include: ['**/tests/*-test.ts'],
|
||||
},
|
||||
});
|
1018
pnpm-lock.yaml
generated
1018
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user