From 0ed392fb6223e8266130747b2fc2424fc67e383c Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Tue, 29 Sep 2026 11:58:49 +0000 Subject: [PATCH] chore(dify-ui): align authoring rules and dependency optimization (#43233) --- lint.config.ts | 14 ++ packages/dify-ui/docs/authoring.md | 9 + .../src/alert-dialog/index.stories.tsx | 4 +- .../src/autocomplete/index.stories.tsx | 180 +++++++++--------- packages/dify-ui/src/avatar/index.tsx | 2 +- .../dify-ui/src/collapsible/index.stories.tsx | 4 +- .../dify-ui/src/combobox/index.stories.tsx | 50 ++--- .../src/context-menu/index.stories.tsx | 22 ++- .../src/date-time/__tests__/label.spec.tsx | 22 +-- .../date-picker/__tests__/behavior.spec.tsx | 8 +- .../date-time/date-picker/calendar-panel.tsx | 4 +- .../src/date-time/date-time-picker/index.tsx | 8 +- .../src/date-time/internal/picker-parts.tsx | 4 +- .../time-picker/__tests__/index.spec.tsx | 4 +- .../date-time/time-picker/index.stories.tsx | 4 +- .../src/date-time/time-picker/index.tsx | 8 +- .../src/dialog/__tests__/index.spec.tsx | 4 +- packages/dify-ui/src/dialog/index.stories.tsx | 8 +- .../src/dropdown-menu/index.stories.tsx | 32 ++-- .../src/infotip/__tests__/index.spec.tsx | 4 +- .../dify-ui/src/infotip/__tests__/types.tsx | 6 +- .../src/input-group/__tests__/index.spec.tsx | 5 +- packages/dify-ui/src/kbd/index.stories.tsx | 24 +-- .../src/number-field/__tests__/index.spec.tsx | 4 +- .../dify-ui/src/popover/index.stories.tsx | 4 +- .../src/preview-card/index.stories.tsx | 44 +++-- .../src/scroll-area/__tests__/index.spec.tsx | 6 +- .../__tests__/index.spec.tsx | 6 +- .../src/segmented-control/index.stories.tsx | 18 +- packages/dify-ui/src/select/index.stories.tsx | 4 +- .../src/status-dot/__tests__/index.spec.tsx | 9 +- .../dify-ui/src/switch/__tests__/types.tsx | 5 +- packages/dify-ui/src/switch/index.stories.tsx | 50 ++--- .../dify-ui/src/textarea/index.stories.tsx | 6 +- .../src/toast/__tests__/index.spec.tsx | 21 +- packages/dify-ui/src/toast/index.stories.tsx | 18 +- .../dify-ui/src/tooltip/index.stories.tsx | 40 ++-- packages/dify-ui/vite.config.ts | 10 - 38 files changed, 359 insertions(+), 316 deletions(-) diff --git a/lint.config.ts b/lint.config.ts index 45a417ad1d0..74036a4a95a 100644 --- a/lint.config.ts +++ b/lint.config.ts @@ -1193,15 +1193,29 @@ export const lintConfig = { { files: ['packages/dify-ui/**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}'], rules: { + 'func-names': ['error', 'as-needed'], + 'id-denylist': ['error', 'e'], 'unicorn/import-style': [ 'error', { extendDefaultStyles: false, styles: { react: { namespace: true }, + 'react-dom': { namespace: true }, + 'react-dom/client': { namespace: true }, + 'react-dom/server': { namespace: true }, }, }, ], + 'react/function-component-definition': [ + 'error', + { + namedComponents: ['function-declaration', 'function-expression'], + unnamedComponents: ['arrow-function', 'function-expression'], + }, + ], + 'react/jsx-fragments': ['error', 'element'], + 'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }], 'react/exhaustive-deps': [ 'error', { diff --git a/packages/dify-ui/docs/authoring.md b/packages/dify-ui/docs/authoring.md index 2cdda20303e..3a4639ddd37 100644 --- a/packages/dify-ui/docs/authoring.md +++ b/packages/dify-ui/docs/authoring.md @@ -13,6 +13,15 @@ Use React namespace imports throughout this package, including stories and tests import also covers React types; do not add separate named type imports. JSX alone does not require an explicit React import. +Use namespace imports for `react-dom`, `react-dom/client`, and `react-dom/server` as well. +Write grouping fragments as `` and remove unnecessary fragment wrappers. + +## Component writing style + +Define named components with function declarations or function expressions. Arrow functions +remain appropriate for event handlers, render props, and Storybook render callbacks. Name function +expressions when JavaScript cannot infer their name. Use `event` or `error` instead of `e`. + ## Subpaths and names Every public primitive needs a matching `package.json#exports` subpath. Import relatively between diff --git a/packages/dify-ui/src/alert-dialog/index.stories.tsx b/packages/dify-ui/src/alert-dialog/index.stories.tsx index d0a8fa7efe4..f19693e0501 100644 --- a/packages/dify-ui/src/alert-dialog/index.stories.tsx +++ b/packages/dify-ui/src/alert-dialog/index.stories.tsx @@ -104,7 +104,7 @@ export const NonDestructive: Story = { ), } -const ControlledDemo = () => { +function ControlledDemo() { const [open, setOpen] = React.useState(false) const [count, setCount] = React.useState(0) @@ -149,7 +149,7 @@ export const Controlled: Story = { render: () => , } -const LoadingConfirmDemo = () => { +function LoadingConfirmDemo() { const [pending, setPending] = React.useState(false) const [open, setOpen] = React.useState(false) const confirmLabelId = React.useId() diff --git a/packages/dify-ui/src/autocomplete/index.stories.tsx b/packages/dify-ui/src/autocomplete/index.stories.tsx index 0e143166e59..b47cd73a9cb 100644 --- a/packages/dify-ui/src/autocomplete/index.stories.tsx +++ b/packages/dify-ui/src/autocomplete/index.stories.tsx @@ -293,27 +293,29 @@ async function searchSuggestions( } } -const SuggestionItem = ({ item, dense }: { item: Suggestion; dense?: boolean }) => ( - - {item.icon && ( - -) +
+ {item.label} + {!dense && item.description && ( + {item.description} + )} +
+ {item.meta && ( + + {item.meta} + + )} + + ) +} // Only virtualized items receive an explicit index; ordinary lists must let Base UI register items by DOM order for keyboard navigation. -const VirtualizedSuggestionItem = ({ +function VirtualizedSuggestionItem({ item, index, dense, @@ -321,70 +323,76 @@ const VirtualizedSuggestionItem = ({ item: Suggestion index: number dense?: boolean -}) => ( - - {item.icon && ( - -) +
+ {item.label} + {!dense && item.description && ( + {item.description} + )} +
+ {item.meta && ( + + {item.meta} + + )} + + ) +} -const TagSuggestionItem = ({ item }: { item: Suggestion }) => ( - - {item.label} - {item.description && ( - - {item.description} - - )} - -) +function TagSuggestionItem({ item }: { item: Suggestion }) { + return ( + + {item.label} + {item.description && ( + + {item.description} + + )} + + ) +} -const BasicTagAutocomplete = ({ size = 'medium' }: { size?: 'small' | 'medium' | 'large' }) => ( - - - - - - - > - {(item) => } - - No tag suggestion. Keep the typed value. - - - - -) +function BasicTagAutocomplete({ size = 'medium' }: { size?: 'small' | 'medium' | 'large' }) { + return ( + + + + + + + > + {(item) => } + + No tag suggestion. Keep the typed value. + + + + + ) +} -const GroupedSuggestionList = () => { +function GroupedSuggestionList() { const groups = useAutocompleteFilteredItems() return ( @@ -402,7 +410,7 @@ const GroupedSuggestionList = () => { ) } -const CommandPaletteList = () => { +function CommandPaletteList() { const groups = useAutocompleteFilteredItems() return ( @@ -450,7 +458,7 @@ const LimitedStatus = ({ total }: { total: number }) => { : `${items.length} suggestions available.` } -const AsyncSearchDemo = () => { +function AsyncSearchDemo() { const [searchValue, setSearchValue] = React.useState('') const [searchResults, setSearchResults] = React.useState([]) const [error, setError] = React.useState(null) @@ -532,11 +540,11 @@ const AsyncSearchDemo = () => { ) } -const VirtualizedSuggestionList = ({ +function VirtualizedSuggestionList({ virtualizerRef, }: { virtualizerRef: React.RefObject -}) => { +}) { const scrollRef = React.useRef(null) const filteredItems = useAutocompleteFilteredItems() const virtualizer = useVirtualizer({ @@ -586,7 +594,7 @@ const VirtualizedSuggestionList = ({ ) } -const VirtualizedStatus = () => { +function VirtualizedStatus() { const filteredItems = useAutocompleteFilteredItems() return ( @@ -596,7 +604,7 @@ const VirtualizedStatus = () => { ) } -const FuzzyHighlight = ({ text, query }: { text: string; query: string }) => { +function FuzzyHighlight({ text, query }: { text: string; query: string }) { const parts = React.useMemo(() => { const trimmed = query.trim() @@ -622,7 +630,7 @@ const FuzzyHighlight = ({ text, query }: { text: string; query: string }) => { ) } -const FuzzyMatchingDemo = () => { +function FuzzyMatchingDemo() { const [value, setValue] = React.useState('retr') const { contains } = useAutocompleteFilter({ sensitivity: 'base' }) @@ -863,7 +871,7 @@ export const CommandPalette: Story = { ), } -const VirtualizedLongSuggestionsDemo = () => { +function VirtualizedLongSuggestionsDemo() { const virtualizerRef = React.useRef(null) return ( diff --git a/packages/dify-ui/src/avatar/index.tsx b/packages/dify-ui/src/avatar/index.tsx index 03b84501ee9..5f1ba7fc8ae 100644 --- a/packages/dify-ui/src/avatar/index.tsx +++ b/packages/dify-ui/src/avatar/index.tsx @@ -74,7 +74,7 @@ function AvatarImage({ className, ...props }: AvatarImageProps) { ) } -const Avatar = ({ name, avatar, size = 'md', className, onLoadingStatusChange }: AvatarProps) => { +function Avatar({ name, avatar, size = 'md', className, onLoadingStatusChange }: AvatarProps) { return ( {avatar && ( diff --git a/packages/dify-ui/src/collapsible/index.stories.tsx b/packages/dify-ui/src/collapsible/index.stories.tsx index 6949bff074d..afe469ff3f6 100644 --- a/packages/dify-ui/src/collapsible/index.stories.tsx +++ b/packages/dify-ui/src/collapsible/index.stories.tsx @@ -50,7 +50,7 @@ function TriggerIcon() { function RecoveryKeys(props: Pick) { return ( - <> + Recovery keys @@ -62,7 +62,7 @@ function RecoveryKeys(props: Pickhorse-battery-staple - + ) } diff --git a/packages/dify-ui/src/combobox/index.stories.tsx b/packages/dify-ui/src/combobox/index.stories.tsx index 406270cf695..60712dde5a7 100644 --- a/packages/dify-ui/src/combobox/index.stories.tsx +++ b/packages/dify-ui/src/combobox/index.stories.tsx @@ -353,23 +353,25 @@ const renderVirtualizedOptionItem = (option: Option, index: number, itemCount: n ) -const PopupSearchInput = ({ label, placeholder }: { label: string; placeholder: string }) => ( -
- - - - -
-) +function PopupSearchInput({ label, placeholder }: { label: string; placeholder: string }) { + return ( +
+ + + + +
+ ) +} -const GroupedToolList = () => { +function GroupedToolList() { const groups = useComboboxFilteredItems() return ( @@ -385,11 +387,11 @@ const GroupedToolList = () => { ) } -const VirtualizedModelList = ({ +function VirtualizedModelList({ virtualizerRef, }: { virtualizerRef: React.RefObject -}) => { +}) { const scrollRef = React.useRef(null) const filteredItems = useComboboxFilteredItems
, ) const input = screen.getByRole('textbox', { name: 'Invalid value' }) const surface = screen.getByTestId('invalid-surface').element() diff --git a/packages/dify-ui/src/kbd/index.stories.tsx b/packages/dify-ui/src/kbd/index.stories.tsx index 1736f80e58e..bdc1342966d 100644 --- a/packages/dify-ui/src/kbd/index.stories.tsx +++ b/packages/dify-ui/src/kbd/index.stories.tsx @@ -43,7 +43,7 @@ const meta = { export default meta type Story = StoryObj -const HotkeyKbdGroup = ({ +function HotkeyKbdGroup({ hotkey, color = 'gray', platform = 'mac', @@ -51,16 +51,18 @@ const HotkeyKbdGroup = ({ hotkey: DisplayHotkey color?: 'gray' | 'white' platform?: FormatDisplayOptions['platform'] -}) => ( - - {formatForDisplay(hotkey, { platform, parts: true }).map((key, index) => ( - // oxlint-disable-next-line react/no-array-index-key -- Repeated display keys are static, ordered tokens with no component state. - - {key} - - ))} - -) +}) { + return ( + + {formatForDisplay(hotkey, { platform, parts: true }).map((key, index) => ( + // oxlint-disable-next-line react/no-array-index-key -- Repeated display keys are static, ordered tokens with no component state. + + {key} + + ))} + + ) +} export const Default: Story = { render: () => , diff --git a/packages/dify-ui/src/number-field/__tests__/index.spec.tsx b/packages/dify-ui/src/number-field/__tests__/index.spec.tsx index 39e6b4453fc..6fc8a6d3d6b 100644 --- a/packages/dify-ui/src/number-field/__tests__/index.spec.tsx +++ b/packages/dify-ui/src/number-field/__tests__/index.spec.tsx @@ -268,7 +268,7 @@ describe('Invalid focus colors', () => { document.documentElement.dataset.theme = theme try { const screen = await render( - <> + Invalid value @@ -278,7 +278,7 @@ describe('Invalid focus colors', () => { - , + , ) const input = screen.getByRole('textbox', { name: 'Invalid value' }) const surface = screen.getByTestId('invalid-surface').element() diff --git a/packages/dify-ui/src/popover/index.stories.tsx b/packages/dify-ui/src/popover/index.stories.tsx index 5e2b367ed0f..1ea37addfc0 100644 --- a/packages/dify-ui/src/popover/index.stories.tsx +++ b/packages/dify-ui/src/popover/index.stories.tsx @@ -208,7 +208,7 @@ const PLACEMENTS: PopoverPlacement[] = [ 'left-end', ] -const PlacementsDemo = () => { +function PlacementsDemo() { const [placement, setPlacement] = React.useState('bottom') return ( @@ -254,7 +254,7 @@ export const Placements: Story = { render: () => , } -const ControlledDemo = () => { +function ControlledDemo() { const [open, setOpen] = React.useState(false) return ( diff --git a/packages/dify-ui/src/preview-card/index.stories.tsx b/packages/dify-ui/src/preview-card/index.stories.tsx index 7c198aa0eb5..946b2470cc4 100644 --- a/packages/dify-ui/src/preview-card/index.stories.tsx +++ b/packages/dify-ui/src/preview-card/index.stories.tsx @@ -126,7 +126,7 @@ const PLACEMENTS: PreviewCardPlacement[] = [ 'left-end', ] -const PlacementsDemo = () => { +function PlacementsDemo() { const [placement, setPlacement] = React.useState('bottom') return ( @@ -172,27 +172,29 @@ export const Placements: Story = { render: () => , } -const CustomDelayDemo = () => ( - - - Snappy trigger - - -
-
Fast hover
-
- Base UI defaults (600ms / 300ms) are tuned for link previews. Override per trigger for - denser UIs. +function CustomDelayDemo() { + return ( + + + Snappy trigger + + +
+
Fast hover
+
+ Base UI defaults (600ms / 300ms) are tuned for link previews. Override per trigger for + denser UIs. +
-
- - -) + + + ) +} export const CustomDelays: Story = { render: () => , diff --git a/packages/dify-ui/src/scroll-area/__tests__/index.spec.tsx b/packages/dify-ui/src/scroll-area/__tests__/index.spec.tsx index 4f451bb0e31..9bfa04cbed8 100644 --- a/packages/dify-ui/src/scroll-area/__tests__/index.spec.tsx +++ b/packages/dify-ui/src/scroll-area/__tests__/index.spec.tsx @@ -1,4 +1,4 @@ -import type * as React from 'react' +import * as React from 'react' import { render } from 'vitest-browser-react' import { ScrollArea, @@ -105,7 +105,7 @@ describe('scroll area', () => { it('should keep accessible region semantics on the viewport', async () => { const screen = await render( - <> +

Installed apps

{ - , +
, ) const viewport = screen.getByRole('region', { name: 'Installed apps' }) diff --git a/packages/dify-ui/src/segmented-control/__tests__/index.spec.tsx b/packages/dify-ui/src/segmented-control/__tests__/index.spec.tsx index 2e12732a9ec..2a3b5e27644 100644 --- a/packages/dify-ui/src/segmented-control/__tests__/index.spec.tsx +++ b/packages/dify-ui/src/segmented-control/__tests__/index.spec.tsx @@ -1,11 +1,11 @@ -import type * as React from 'react' +import * as React from 'react' import { userEvent } from 'vite-plus/test/browser' import { render } from 'vitest-browser-react' import { SegmentedControl, SegmentedControlDivider, SegmentedControlItem } from '../index' function SegmentedControlTypeExamples() { return ( - <> + value={10} onValueChange={() => {}} aria-label="Page size"> value={10}>10 value={20}>20 @@ -14,7 +14,7 @@ function SegmentedControlTypeExamples() { One - + ) } diff --git a/packages/dify-ui/src/segmented-control/index.stories.tsx b/packages/dify-ui/src/segmented-control/index.stories.tsx index b8bbc174eed..d9e3ed5b8f6 100644 --- a/packages/dify-ui/src/segmented-control/index.stories.tsx +++ b/packages/dify-ui/src/segmented-control/index.stories.tsx @@ -30,14 +30,18 @@ type SegmentedControlProps = { noPadding?: boolean } -const Icon = () =>