diff --git a/.stylelintrc.json b/.stylelintrc.json index edece5d9a..71a1311ae 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -59,7 +59,7 @@ "function-no-unknown": [ true, { - "ignoreFunctions": ["theme", "v-bind"] + "ignoreFunctions": ["theme", "v-bind", "from-folder", "from-json"] } ] }, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 406b4ad3f..daa8808c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -201,7 +201,7 @@ The project supports three types of icons, all with automatic imports (no manual 2. **Iconify Icons** - 200,000+ icons from various libraries: ``, `` 3. **Custom Icons** - Your own SVG icons: `` -Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Custom icons are stored in `packages/design-system/src/icons/` and processed by `packages/design-system/src/iconCollection.ts` with automatic validation. +Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Tailwind CSS icon classes (`icon-[comfy--template]`) are provided by `@iconify/tailwind4`, configured in `packages/design-system/src/css/style.css`. Custom icons are stored in `packages/design-system/src/icons/` and loaded via `from-folder` at build time. For detailed instructions and code examples, see [packages/design-system/src/icons/README.md](packages/design-system/src/icons/README.md). diff --git a/components.json b/components.json index 5127811f3..a70765222 100644 --- a/components.json +++ b/components.json @@ -3,7 +3,6 @@ "style": "new-york", "typescript": true, "tailwind": { - "config": "tailwind.config.ts", "css": "src/assets/css/style.css", "baseColor": "stone", "cssVariables": true, diff --git a/knip.config.ts b/knip.config.ts index d6a4a7517..e000e1882 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -20,10 +20,6 @@ const config: KnipConfig = { 'packages/tailwind-utils': { project: ['src/**/*.{js,ts}'] }, - 'packages/design-system': { - entry: ['src/**/*.ts'], - project: ['src/**/*.{js,ts}', '*.{js,ts,mts}'] - }, 'packages/registry-types': { project: ['src/**/*.{js,ts}'] } @@ -31,6 +27,7 @@ const config: KnipConfig = { ignoreBinaries: ['python3', 'gh'], ignoreDependencies: [ // Weird importmap things + '@iconify-json/lucide', '@iconify/json', '@primeuix/forms', '@primeuix/styled', diff --git a/packages/design-system/package.json b/packages/design-system/package.json index d20ef86ab..90fd14609 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -4,7 +4,6 @@ "description": "Shared design system for ComfyUI Frontend", "type": "module", "exports": { - "./tailwind-config": "./tailwind.config.ts", "./css/*": "./src/css/*" }, "scripts": { @@ -12,7 +11,7 @@ }, "dependencies": { "@iconify-json/lucide": "catalog:", - "@iconify/tailwind": "catalog:" + "@iconify/tailwind4": "catalog:" }, "devDependencies": { "tailwindcss": "catalog:", diff --git a/packages/design-system/src/css/style.css b/packages/design-system/src/css/style.css index 6fefc8cd6..88487f0a1 100644 --- a/packages/design-system/src/css/style.css +++ b/packages/design-system/src/css/style.css @@ -7,11 +7,16 @@ @plugin 'tailwindcss-primeui'; -@config '../../tailwind.config.ts'; +@plugin "@iconify/tailwind4" { + scale: 1.2; + icon-sets: from-folder(comfy, './packages/design-system/src/icons'); +} @custom-variant touch (@media (hover: none)); @theme { + --shadow-interface: var(--interface-panel-box-shadow); + --text-xxs: 0.625rem; --text-xxs--line-height: calc(1 / 0.625); diff --git a/packages/design-system/src/iconCollection.ts b/packages/design-system/src/iconCollection.ts deleted file mode 100644 index 170a5465f..000000000 --- a/packages/design-system/src/iconCollection.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { existsSync, readFileSync, readdirSync } from 'fs' -import { join } from 'path' -import { dirname } from 'path' -import { fileURLToPath } from 'url' - -const fileName = fileURLToPath(import.meta.url) -const dirName = dirname(fileName) -const customIconsPath = join(dirName, 'icons') - -// Iconify collection structure -interface IconifyIcon { - body: string - width?: number - height?: number -} - -interface IconifyCollection { - prefix: string - icons: Record - width?: number - height?: number -} - -// Create an Iconify collection for custom icons -export const iconCollection: IconifyCollection = { - prefix: 'comfy', - icons: {}, - width: 16, - height: 16 -} - -/** - * Validates that an SVG file contains valid SVG content - */ -function validateSvgContent(content: string, filename: string): void { - if (!content.trim()) { - throw new Error(`Empty SVG file: ${filename}`) - } - - if (!content.includes(' tag): ${filename}`) - } - - // Basic XML structure validation - const openTags = (content.match(/]*>/g) || []).length - const closeTags = (content.match(/<\/svg>/g) || []).length - - if (openTags !== closeTags) { - throw new Error(`Malformed SVG file (mismatched svg tags): ${filename}`) - } -} - -/** - * Loads custom SVG icons from the icons directory - */ -function loadCustomIcons(): void { - if (!existsSync(customIconsPath)) { - console.warn(`Custom icons directory not found: ${customIconsPath}`) - return - } - - try { - const files = readdirSync(customIconsPath) - const svgFiles = files.filter((file) => file.endsWith('.svg')) - - if (svgFiles.length === 0) { - console.warn('No SVG files found in custom icons directory') - return - } - - svgFiles.forEach((file) => { - const name = file.replace('.svg', '') - const filePath = join(customIconsPath, file) - - try { - const content = readFileSync(filePath, 'utf-8') - validateSvgContent(content, file) - - iconCollection.icons[name] = { - body: content - } - } catch (error) { - console.error( - `Failed to load custom icon ${file}:`, - error instanceof Error ? error.message : error - ) - // Continue loading other icons instead of failing the entire build - } - }) - } catch (error) { - console.error( - 'Failed to read custom icons directory:', - error instanceof Error ? error.message : error - ) - // Don't throw here - allow build to continue without custom icons - } -} - -// Load icons when this module is imported -loadCustomIcons() diff --git a/packages/design-system/src/icons/README.md b/packages/design-system/src/icons/README.md index 4c98b95aa..51e0b880b 100644 --- a/packages/design-system/src/icons/README.md +++ b/packages/design-system/src/icons/README.md @@ -251,26 +251,25 @@ Icons are automatically imported using `unplugin-icons` - no manual imports need The icon system has two layers: -1. **Build-time Processing** (`packages/design-system/src/iconCollection.ts`): - - Scans `packages/design-system/src/icons/` for SVG files - - Validates SVG content and structure - - Creates Iconify collection for Tailwind CSS - - Provides error handling for malformed files +1. **Tailwind CSS Plugin** (`@iconify/tailwind4`): + - Configured via `@plugin` directive in `packages/design-system/src/css/style.css` + - Uses `from-folder(comfy, ...)` to load SVGs from `packages/design-system/src/icons/` + - Auto-cleans and optimizes SVGs at build time 2. **Vite Runtime** (`vite.config.mts`): - Enables direct SVG import as Vue components - Supports dynamic icon loading -```typescript -// Build script creates Iconify collection -export const iconCollection: IconifyCollection = { - prefix: 'comfy', - icons: { - workflow: { body: '...' }, - node: { body: '...' } - } +```css +/* CSS configuration for Tailwind icon classes */ +@plugin "@iconify/tailwind4" { + prefix: 'icon'; + scale: 1.2; + icon-sets: from-folder(comfy, './packages/design-system/src/icons'); } +``` +```typescript // Vite configuration for component-based usage Icons({ compiler: 'vue3', diff --git a/packages/design-system/tailwind.config.ts b/packages/design-system/tailwind.config.ts deleted file mode 100644 index 62b611426..000000000 --- a/packages/design-system/tailwind.config.ts +++ /dev/null @@ -1,24 +0,0 @@ -import lucide from '@iconify-json/lucide/icons.json' with { type: 'json' } -import { addDynamicIconSelectors } from '@iconify/tailwind' - -import { iconCollection } from './src/iconCollection' - -export default { - theme: { - extend: { - boxShadow: { - interface: 'var(--interface-panel-box-shadow)' - } - } - }, - plugins: [ - addDynamicIconSelectors({ - iconSets: { - comfy: iconCollection, - lucide - }, - scale: 1.2, - prefix: 'icon' - }) - ] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d742311b7..1539fc2dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,9 +21,9 @@ catalogs: '@iconify/json': specifier: ^2.2.380 version: 2.2.380 - '@iconify/tailwind': - specifier: ^1.1.3 - version: 1.2.0 + '@iconify/tailwind4': + specifier: ^1.2.0 + version: 1.2.1 '@intlify/eslint-plugin-vue-i18n': specifier: ^4.1.0 version: 4.1.0 @@ -805,9 +805,9 @@ importers: '@iconify-json/lucide': specifier: 'catalog:' version: 1.2.79 - '@iconify/tailwind': + '@iconify/tailwind4': specifier: 'catalog:' - version: 1.2.0 + version: 1.2.1(tailwindcss@4.1.12) devDependencies: tailwindcss: specifier: 'catalog:' @@ -1528,6 +1528,9 @@ packages: peerDependencies: postcss-selector-parser: ^7.0.0 + '@cyberalien/svg-utils@1.1.1': + resolution: {integrity: sha512-i05Cnpzeezf3eJAXLx7aFirTYYoq5D1XUItp1XsjqkerNJh//6BG9sOYHbiO7v0KYMvJAx3kosrZaRcNlQPdsA==} + '@dual-bundle/import-meta-resolve@4.2.1': resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} @@ -2149,8 +2152,13 @@ packages: '@iconify/json@2.2.380': resolution: {integrity: sha512-+Al/Q+mMB/nLz/tawmJEOkCs6+RKKVUS/Yg9I80h2yRpu0kIzxVLQRfF0NifXz/fH92vDVXbS399wio4lMVF4Q==} - '@iconify/tailwind@1.2.0': - resolution: {integrity: sha512-KgpIHWOTcRYw1XcoUqyNSrmYyfLLqZYu3AmP8zdfLk0F5TqRO8YerhlvlQmGfn7rJXgPeZN569xPAJnJ53zZxA==} + '@iconify/tailwind4@1.2.1': + resolution: {integrity: sha512-Hd7k8y7uzT3hk8ltw0jGku0r0wA8sc3d2iMvVTYv/9tMxBb+frZtWZGD9hDMU3EYuE+lMn58wi2lS8R2ZbwFcQ==} + peerDependencies: + tailwindcss: '>= 4.0.0' + + '@iconify/tools@5.0.3': + resolution: {integrity: sha512-W5nbH5fNv20TvU49Al19Foos/ViAnmppbCNV9ieGl6/dRMDRzxeFol6peXX/NAgaOytQwZZxTTJRq/Kxd4eWsA==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -4664,6 +4672,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@13.1.0: resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} @@ -4769,6 +4781,13 @@ packages: css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + css-tree@3.1.0: resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -4785,6 +4804,10 @@ packages: engines: {node: '>=4'} hasBin: true + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + cssstyle@5.3.5: resolution: {integrity: sha512-GlsEptulso7Jg0VaOZ8BXQi3AkYM5BOJKEO/rjMidSCq70FkIC5y0eawrCXeYzxgt3OCf4Ls+eoxN+/05vN0Ag==} engines: {node: '>=20'} @@ -6490,6 +6513,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + mdn-data@2.12.2: resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} @@ -6683,6 +6709,10 @@ packages: mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + modern-tar@0.7.3: + resolution: {integrity: sha512-4W79zekKGyYU4JXVmB78DOscMFaJth2gGhgfTl2alWE4rNe3nf4N2pqenQ0rEtIewrnD79M687Ouba3YGTLOvg==} + engines: {node: '>=18.0.0'} + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -7441,6 +7471,10 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + sax@1.4.4: + resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} + engines: {node: '>=11.0.0'} + saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -7704,6 +7738,11 @@ packages: svg-tags@1.0.0: resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} + svgo@4.0.0: + resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} + engines: {node: '>=16'} + hasBin: true + swr@2.3.6: resolution: {integrity: sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==} peerDependencies: @@ -9435,6 +9474,10 @@ snapshots: dependencies: postcss-selector-parser: 7.1.1 + '@cyberalien/svg-utils@1.1.1': + dependencies: + '@iconify/types': 2.0.0 + '@dual-bundle/import-meta-resolve@4.2.1': {} '@emnapi/core@1.7.1': @@ -10023,9 +10066,22 @@ snapshots: '@iconify/types': 2.0.0 pathe: 1.1.2 - '@iconify/tailwind@1.2.0': + '@iconify/tailwind4@1.2.1(tailwindcss@4.1.12)': dependencies: + '@iconify/tools': 5.0.3 '@iconify/types': 2.0.0 + '@iconify/utils': 3.1.0 + tailwindcss: 4.1.12 + + '@iconify/tools@5.0.3': + dependencies: + '@cyberalien/svg-utils': 1.1.1 + '@iconify/types': 2.0.0 + '@iconify/utils': 3.1.0 + fflate: 0.8.2 + modern-tar: 0.7.3 + pathe: 2.0.3 + svgo: 4.0.0 '@iconify/types@2.0.0': {} @@ -12834,6 +12890,8 @@ snapshots: commander@10.0.1: {} + commander@11.1.0: {} + commander@13.1.0: {} commander@14.0.2: {} @@ -12941,6 +12999,19 @@ snapshots: domutils: 2.8.0 nth-check: 2.1.1 + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + css-tree@3.1.0: dependencies: mdn-data: 2.12.2 @@ -12952,6 +13023,10 @@ snapshots: cssesc@3.0.0: {} + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + cssstyle@5.3.5: dependencies: '@asamuzakjp/css-color': 4.1.1 @@ -14893,6 +14968,8 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdn-data@2.0.28: {} + mdn-data@2.12.2: {} mdurl@2.0.0: {} @@ -15193,6 +15270,8 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.1 + modern-tar@0.7.3: {} + mrmime@2.0.1: {} ms@2.1.3: {} @@ -16233,6 +16312,8 @@ snapshots: is-regex: 1.2.1 optional: true + sax@1.4.4: {} + saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -16570,6 +16651,16 @@ snapshots: svg-tags@1.0.0: {} + svgo@4.0.0: + dependencies: + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.1.0 + css-what: 6.1.0 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.4.4 + swr@2.3.6(react@19.2.3): dependencies: dequal: 2.0.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 89e14f9cb..ddf7d3595 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,7 +8,7 @@ catalog: '@eslint/js': ^9.39.1 '@iconify-json/lucide': ^1.1.178 '@iconify/json': ^2.2.380 - '@iconify/tailwind': ^1.1.3 + '@iconify/tailwind4': ^1.2.0 '@intlify/eslint-plugin-vue-i18n': ^4.1.0 '@lobehub/i18n-cli': ^1.25.1 '@nx/eslint': 22.2.6 diff --git a/tailwind.config.ts b/tailwind.config.ts deleted file mode 100644 index ae57cde6a..000000000 --- a/tailwind.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import baseConfig from '@comfyorg/design-system/tailwind-config' - -export default { - ...baseConfig, - content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'] -} diff --git a/tsconfig.json b/tsconfig.json index 4c48d6e48..988be359b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -45,7 +45,7 @@ "src/types/**/*.d.ts", "playwright.config.ts", "playwright.i18n.config.ts", - "tailwind.config.ts", + "tests-ui/**/*", "vite.config.mts", "vitest.config.ts"