chore: introduce css icons (#32004)

This commit is contained in:
Stephen Zhou
2026-02-09 18:37:41 +08:00
committed by GitHub
parent 898e09264b
commit e0fcf33979
7 changed files with 532 additions and 197 deletions

View File

@@ -2,9 +2,7 @@ import consistentPlaceholders from './rules/consistent-placeholders.js'
import noAsAnyInT from './rules/no-as-any-in-t.js' import noAsAnyInT from './rules/no-as-any-in-t.js'
import noExtraKeys from './rules/no-extra-keys.js' import noExtraKeys from './rules/no-extra-keys.js'
import noLegacyNamespacePrefix from './rules/no-legacy-namespace-prefix.js' import noLegacyNamespacePrefix from './rules/no-legacy-namespace-prefix.js'
import noVersionPrefix from './rules/no-version-prefix.js'
import requireNsOption from './rules/require-ns-option.js' import requireNsOption from './rules/require-ns-option.js'
import validI18nKeys from './rules/valid-i18n-keys.js'
/** @type {import('eslint').ESLint.Plugin} */ /** @type {import('eslint').ESLint.Plugin} */
const plugin = { const plugin = {
@@ -17,9 +15,7 @@ const plugin = {
'no-as-any-in-t': noAsAnyInT, 'no-as-any-in-t': noAsAnyInT,
'no-extra-keys': noExtraKeys, 'no-extra-keys': noExtraKeys,
'no-legacy-namespace-prefix': noLegacyNamespacePrefix, 'no-legacy-namespace-prefix': noLegacyNamespacePrefix,
'no-version-prefix': noVersionPrefix,
'require-ns-option': requireNsOption, 'require-ns-option': requireNsOption,
'valid-i18n-keys': validI18nKeys,
}, },
} }

View File

@@ -1,45 +0,0 @@
const DEPENDENCY_KEYS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']
const VERSION_PREFIXES = ['^', '~']
/** @type {import('eslint').Rule.RuleModule} */
export default {
meta: {
type: 'problem',
docs: {
description: `Ensure package.json dependencies do not use version prefixes (${VERSION_PREFIXES.join(' or ')})`,
},
fixable: 'code',
},
create(context) {
const { filename } = context
if (!filename.endsWith('package.json'))
return {}
const selector = `JSONProperty:matches(${DEPENDENCY_KEYS.map(k => `[key.value="${k}"]`).join(', ')}) > JSONObjectExpression > JSONProperty`
return {
[selector](node) {
const versionNode = node.value
if (versionNode && versionNode.type === 'JSONLiteral' && typeof versionNode.value === 'string') {
const version = versionNode.value
const foundPrefix = VERSION_PREFIXES.find(prefix => version.startsWith(prefix))
if (foundPrefix) {
const packageName = node.key.value || node.key.name
const cleanVersion = version.substring(1)
const canAutoFix = /^\d+\.\d+\.\d+$/.test(cleanVersion)
context.report({
node: versionNode,
message: `Dependency "${packageName}" has version prefix "${foundPrefix}" that should be removed (found: "${version}", expected: "${cleanVersion}")`,
fix: canAutoFix
? fixer => fixer.replaceText(versionNode, `"${cleanVersion}"`)
: undefined,
})
}
}
},
}
},
}

View File

@@ -1,61 +0,0 @@
import { cleanJsonText } from '../utils.js'
/** @type {import('eslint').Rule.RuleModule} */
export default {
meta: {
type: 'problem',
docs: {
description: 'Ensure i18n JSON keys are flat and valid as object paths',
},
},
create(context) {
return {
Program(node) {
const { filename, sourceCode } = context
if (!filename.endsWith('.json'))
return
let json
try {
json = JSON.parse(cleanJsonText(sourceCode.text))
}
catch {
context.report({
node,
message: 'Invalid JSON format',
})
return
}
const keys = Object.keys(json)
const keyPrefixes = new Set()
for (const key of keys) {
if (key.includes('.')) {
const parts = key.split('.')
for (let i = 1; i < parts.length; i++) {
const prefix = parts.slice(0, i).join('.')
if (keys.includes(prefix)) {
context.report({
node,
message: `Invalid key structure: '${key}' conflicts with '${prefix}'`,
})
}
keyPrefixes.add(prefix)
}
}
}
for (const key of keys) {
if (keyPrefixes.has(key)) {
context.report({
node,
message: `Invalid key structure: '${key}' is a prefix of another key`,
})
}
}
},
}
},
}

