mirror of https://github.com/langgenius/dify.git
Merge branch 'feat/web-overlay-phase0-primitives' into test/ui-primitive-wrapper-tests-pr
This commit is contained in:
commit
ceb8c8bf1e
|
|
@ -25,24 +25,36 @@ type DropdownMenuContentProps = {
|
|||
alignOffset?: number
|
||||
className?: string
|
||||
popupClassName?: string
|
||||
positionerProps?: Omit<
|
||||
React.ComponentPropsWithoutRef<typeof Menu.Positioner>,
|
||||
'children' | 'className' | 'side' | 'align' | 'sideOffset' | 'alignOffset'
|
||||
>
|
||||
popupProps?: Omit<
|
||||
React.ComponentPropsWithoutRef<typeof Menu.Popup>,
|
||||
'children' | 'className'
|
||||
>
|
||||
}
|
||||
|
||||
type DropdownMenuPopupProps = Required<Pick<DropdownMenuContentProps, 'children'>> & {
|
||||
type DropdownMenuPopupRenderProps = Required<Pick<DropdownMenuContentProps, 'children'>> & {
|
||||
placement: Placement
|
||||
sideOffset: number
|
||||
alignOffset: number
|
||||
className?: string
|
||||
popupClassName?: string
|
||||
positionerProps?: DropdownMenuContentProps['positionerProps']
|
||||
popupProps?: DropdownMenuContentProps['popupProps']
|
||||
}
|
||||
|
||||
function DropdownMenuPopup({
|
||||
function renderDropdownMenuPopup({
|
||||
children,
|
||||
placement,
|
||||
sideOffset,
|
||||
alignOffset,
|
||||
className,
|
||||
popupClassName,
|
||||
}: DropdownMenuPopupProps) {
|
||||
positionerProps,
|
||||
popupProps,
|
||||
}: DropdownMenuPopupRenderProps) {
|
||||
const { side, align } = parsePlacement(placement)
|
||||
|
||||
return (
|
||||
|
|
@ -53,6 +65,7 @@ function DropdownMenuPopup({
|
|||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
className={cn('outline-none', className)}
|
||||
{...positionerProps}
|
||||
>
|
||||
<Menu.Popup
|
||||
className={cn(
|
||||
|
|
@ -60,6 +73,7 @@ function DropdownMenuPopup({
|
|||
'origin-[var(--transform-origin)] transition-[transform,scale,opacity] data-[ending-style]:scale-95 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0',
|
||||
popupClassName,
|
||||
)}
|
||||
{...popupProps}
|
||||
>
|
||||
{children}
|
||||
</Menu.Popup>
|
||||
|
|
@ -75,18 +89,19 @@ export function DropdownMenuContent({
|
|||
alignOffset = 0,
|
||||
className,
|
||||
popupClassName,
|
||||
positionerProps,
|
||||
popupProps,
|
||||
}: DropdownMenuContentProps) {
|
||||
return (
|
||||
<DropdownMenuPopup
|
||||
placement={placement}
|
||||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
className={className}
|
||||
popupClassName={popupClassName}
|
||||
>
|
||||
{children}
|
||||
</DropdownMenuPopup>
|
||||
)
|
||||
return renderDropdownMenuPopup({
|
||||
children,
|
||||
placement,
|
||||
sideOffset,
|
||||
alignOffset,
|
||||
className,
|
||||
popupClassName,
|
||||
positionerProps,
|
||||
popupProps,
|
||||
})
|
||||
}
|
||||
|
||||
type DropdownMenuSubTriggerProps = React.ComponentPropsWithoutRef<typeof Menu.SubmenuTrigger> & {
|
||||
|
|
@ -118,6 +133,8 @@ type DropdownMenuSubContentProps = {
|
|||
alignOffset?: number
|
||||
className?: string
|
||||
popupClassName?: string
|
||||
positionerProps?: DropdownMenuContentProps['positionerProps']
|
||||
popupProps?: DropdownMenuContentProps['popupProps']
|
||||
}
|
||||
|
||||
export function DropdownMenuSubContent({
|
||||
|
|
@ -127,18 +144,19 @@ export function DropdownMenuSubContent({
|
|||
alignOffset = 0,
|
||||
className,
|
||||
popupClassName,
|
||||
positionerProps,
|
||||
popupProps,
|
||||
}: DropdownMenuSubContentProps) {
|
||||
return (
|
||||
<DropdownMenuPopup
|
||||
placement={placement}
|
||||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
className={className}
|
||||
popupClassName={popupClassName}
|
||||
>
|
||||
{children}
|
||||
</DropdownMenuPopup>
|
||||
)
|
||||
return renderDropdownMenuPopup({
|
||||
children,
|
||||
placement,
|
||||
sideOffset,
|
||||
alignOffset,
|
||||
className,
|
||||
popupClassName,
|
||||
positionerProps,
|
||||
popupProps,
|
||||
})
|
||||
}
|
||||
|
||||
type DropdownMenuItemProps = React.ComponentPropsWithoutRef<typeof Menu.Item> & {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,14 @@ type PopoverContentProps = {
|
|||
alignOffset?: number
|
||||
className?: string
|
||||
popupClassName?: string
|
||||
positionerProps?: Omit<
|
||||
React.ComponentPropsWithoutRef<typeof BasePopover.Positioner>,
|
||||
'children' | 'className' | 'side' | 'align' | 'sideOffset' | 'alignOffset'
|
||||
>
|
||||
popupProps?: Omit<
|
||||
React.ComponentPropsWithoutRef<typeof BasePopover.Popup>,
|
||||
'children' | 'className'
|
||||
>
|
||||
}
|
||||
|
||||
export function PopoverContent({
|
||||
|
|
@ -28,6 +36,8 @@ export function PopoverContent({
|
|||
alignOffset = 0,
|
||||
className,
|
||||
popupClassName,
|
||||
positionerProps,
|
||||
popupProps,
|
||||
}: PopoverContentProps) {
|
||||
const { side, align } = parsePlacement(placement)
|
||||
|
||||
|
|
@ -39,6 +49,7 @@ export function PopoverContent({
|
|||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
className={cn('outline-none', className)}
|
||||
{...positionerProps}
|
||||
>
|
||||
<BasePopover.Popup
|
||||
className={cn(
|
||||
|
|
@ -46,6 +57,7 @@ export function PopoverContent({
|
|||
'origin-[var(--transform-origin)] transition-[transform,scale,opacity] data-[ending-style]:scale-95 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0',
|
||||
popupClassName,
|
||||
)}
|
||||
{...popupProps}
|
||||
>
|
||||
{children}
|
||||
</BasePopover.Popup>
|
||||
|
|
|
|||
|
|
@ -42,6 +42,18 @@ type SelectContentProps = {
|
|||
className?: string
|
||||
popupClassName?: string
|
||||
listClassName?: string
|
||||
positionerProps?: Omit<
|
||||
React.ComponentPropsWithoutRef<typeof BaseSelect.Positioner>,
|
||||
'children' | 'className' | 'side' | 'align' | 'sideOffset' | 'alignOffset'
|
||||
>
|
||||
popupProps?: Omit<
|
||||
React.ComponentPropsWithoutRef<typeof BaseSelect.Popup>,
|
||||
'children' | 'className'
|
||||
>
|
||||
listProps?: Omit<
|
||||
React.ComponentPropsWithoutRef<typeof BaseSelect.List>,
|
||||
'children' | 'className'
|
||||
>
|
||||
}
|
||||
|
||||
export function SelectContent({
|
||||
|
|
@ -52,6 +64,9 @@ export function SelectContent({
|
|||
className,
|
||||
popupClassName,
|
||||
listClassName,
|
||||
positionerProps,
|
||||
popupProps,
|
||||
listProps,
|
||||
}: SelectContentProps) {
|
||||
const { side, align } = parsePlacement(placement)
|
||||
|
||||
|
|
@ -63,6 +78,7 @@ export function SelectContent({
|
|||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
className={cn('outline-none', className)}
|
||||
{...positionerProps}
|
||||
>
|
||||
<BaseSelect.Popup
|
||||
className={cn(
|
||||
|
|
@ -70,8 +86,12 @@ export function SelectContent({
|
|||
'origin-[var(--transform-origin)] transition-[transform,scale,opacity] data-[ending-style]:scale-95 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0',
|
||||
popupClassName,
|
||||
)}
|
||||
{...popupProps}
|
||||
>
|
||||
<BaseSelect.List className={cn('max-h-80 min-w-[10rem] overflow-auto p-1 outline-none', listClassName)}>
|
||||
<BaseSelect.List
|
||||
className={cn('max-h-80 min-w-[10rem] overflow-auto p-1 outline-none', listClassName)}
|
||||
{...listProps}
|
||||
>
|
||||
{children}
|
||||
</BaseSelect.List>
|
||||
</BaseSelect.Popup>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ This command lints the entire project and is intended for final verification bef
|
|||
If a new rule causes many existing code errors or automatic fixes generate too many diffs, do not use the `--fix` option for automatic fixes.
|
||||
You can introduce the rule first, then use the `--suppress-all` option to temporarily suppress these errors, and gradually fix them in subsequent changes.
|
||||
|
||||
For overlay migration policy and cleanup phases, see [Overlay Migration Guide](./overlay-migration.md).
|
||||
|
||||
## Type Check
|
||||
|
||||
You should be able to see suggestions from TypeScript in your editor for all open files.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
# Overlay Migration Guide
|
||||
|
||||
This document tracks the migration away from legacy `portal-to-follow-elem` APIs.
|
||||
|
||||
## Scope
|
||||
|
||||
- Deprecated API: `@/app/components/base/portal-to-follow-elem`
|
||||
- Replacement primitives:
|
||||
- `@/app/components/base/ui/tooltip`
|
||||
- `@/app/components/base/ui/dropdown-menu`
|
||||
- `@/app/components/base/ui/popover`
|
||||
- `@/app/components/base/ui/dialog`
|
||||
- `@/app/components/base/ui/select`
|
||||
- Tracking issue: https://github.com/langgenius/dify/issues/32767
|
||||
|
||||
## ESLint policy
|
||||
|
||||
- `no-restricted-imports` blocks new usage of `portal-to-follow-elem`.
|
||||
- The rule is enabled for normal source files and test files are excluded.
|
||||
- Legacy `app/components/base/*` callers are temporarily allowlisted in ESLint config.
|
||||
- New files must not be added to the allowlist without migration owner approval.
|
||||
|
||||
## Migration phases
|
||||
|
||||
1. Business/UI features outside `app/components/base/**`
|
||||
- Migrate old calls to semantic primitives.
|
||||
- Keep `eslint-suppressions.json` stable or shrinking.
|
||||
1. Legacy base components in allowlist
|
||||
- Migrate allowlisted base callers gradually.
|
||||
- Remove migrated files from allowlist immediately.
|
||||
1. Cleanup
|
||||
- Remove remaining suppressions for `no-restricted-imports`.
|
||||
- Remove legacy `portal-to-follow-elem` implementation.
|
||||
|
||||
## Suppression maintenance
|
||||
|
||||
- After each migration batch, run:
|
||||
|
||||
```sh
|
||||
pnpm eslint --prune-suppressions --pass-on-unpruned-suppressions <changed-files>
|
||||
```
|
||||
|
||||
- Never increase suppressions to bypass new code.
|
||||
- Prefer direct migration over adding suppression entries.
|
||||
|
||||
## React Refresh policy for base UI primitives
|
||||
|
||||
- We keep primitive aliases (for example `DropdownMenu = Menu.Root`) in the same module.
|
||||
- To avoid IDE noise, `react-refresh/only-export-components` is configured with explicit `allowExportNames` for the base UI primitive surface.
|
||||
- Do not use file-level `eslint-disable` comments for this policy.
|
||||
|
|
@ -6,6 +6,7 @@ import hyoban from 'eslint-plugin-hyoban'
|
|||
import sonar from 'eslint-plugin-sonarjs'
|
||||
import storybook from 'eslint-plugin-storybook'
|
||||
import dify from './eslint-rules/index.js'
|
||||
import { BASE_UI_PRIMITIVE_EXPORT_NAMES, OVERLAY_MIGRATION_LEGACY_BASE_FILES } from './eslint.constants.mjs'
|
||||
|
||||
// Enable Tailwind CSS IntelliSense mode for ESLint runs
|
||||
// See: tailwind-css-plugin.ts
|
||||
|
|
@ -147,17 +148,19 @@ export default antfu(
|
|||
},
|
||||
{
|
||||
name: 'dify/base-ui-primitives',
|
||||
files: ['app/components/base/ui/**/*.ts', 'app/components/base/ui/**/*.tsx'],
|
||||
files: ['app/components/base/ui/**/*.tsx'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': 'off',
|
||||
'react-refresh/only-export-components': ['error', {
|
||||
allowExportNames: BASE_UI_PRIMITIVE_EXPORT_NAMES,
|
||||
}],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dify/overlay-migration',
|
||||
files: [GLOB_TS, GLOB_TSX],
|
||||
ignores: [
|
||||
'app/components/base/**',
|
||||
...GLOB_TESTS,
|
||||
...OVERLAY_MIGRATION_LEGACY_BASE_FILES,
|
||||
],
|
||||
rules: {
|
||||
'no-restricted-imports': ['error', {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
export const BASE_UI_PRIMITIVE_EXPORT_NAMES = [
|
||||
'Dialog',
|
||||
'DialogClose',
|
||||
'DialogContent',
|
||||
'DialogDescription',
|
||||
'DialogTitle',
|
||||
'DialogTrigger',
|
||||
'DropdownMenu',
|
||||
'DropdownMenuCheckboxItem',
|
||||
'DropdownMenuCheckboxItemIndicator',
|
||||
'DropdownMenuContent',
|
||||
'DropdownMenuGroup',
|
||||
'DropdownMenuGroupLabel',
|
||||
'DropdownMenuItem',
|
||||
'DropdownMenuPortal',
|
||||
'DropdownMenuRadioGroup',
|
||||
'DropdownMenuRadioItem',
|
||||
'DropdownMenuRadioItemIndicator',
|
||||
'DropdownMenuSeparator',
|
||||
'DropdownMenuSub',
|
||||
'DropdownMenuSubContent',
|
||||
'DropdownMenuSubTrigger',
|
||||
'DropdownMenuTrigger',
|
||||
'Popover',
|
||||
'PopoverClose',
|
||||
'PopoverContent',
|
||||
'PopoverDescription',
|
||||
'PopoverTitle',
|
||||
'PopoverTrigger',
|
||||
'Select',
|
||||
'SelectContent',
|
||||
'SelectGroup',
|
||||
'SelectGroupLabel',
|
||||
'SelectItem',
|
||||
'SelectSeparator',
|
||||
'SelectTrigger',
|
||||
'SelectValue',
|
||||
'Tooltip',
|
||||
'TooltipContent',
|
||||
'TooltipProvider',
|
||||
'TooltipTrigger',
|
||||
]
|
||||
|
||||
export const OVERLAY_MIGRATION_LEGACY_BASE_FILES = [
|
||||
'app/components/base/chat/chat-with-history/header/mobile-operation-dropdown.tsx',
|
||||
'app/components/base/chat/chat-with-history/header/operation.tsx',
|
||||
'app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown.tsx',
|
||||
'app/components/base/chat/chat-with-history/sidebar/operation.tsx',
|
||||
'app/components/base/chat/chat/citation/popup.tsx',
|
||||
'app/components/base/chat/chat/citation/progress-tooltip.tsx',
|
||||
'app/components/base/chat/chat/citation/tooltip.tsx',
|
||||
'app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown.tsx',
|
||||
'app/components/base/chip/index.tsx',
|
||||
'app/components/base/date-and-time-picker/date-picker/index.tsx',
|
||||
'app/components/base/date-and-time-picker/time-picker/index.tsx',
|
||||
'app/components/base/dropdown/index.tsx',
|
||||
'app/components/base/features/new-feature-panel/file-upload/setting-modal.tsx',
|
||||
'app/components/base/features/new-feature-panel/text-to-speech/voice-settings.tsx',
|
||||
'app/components/base/file-uploader/file-from-link-or-local/index.tsx',
|
||||
'app/components/base/image-uploader/chat-image-uploader.tsx',
|
||||
'app/components/base/image-uploader/text-generation-image-uploader.tsx',
|
||||
'app/components/base/modal/modal.tsx',
|
||||
'app/components/base/prompt-editor/plugins/context-block/component.tsx',
|
||||
'app/components/base/prompt-editor/plugins/history-block/component.tsx',
|
||||
'app/components/base/select/custom.tsx',
|
||||
'app/components/base/select/index.tsx',
|
||||
'app/components/base/select/pure.tsx',
|
||||
'app/components/base/sort/index.tsx',
|
||||
'app/components/base/tag-management/filter.tsx',
|
||||
'app/components/base/theme-selector.tsx',
|
||||
'app/components/base/tooltip/index.tsx',
|
||||
]
|
||||
Loading…
Reference in New Issue