diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 1b3334dd274..3854d4ecd4d 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -176,9 +176,9 @@ jobs: uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: .eslintcache - key: ${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-${{ github.sha }} + key: ${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'packages/dify-ui/eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}- + ${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'packages/dify-ui/eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}- - name: Style check if: steps.changed-files.outputs.any_changed == 'true' diff --git a/packages/dify-ui/README.md b/packages/dify-ui/README.md index 1740157c7aa..eccdb888d48 100644 --- a/packages/dify-ui/README.md +++ b/packages/dify-ui/README.md @@ -188,6 +188,7 @@ See `[web/docs/overlay.md](../../web/docs/overlay.md)` for the web app overlay b ## Development +- `vp run @langgenius/dify-ui#lint` (from the repository root) — strict ESLint checks for component source, stories, tests, and package configuration. - `pnpm -C packages/dify-ui test` — Vitest unit tests for primitives. - `pnpm -C packages/dify-ui storybook` — Storybook on the default port. Each primitive has `index.stories.tsx`. - `pnpm -C packages/dify-ui test:storybook` — Storybook component tests in Vitest browser mode. Stories without `play` are render and a11y smoke tests; stories with `play` should cover public UI contracts such as opening overlays, keyboard navigation, disabled/loading guards, form submission, and controlled state updates. diff --git a/packages/dify-ui/eslint.config.mjs b/packages/dify-ui/eslint.config.mjs new file mode 100644 index 00000000000..1a33c409edd --- /dev/null +++ b/packages/dify-ui/eslint.config.mjs @@ -0,0 +1,109 @@ +// @ts-check + +import path from 'node:path' +import antfu, { GLOB_MARKDOWN_CODE, GLOB_TS, GLOB_TSX } from '@antfu/eslint-config' +import tailwindcss from 'eslint-plugin-better-tailwindcss' +import jsxA11y from 'eslint-plugin-jsx-a11y' +import storybook from 'eslint-plugin-storybook' + +const SOURCE_FILES = [GLOB_TS, GLOB_TSX] +const TEST_FILES = ['**/__tests__/**/*.{ts,tsx}', '**/*.spec.{ts,tsx}'] + +export default antfu( + { + ignores: [ + 'coverage/', + 'dist/', + 'storybook-static/', + ], + react: { + overrides: { + 'react/exhaustive-deps': ['error', { additionalHooks: 'useIsoLayoutEffect' }], + 'react/no-context-provider': 'off', + 'react/no-unnecessary-use-prefix': 'error', + 'react/no-use-context': 'off', + 'react/rules-of-hooks': 'error', + 'react/set-state-in-effect': 'error', + 'react/set-state-in-render': 'error', + 'react/static-components': 'error', + 'react-refresh/only-export-components': 'off', + }, + }, + typescript: { + overrides: { + 'ts/consistent-type-definitions': ['error', 'type'], + 'ts/no-explicit-any': 'error', + 'ts/no-redeclare': 'off', + }, + erasableOnly: true, + }, + test: { + overrides: { + 'test/prefer-lowercase-title': 'off', + }, + }, + stylistic: { + overrides: { + 'antfu/top-level-function': 'off', + }, + }, + e18e: false, + pnpm: false, + }, + { + files: [GLOB_TSX], + ...jsxA11y.flatConfigs.recommended, + rules: { + ...jsxA11y.flatConfigs.recommended.rules, + 'jsx-a11y/anchor-has-content': 'off', + 'jsx-a11y/label-has-associated-control': 'off', + }, + }, + ...storybook.configs['flat/recommended'], + { + name: 'dify-ui/storybook', + files: ['**/.storybook/main.{js,cjs,mjs,ts}'], + rules: { + 'storybook/no-uninstalled-addons': ['error', { + packageJsonLocation: path.resolve(import.meta.dirname, 'package.json'), + }], + }, + }, + { + name: 'dify-ui/tailwind', + files: SOURCE_FILES, + ignores: [GLOB_MARKDOWN_CODE, ...TEST_FILES], + plugins: { + tailwindcss, + }, + rules: { + 'tailwindcss/enforce-consistent-class-order': 'error', + 'tailwindcss/no-duplicate-classes': 'error', + 'tailwindcss/no-deprecated-classes': 'error', + 'tailwindcss/no-unknown-classes': 'error', + 'tailwindcss/no-unnecessary-whitespace': 'error', + }, + settings: { + 'better-tailwindcss': { + cwd: import.meta.dirname, + entryPoint: path.resolve(import.meta.dirname, './.storybook/storybook.css'), + }, + }, + }, + { + files: TEST_FILES, + rules: { + 'react/purity': 'off', + }, + }, + { + rules: { + 'node/prefer-global/process': 'off', + }, + }, + { + linterOptions: { + reportUnusedDisableDirectives: 'error', + }, + }, +) diff --git a/packages/dify-ui/package.json b/packages/dify-ui/package.json index a133ab31e0b..e49c4f932e0 100644 --- a/packages/dify-ui/package.json +++ b/packages/dify-ui/package.json @@ -151,6 +151,7 @@ } }, "scripts": { + "lint": "eslint --cache --max-warnings=0 .", "storybook": "storybook dev -p 6006", "storybook:build": "storybook build", "test": "vp test --project unit", @@ -170,10 +171,12 @@ "tailwind-merge": "catalog:" }, "devDependencies": { + "@antfu/eslint-config": "catalog:", "@base-ui/react": "catalog:", "@chromatic-com/storybook": "catalog:", "@dify/tsconfig": "workspace:*", "@egoist/tailwindcss-icons": "catalog:", + "@eslint-react/eslint-plugin": "catalog:", "@iconify-json/ri": "catalog:", "@storybook/addon-a11y": "catalog:", "@storybook/addon-docs": "catalog:", @@ -192,6 +195,11 @@ "@vitest/browser-playwright": "catalog:", "@vitest/coverage-v8": "catalog:", "class-variance-authority": "catalog:", + "eslint": "catalog:", + "eslint-plugin-better-tailwindcss": "catalog:", + "eslint-plugin-jsx-a11y": "catalog:", + "eslint-plugin-react-refresh": "catalog:", + "eslint-plugin-storybook": "catalog:", "playwright": "catalog:", "react": "catalog:", "react-dom": "catalog:", diff --git a/packages/dify-ui/src/autocomplete/index.stories.tsx b/packages/dify-ui/src/autocomplete/index.stories.tsx index bf96cd1ee17..953b90de0af 100644 --- a/packages/dify-ui/src/autocomplete/index.stories.tsx +++ b/packages/dify-ui/src/autocomplete/index.stories.tsx @@ -254,7 +254,7 @@ const BasicTagAutocomplete = ({ openOnInputClick > -