View File

@@ -2,6 +2,7 @@
import antfu, { GLOB_TESTS, GLOB_TS, GLOB_TSX } from '@antfu/eslint-config' import antfu, { GLOB_TESTS, GLOB_TS, GLOB_TSX } from '@antfu/eslint-config'
import pluginQuery from '@tanstack/eslint-plugin-query' import pluginQuery from '@tanstack/eslint-plugin-query'
import tailwindcss from 'eslint-plugin-better-tailwindcss' import tailwindcss from 'eslint-plugin-better-tailwindcss'
import hyoban from 'eslint-plugin-hyoban'
import sonar from 'eslint-plugin-sonarjs' import sonar from 'eslint-plugin-sonarjs'
import storybook from 'eslint-plugin-storybook' import storybook from 'eslint-plugin-storybook'
import dify from './eslint-rules/index.js' import dify from './eslint-rules/index.js'
@@ -80,7 +81,47 @@ export default antfu(
}, },
}, },
{ {
plugins: { dify }, name: 'dify/custom/setup',
plugins: {
dify,
hyoban,
},
},
{
files: ['**/*.tsx'],
rules: {
'hyoban/prefer-tailwind-icons': ['warn', {
prefix: 'i-',
propMappings: {
size: 'size',
width: 'w',
height: 'h',
},
libraries: [
{
prefix: 'i-custom-',
source: '^@/app/components/base/icons/src/(?<set>(?:public|vender)(?:/.*)?)$',
name: '^(?<name>.*)$',
},
{
source: '^@remixicon/react$',
name: '^(?<set>Ri)(?<name>.+)$',
},
{
source: '^@(?<set>heroicons)/react/24/outline$',
name: '^(?<name>.*)Icon$',
},
{
source: '^@(?<set>heroicons)/react/24/(?<variant>solid)$',
name: '^(?<name>.*)Icon$',
},
{
source: '^@(?<set>heroicons)/react/(?<variant>\\d+/(?:solid|outline))$',
name: '^(?<name>.*)Icon$',
},
],
}],
},
}, },
{ {
files: ['i18n/**/*.json'], files: ['i18n/**/*.json'],
@@ -89,7 +130,7 @@ export default antfu(
'max-lines': 'off', 'max-lines': 'off',
'jsonc/sort-keys': 'error', 'jsonc/sort-keys': 'error',
'dify/valid-i18n-keys': 'error', 'hyoban/i18n-flat-key': 'error',
'dify/no-extra-keys': 'error', 'dify/no-extra-keys': 'error',
'dify/consistent-placeholders': 'error', 'dify/consistent-placeholders': 'error',
}, },
@@ -97,7 +138,7 @@ export default antfu(
{ {
files: ['**/package.json'], files: ['**/package.json'],
rules: { rules: {
'dify/no-version-prefix': 'error', 'hyoban/no-dependency-version-prefix': 'error',
}, },
}, },
) )

View File

