diff --git a/lint.config.ts b/lint.config.ts index 6ee726761a9..367f6c55165 100644 --- a/lint.config.ts +++ b/lint.config.ts @@ -3,6 +3,22 @@ import path from 'node:path' const rootDir = import.meta.dirname const difyUiPackageJson = path.resolve(rootDir, 'packages/dify-ui/package.json') +const enableTailwindCanonicalClasses = process.env.TAILWIND_CANONICAL_CLASSES === 'true' +const tailwindCanonicalClassesOverride = { + files: [ + 'web/**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}', + 'packages/dify-ui/**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}', + ], + rules: { + 'better-tailwindcss/enforce-canonical-classes': [ + 'warn', + { + collapse: false, + logical: false, + }, + ], + }, +} satisfies NonNullable[number] /** * Oxlint equivalent of the ESLint configurations that were active before the migration. @@ -55,6 +71,7 @@ export const lintConfig = { jsPlugins: [ '@tanstack/eslint-plugin-query', 'eslint-plugin-antfu', + ...(enableTailwindCanonicalClasses ? ['eslint-plugin-better-tailwindcss'] : []), 'eslint-plugin-command', 'eslint-plugin-erasable-syntax-only', { @@ -86,6 +103,15 @@ export const lintConfig = { typeCheck: true, }, settings: { + ...(enableTailwindCanonicalClasses + ? { + 'better-tailwindcss': { + cwd: path.resolve(rootDir, 'web'), + entryPoint: 'app/styles/globals.css', + rootFontSize: 16, + }, + } + : {}), 'react-x': { additionalStateHooks: '/^use\\w*State(?:s)?|useAtom$/u', }, @@ -425,6 +451,7 @@ export const lintConfig = { 'no-undef': 'error', }, overrides: [ + ...(enableTailwindCanonicalClasses ? [tailwindCanonicalClassesOverride] : []), { files: ['**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}'], rules: { diff --git a/package.json b/package.json index fa39d23fd73..16c8d9b5ec3 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "lint:oxlint": "vp lint", "lint:oxlint:fix": "vp lint --fix", "lint:oxlint:quiet": "vp lint --quiet", + "lint:tailwind": "TAILWIND_CANONICAL_CLASSES=true vp lint web packages/dify-ui", + "lint:tailwind:fix": "TAILWIND_CANONICAL_CLASSES=true vp lint --fix web packages/dify-ui", "lint:eslint": "eslint --concurrency=auto", "lint:eslint:fix": "eslint --fix --concurrency=auto", "lint:eslint:quiet": "eslint --quiet --concurrency=auto" @@ -27,6 +29,7 @@ "eslint": "catalog:", "eslint-markdown": "catalog:", "eslint-plugin-antfu": "catalog:", + "eslint-plugin-better-tailwindcss": "catalog:", "eslint-plugin-command": "catalog:", "eslint-plugin-erasable-syntax-only": "catalog:", "eslint-plugin-hyoban": "catalog:", diff --git a/packages/dify-ui/src/autocomplete/index.tsx b/packages/dify-ui/src/autocomplete/index.tsx index dede962654b..9127a5cbef0 100644 --- a/packages/dify-ui/src/autocomplete/index.tsx +++ b/packages/dify-ui/src/autocomplete/index.tsx @@ -119,7 +119,7 @@ const autocompleteInputVariants = cva( variants: { size: { small: 'px-2 py-1 system-xs-regular', - medium: 'px-3 py-[7px] system-sm-regular', + medium: 'px-3 py-1.75 system-sm-regular', large: 'px-4 py-2 system-md-regular', }, }, diff --git a/packages/dify-ui/src/button/index.tsx b/packages/dify-ui/src/button/index.tsx index cd8d0a8b7bb..18a812ee0ad 100644 --- a/packages/dify-ui/src/button/index.tsx +++ b/packages/dify-ui/src/button/index.tsx @@ -7,41 +7,41 @@ import { cva } from 'class-variance-authority' import { cn } from '../cn' export const buttonVariants = cva( - 'inline-flex cursor-pointer items-center justify-center whitespace-nowrap outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid data-[disabled]:cursor-not-allowed', + 'inline-flex cursor-pointer items-center justify-center whitespace-nowrap outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid data-disabled:cursor-not-allowed', { variants: { variant: { primary: [ 'border-components-button-primary-border bg-components-button-primary-bg text-components-button-primary-text shadow-sm', 'hover:border-components-button-primary-border-hover hover:bg-components-button-primary-bg-hover', - 'data-[disabled]:border-components-button-primary-border-disabled data-[disabled]:bg-components-button-primary-bg-disabled data-[disabled]:text-components-button-primary-text-disabled data-[disabled]:shadow-none', + 'data-disabled:border-components-button-primary-border-disabled data-disabled:bg-components-button-primary-bg-disabled data-disabled:text-components-button-primary-text-disabled data-disabled:shadow-none', ], secondary: [ 'border-[0.5px] shadow-xs backdrop-blur-[5px]', 'border-components-button-secondary-border bg-components-button-secondary-bg text-components-button-secondary-text', 'hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover', - 'data-[disabled]:border-components-button-secondary-border-disabled data-[disabled]:bg-components-button-secondary-bg-disabled data-[disabled]:text-components-button-secondary-text-disabled data-[disabled]:backdrop-blur-xs', + 'data-disabled:border-components-button-secondary-border-disabled data-disabled:bg-components-button-secondary-bg-disabled data-disabled:text-components-button-secondary-text-disabled data-disabled:backdrop-blur-xs', ], 'secondary-accent': [ 'border-[0.5px] shadow-xs', 'border-components-button-secondary-border bg-components-button-secondary-bg text-components-button-secondary-accent-text', 'hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover', - 'data-[disabled]:border-components-button-secondary-border-disabled data-[disabled]:bg-components-button-secondary-bg-disabled data-[disabled]:text-components-button-secondary-accent-text-disabled', + 'data-disabled:border-components-button-secondary-border-disabled data-disabled:bg-components-button-secondary-bg-disabled data-disabled:text-components-button-secondary-accent-text-disabled', ], tertiary: [ 'bg-components-button-tertiary-bg text-components-button-tertiary-text', 'hover:bg-components-button-tertiary-bg-hover', - 'data-[disabled]:bg-components-button-tertiary-bg-disabled data-[disabled]:text-components-button-tertiary-text-disabled', + 'data-disabled:bg-components-button-tertiary-bg-disabled data-disabled:text-components-button-tertiary-text-disabled', ], ghost: [ 'text-components-button-ghost-text', 'hover:bg-components-button-ghost-bg-hover', - 'data-[disabled]:text-components-button-ghost-text-disabled', + 'data-disabled:text-components-button-ghost-text-disabled', ], 'ghost-accent': [ 'text-components-button-secondary-accent-text', 'hover:bg-state-accent-hover', - 'data-[disabled]:text-components-button-secondary-accent-text-disabled', + 'data-disabled:text-components-button-secondary-accent-text-disabled', ], }, size: { @@ -61,7 +61,7 @@ export const buttonVariants = cva( class: [ 'border-components-button-destructive-primary-border bg-components-button-destructive-primary-bg text-components-button-destructive-primary-text', 'hover:border-components-button-destructive-primary-border-hover hover:bg-components-button-destructive-primary-bg-hover', - 'data-[disabled]:border-components-button-destructive-primary-border-disabled data-[disabled]:bg-components-button-destructive-primary-bg-disabled data-[disabled]:text-components-button-destructive-primary-text-disabled data-[disabled]:shadow-none', + 'data-disabled:border-components-button-destructive-primary-border-disabled data-disabled:bg-components-button-destructive-primary-bg-disabled data-disabled:text-components-button-destructive-primary-text-disabled data-disabled:shadow-none', ], }, { @@ -70,7 +70,7 @@ export const buttonVariants = cva( class: [ 'border-components-button-destructive-secondary-border bg-components-button-destructive-secondary-bg text-components-button-destructive-secondary-text', 'hover:border-components-button-destructive-secondary-border-hover hover:bg-components-button-destructive-secondary-bg-hover', - 'data-[disabled]:border-components-button-destructive-secondary-border-disabled data-[disabled]:bg-components-button-destructive-secondary-bg-disabled data-[disabled]:text-components-button-destructive-secondary-text-disabled', + 'data-disabled:border-components-button-destructive-secondary-border-disabled data-disabled:bg-components-button-destructive-secondary-bg-disabled data-disabled:text-components-button-destructive-secondary-text-disabled', ], }, { @@ -79,7 +79,7 @@ export const buttonVariants = cva( class: [ 'bg-components-button-destructive-tertiary-bg text-components-button-destructive-tertiary-text', 'hover:bg-components-button-destructive-tertiary-bg-hover', - 'data-[disabled]:bg-components-button-destructive-tertiary-bg-disabled data-[disabled]:text-components-button-destructive-tertiary-text-disabled', + 'data-disabled:bg-components-button-destructive-tertiary-bg-disabled data-disabled:text-components-button-destructive-tertiary-text-disabled', ], }, { @@ -88,7 +88,7 @@ export const buttonVariants = cva( class: [ 'text-components-button-destructive-ghost-text', 'hover:bg-components-button-destructive-ghost-bg-hover', - 'data-[disabled]:text-components-button-destructive-ghost-text-disabled', + 'data-disabled:text-components-button-destructive-ghost-text-disabled', ], }, ], diff --git a/packages/dify-ui/src/combobox/index.tsx b/packages/dify-ui/src/combobox/index.tsx index d25474d400e..40dc1dfb84c 100644 --- a/packages/dify-ui/src/combobox/index.tsx +++ b/packages/dify-ui/src/combobox/index.tsx @@ -166,7 +166,7 @@ const comboboxInputVariants = cva( variants: { size: { small: 'px-2 py-1 system-xs-regular', - medium: 'px-3 py-[7px] system-sm-regular', + medium: 'px-3 py-1.75 system-sm-regular', large: 'px-4 py-2 system-md-regular', }, }, diff --git a/packages/dify-ui/src/dialog/index.stories.tsx b/packages/dify-ui/src/dialog/index.stories.tsx index 725cdd01874..6ba8df0148e 100644 --- a/packages/dify-ui/src/dialog/index.stories.tsx +++ b/packages/dify-ui/src/dialog/index.stories.tsx @@ -278,7 +278,7 @@ const OutsideScrollingContentDemo = () => { }>Review long release notes - + { - + -
+
Knowledge review diff --git a/packages/dify-ui/src/drawer/index.stories.tsx b/packages/dify-ui/src/drawer/index.stories.tsx index b12cf82b4e3..093f348366f 100644 --- a/packages/dify-ui/src/drawer/index.stories.tsx +++ b/packages/dify-ui/src/drawer/index.stories.tsx @@ -247,7 +247,7 @@ export const Positions: Story = { - +
@@ -258,7 +258,7 @@ export const Positions: Story = { This drawer uses the default swipeDirection="down" bottom sheet behavior.
-
+
The drag handle sits at the top because the sheet dismisses downward.
@@ -278,9 +278,9 @@ export const Positions: Story = { - + -
+
Top sheet @@ -288,7 +288,7 @@ export const Positions: Story = { This drawer is positioned with swipeDirection="up" and dismisses upward.
-
+
The drag handle sits at the bottom because the sheet dismisses upward.
@@ -331,9 +331,9 @@ function SnapPointsDemo() { className={cn( 'relative touch-none overflow-visible', '[--bleed:3rem] [--top-margin:1rem]', - 'pb-[max(0px,calc(var(--drawer-snap-point-offset,0px)_+_var(--drawer-swipe-movement-y,0px)))]', + 'pb-[max(0px,calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y,0px)))]', 'after:pointer-events-none after:absolute after:inset-x-0 after:top-full after:h-(--bleed) after:bg-inherit after:content-[""]', - 'data-[swipe-direction=down]:max-h-[calc(100dvh_-_var(--top-margin))]', + 'data-[swipe-direction=down]:max-h-[calc(100dvh-var(--top-margin))]', 'data-ending-style:pb-0 data-starting-style:pb-0', )} > @@ -343,7 +343,7 @@ function SnapPointsDemo() { Snap points
- +
Drag the sheet to snap between a compact peek and a near full-height view. @@ -392,19 +392,19 @@ export const NestedDrawers: Story = {
@@ -591,7 +591,7 @@ function IndentEffectDemo() { - +
@@ -646,7 +646,7 @@ function NonModalDemo() {
- +
@@ -732,7 +732,7 @@ export const MobileNavigation: Story = { aria-label="Mobile drawer viewport" > - +