@@ -31,8 +31,8 @@
"build": "next build", "build": "next build",
"build:docker": "next build && node scripts/optimize-standalone.js", "build:docker": "next build && node scripts/optimize-standalone.js",
"start": "node ./scripts/copy-and-start.mjs", "start": "node ./scripts/copy-and-start.mjs",
"lint": "eslint --cache --concurrency=\"auto\"", "lint": "eslint --cache --concurrency=auto",
"lint:ci": "eslint --cache --concurrency 3", "lint:ci": "eslint --cache --concurrency 2",
"lint:fix": "pnpm lint --fix", "lint:fix": "pnpm lint --fix",
"lint:quiet": "pnpm lint --quiet", "lint:quiet": "pnpm lint --quiet",
"lint:complexity": "pnpm lint --rule 'complexity: [error, {max: 15}]' --quiet", "lint:complexity": "pnpm lint --rule 'complexity: [error, {max: 15}]' --quiet",
@@ -166,7 +166,10 @@
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "7.2.0", "@antfu/eslint-config": "7.2.0",
"@chromatic-com/storybook": "5.0.0", "@chromatic-com/storybook": "5.0.0",
"@egoist/tailwindcss-icons": "1.9.2",
"@eslint-react/eslint-plugin": "2.9.4", "@eslint-react/eslint-plugin": "2.9.4",
"@iconify-json/heroicons": "1.2.3",
"@iconify-json/ri": "1.2.9",
"@mdx-js/loader": "3.1.1", "@mdx-js/loader": "3.1.1",
"@mdx-js/react": "3.1.1", "@mdx-js/react": "3.1.1",
"@next/bundle-analyzer": "16.1.5", "@next/bundle-analyzer": "16.1.5",
@@ -194,7 +197,7 @@
"@types/js-cookie": "3.0.6", "@types/js-cookie": "3.0.6",
"@types/js-yaml": "4.0.9", "@types/js-yaml": "4.0.9",
"@types/negotiator": "0.6.4", "@types/negotiator": "0.6.4",
"@types/node": "18.15.0", "@types/node": "24.10.12",
"@types/postcss-js": "4.1.0", "@types/postcss-js": "4.1.0",
"@types/qs": "6.14.0", "@types/qs": "6.14.0",
"@types/react": "19.2.9", "@types/react": "19.2.9",
@@ -214,12 +217,14 @@
"cross-env": "10.1.0", "cross-env": "10.1.0",
"esbuild": "0.27.2", "esbuild": "0.27.2",
"eslint": "9.39.2", "eslint": "9.39.2",
"eslint-plugin-better-tailwindcss": "4.1.1", "eslint-plugin-better-tailwindcss": "https://pkg.pr.new/hyoban/eslint-plugin-better-tailwindcss@c0161c7",
"eslint-plugin-hyoban": "0.11.1",
"eslint-plugin-react-hooks": "7.0.1", "eslint-plugin-react-hooks": "7.0.1",
"eslint-plugin-react-refresh": "0.5.0", "eslint-plugin-react-refresh": "0.5.0",
"eslint-plugin-sonarjs": "3.0.6", "eslint-plugin-sonarjs": "3.0.6",
"eslint-plugin-storybook": "10.2.6", "eslint-plugin-storybook": "10.2.6",
"husky": "9.1.7", "husky": "9.1.7",
"iconify-import-svg": "0.1.1",
"jsdom": "27.3.0", "jsdom": "27.3.0",
"jsdom-testing-mocks": "1.16.0", "jsdom-testing-mocks": "1.16.0",
"knip": "5.78.0", "knip": "5.78.0",

537
web/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,8 @@
import path from 'node:path' import path from 'node:path'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import { getIconCollections, iconsPlugin } from '@egoist/tailwindcss-icons'
import tailwindTypography from '@tailwindcss/typography' import tailwindTypography from '@tailwindcss/typography'
import { importSvgCollections } from 'iconify-import-svg'
// @ts-expect-error workaround for turbopack issue // @ts-expect-error workaround for turbopack issue
import { cssAsPlugin } from './tailwind-css-plugin.ts' import { cssAsPlugin } from './tailwind-css-plugin.ts'
// @ts-expect-error workaround for turbopack issue // @ts-expect-error workaround for turbopack issue
@@ -158,6 +160,26 @@ const config = {
}, },
plugins: [ plugins: [
tailwindTypography, tailwindTypography,
iconsPlugin({
collections: {
...getIconCollections(['heroicons', 'ri']),
...importSvgCollections({
source: path.resolve(_dirname, 'app/components/base/icons/assets/public'),
prefix: 'custom-public',
ignoreImportErrors: true,
}),
...importSvgCollections({
source: path.resolve(_dirname, 'app/components/base/icons/assets/vender'),
prefix: 'custom-vender',
ignoreImportErrors: true,
}),
},
extraProperties: {
width: '1rem',
height: '1rem',
display: 'block',
},
}),
cssAsPlugin([ cssAsPlugin([
path.resolve(_dirname, './app/styles/globals.css'), path.resolve(_dirname, './app/styles/globals.css'),
]), ]),