refactor(web): align UI component APIs with shadcn v4 best practices (#35328)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
yyh 2026-04-16 21:30:12 +08:00 committed by GitHub
parent abb84f1c38
commit c966e281d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 162 additions and 157 deletions

View File

@ -167,7 +167,7 @@ const AppCardOperationsMenu: React.FC<AppCardOperationsMenuProps> = ({
</>
)}
<DropdownMenuItem
destructive
variant="destructive"
className="gap-2 px-3"
onClick={e => handleMenuAction(e, onDelete)}
>

View File

@ -30,7 +30,7 @@ const NumberInputField = ({
className,
inputClassName,
unit,
size = 'regular',
size = 'medium',
...props
}: NumberInputFieldProps) => {
const field = useFieldContext<number>()

View File

@ -62,11 +62,11 @@ const ParamItem: FC<Props> = ({ className, id, name, noTooltip, tip, step = 0.1,
value={value}
onValueChange={nextValue => onChange(id, nextValue ?? min)}
>
<NumberFieldGroup size="regular">
<NumberFieldInput size="regular" className="w-[72px]" />
<NumberFieldGroup>
<NumberFieldInput className="w-[72px]" />
<NumberFieldControls>
<NumberFieldIncrement size="regular" />
<NumberFieldDecrement size="regular" />
<NumberFieldIncrement />
<NumberFieldDecrement />
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>

View File

@ -112,8 +112,8 @@ describe('context-menu wrapper', () => {
})
})
describe('destructive prop behavior', () => {
it.each([true, false])('should remain interactive and not leak destructive prop on item when destructive is %s', (destructive) => {
describe('variant prop behavior', () => {
it.each(['default', 'destructive'] as const)('should remain interactive and set data-variant on item when variant is %s', (variant) => {
const handleClick = vi.fn()
render(
@ -121,9 +121,9 @@ describe('context-menu wrapper', () => {
<ContextMenuTrigger aria-label="context trigger">Open</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
destructive={destructive}
variant={variant}
aria-label="menu action"
id={`context-item-${String(destructive)}`}
id={`context-item-${variant}`}
onClick={handleClick}
>
Item label
@ -134,12 +134,12 @@ describe('context-menu wrapper', () => {
const item = screen.getByRole('menuitem', { name: 'menu action' })
fireEvent.click(item)
expect(item).toHaveAttribute('id', `context-item-${String(destructive)}`)
expect(item).not.toHaveAttribute('destructive')
expect(item).toHaveAttribute('id', `context-item-${variant}`)
expect(item).toHaveAttribute('data-variant', variant)
expect(handleClick).toHaveBeenCalledTimes(1)
})
it.each([true, false])('should remain interactive and not leak destructive prop on submenu trigger when destructive is %s', (destructive) => {
it.each(['default', 'destructive'] as const)('should remain interactive and set data-variant on submenu trigger when variant is %s', (variant) => {
const handleClick = vi.fn()
render(
@ -148,9 +148,9 @@ describe('context-menu wrapper', () => {
<ContextMenuContent>
<ContextMenuSub open>
<ContextMenuSubTrigger
destructive={destructive}
variant={variant}
aria-label="submenu action"
id={`context-sub-${String(destructive)}`}
id={`context-sub-${variant}`}
onClick={handleClick}
>
Trigger item
@ -162,21 +162,21 @@ describe('context-menu wrapper', () => {
const trigger = screen.getByRole('menuitem', { name: 'submenu action' })
fireEvent.click(trigger)
expect(trigger).toHaveAttribute('id', `context-sub-${String(destructive)}`)
expect(trigger).not.toHaveAttribute('destructive')
expect(trigger).toHaveAttribute('id', `context-sub-${variant}`)
expect(trigger).toHaveAttribute('data-variant', variant)
expect(handleClick).toHaveBeenCalledTimes(1)
})
it.each([true, false])('should remain interactive and not leak destructive prop on link item when destructive is %s', (destructive) => {
it.each(['default', 'destructive'] as const)('should remain interactive and set data-variant on link item when variant is %s', (variant) => {
render(
<ContextMenu open>
<ContextMenuTrigger aria-label="context trigger">Open</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuLinkItem
destructive={destructive}
variant={variant}
href="https://example.com/docs"
aria-label="context docs link"
id={`context-link-${String(destructive)}`}
id={`context-link-${variant}`}
target="_blank"
rel="noopener noreferrer"
>
@ -188,8 +188,8 @@ describe('context-menu wrapper', () => {
const link = screen.getByRole('menuitem', { name: 'context docs link' })
expect(link.tagName.toLowerCase()).toBe('a')
expect(link).toHaveAttribute('id', `context-link-${String(destructive)}`)
expect(link).not.toHaveAttribute('destructive')
expect(link).toHaveAttribute('id', `context-link-${variant}`)
expect(link).toHaveAttribute('data-variant', variant)
})
})

View File

@ -6,8 +6,8 @@ import {
ContextMenuCheckboxItemIndicator,
ContextMenuContent,
ContextMenuGroup,
ContextMenuGroupLabel,
ContextMenuItem,
ContextMenuLabel,
ContextMenuLinkItem,
ContextMenuRadioGroup,
ContextMenuRadioItem,
@ -85,14 +85,14 @@ export const WithGroupLabel: Story = {
<TriggerArea />
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuGroupLabel>Actions</ContextMenuGroupLabel>
<ContextMenuLabel>Actions</ContextMenuLabel>
<ContextMenuItem>Rename</ContextMenuItem>
<ContextMenuItem>Duplicate</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuGroupLabel>Danger Zone</ContextMenuGroupLabel>
<ContextMenuItem destructive>Delete</ContextMenuItem>
<ContextMenuLabel>Danger Zone</ContextMenuLabel>
<ContextMenuItem variant="destructive">Delete</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
@ -171,7 +171,7 @@ export const WithLinkItems: Story = {
Product Roadmap
</ContextMenuLinkItem>
<ContextMenuSeparator />
<ContextMenuLinkItem destructive href="https://example.com/delete" rel="noopener noreferrer" target="_blank">
<ContextMenuLinkItem variant="destructive" href="https://example.com/delete" rel="noopener noreferrer" target="_blank">
Dangerous External Action
</ContextMenuLinkItem>
</ContextMenuContent>
@ -205,7 +205,7 @@ export const Complex: Story = {
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuItem destructive>
<ContextMenuItem variant="destructive">
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" />
Delete
</ContextMenuItem>

View File

@ -1,13 +1,15 @@
'use client'
import type { ReactNode } from 'react'
import type { OverlayItemVariant } from '@/app/components/base/ui/overlay-shared'
import type { Placement } from '@/app/components/base/ui/placement'
import { ContextMenu as BaseContextMenu } from '@base-ui/react/context-menu'
import { cn } from '@langgenius/dify-ui/cn'
import {
overlayBackdropClassName,
overlayGroupLabelClassName,
overlayDestructiveClassName,
overlayIndicatorClassName,
overlayLabelClassName,
overlayPopupAnimationClassName,
overlayPopupBaseClassName,
overlayRowClassName,
@ -114,35 +116,37 @@ export function ContextMenuContent({
}
type ContextMenuItemProps = BaseContextMenu.Item.Props & {
destructive?: boolean
variant?: OverlayItemVariant
}
export function ContextMenuItem({
className,
destructive,
variant = 'default',
...props
}: ContextMenuItemProps) {
return (
<BaseContextMenu.Item
className={cn(overlayRowClassName, destructive && 'text-text-destructive data-highlighted:bg-state-destructive-hover', className)}
data-variant={variant}
className={cn(overlayRowClassName, overlayDestructiveClassName, className)}
{...props}
/>
)
}
type ContextMenuLinkItemProps = BaseContextMenu.LinkItem.Props & {
destructive?: boolean
variant?: OverlayItemVariant
}
export function ContextMenuLinkItem({
className,
destructive,
variant = 'default',
closeOnClick = true,
...props
}: ContextMenuLinkItemProps) {
return (
<BaseContextMenu.LinkItem
className={cn(overlayRowClassName, destructive && 'text-text-destructive data-highlighted:bg-state-destructive-hover', className)}
data-variant={variant}
className={cn(overlayRowClassName, overlayDestructiveClassName, className)}
closeOnClick={closeOnClick}
{...props}
/>
@ -202,18 +206,19 @@ export function ContextMenuRadioItemIndicator({
}
type ContextMenuSubTriggerProps = BaseContextMenu.SubmenuTrigger.Props & {
destructive?: boolean
variant?: OverlayItemVariant
}
export function ContextMenuSubTrigger({
className,
destructive,
variant = 'default',
children,
...props
}: ContextMenuSubTriggerProps) {
return (
<BaseContextMenu.SubmenuTrigger
className={cn(overlayRowClassName, destructive && 'text-text-destructive data-highlighted:bg-state-destructive-hover', className)}
data-variant={variant}
className={cn(overlayRowClassName, overlayDestructiveClassName, className)}
{...props}
>
{children}
@ -255,13 +260,13 @@ export function ContextMenuSubContent({
})
}
export function ContextMenuGroupLabel({
export function ContextMenuLabel({
className,
...props
}: BaseContextMenu.GroupLabel.Props) {
return (
<BaseContextMenu.GroupLabel
className={cn(overlayGroupLabelClassName, className)}
className={cn(overlayLabelClassName, className)}
{...props}
/>
)

View File

@ -177,7 +177,7 @@ describe('dropdown-menu wrapper', () => {
expect(screen.getByRole('menuitem', { name: 'Trigger item' })).toBeInTheDocument()
})
it.each([true, false])('should remain interactive and not leak destructive prop when destructive is %s', (destructive) => {
it.each(['default', 'destructive'] as const)('should remain interactive and set data-variant on submenu trigger when variant is %s', (variant) => {
const handleClick = vi.fn()
render(
@ -186,9 +186,9 @@ describe('dropdown-menu wrapper', () => {
<DropdownMenuContent>
<DropdownMenuSub open>
<DropdownMenuSubTrigger
destructive={destructive}
variant={variant}
aria-label="submenu action"
id={`submenu-trigger-${String(destructive)}`}
id={`submenu-trigger-${variant}`}
onClick={handleClick}
>
Trigger item
@ -201,14 +201,14 @@ describe('dropdown-menu wrapper', () => {
const subTrigger = screen.getByRole('menuitem', { name: 'submenu action' })
fireEvent.click(subTrigger)
expect(subTrigger).toHaveAttribute('id', `submenu-trigger-${String(destructive)}`)
expect(subTrigger).not.toHaveAttribute('destructive')
expect(subTrigger).toHaveAttribute('id', `submenu-trigger-${variant}`)
expect(subTrigger).toHaveAttribute('data-variant', variant)
expect(handleClick).toHaveBeenCalledTimes(1)
})
})
describe('DropdownMenuItem', () => {
it.each([true, false])('should remain interactive and not leak destructive prop when destructive is %s', (destructive) => {
it.each(['default', 'destructive'] as const)('should remain interactive and set data-variant when variant is %s', (variant) => {
const handleClick = vi.fn()
render(
@ -216,9 +216,9 @@ describe('dropdown-menu wrapper', () => {
<DropdownMenuTrigger aria-label="menu trigger">Open</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
destructive={destructive}
variant={variant}
aria-label="menu action"
id={`menu-item-${String(destructive)}`}
id={`menu-item-${variant}`}
onClick={handleClick}
>
Item label
@ -230,8 +230,8 @@ describe('dropdown-menu wrapper', () => {
const item = screen.getByRole('menuitem', { name: 'menu action' })
fireEvent.click(item)
expect(item).toHaveAttribute('id', `menu-item-${String(destructive)}`)
expect(item).not.toHaveAttribute('destructive')
expect(item).toHaveAttribute('id', `menu-item-${variant}`)
expect(item).toHaveAttribute('data-variant', variant)
expect(handleClick).toHaveBeenCalledTimes(1)
})
})
@ -299,7 +299,7 @@ describe('dropdown-menu wrapper', () => {
expect(link).toHaveTextContent('Account settings')
})
it.each([true, false])('should remain interactive and not leak destructive prop when destructive is %s', (destructive) => {
it.each(['default', 'destructive'] as const)('should remain interactive and set data-variant when variant is %s', (variant) => {
const handleClick = vi.fn()
render(
@ -307,10 +307,10 @@ describe('dropdown-menu wrapper', () => {
<DropdownMenuTrigger aria-label="menu trigger">Open</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLinkItem
destructive={destructive}
variant={variant}
href="https://example.com/docs"
aria-label="docs link"
id={`menu-link-${String(destructive)}`}
id={`menu-link-${variant}`}
onClick={handleClick}
>
Docs
@ -323,8 +323,8 @@ describe('dropdown-menu wrapper', () => {
fireEvent.click(link)
expect(link.tagName.toLowerCase()).toBe('a')
expect(link).toHaveAttribute('id', `menu-link-${String(destructive)}`)
expect(link).not.toHaveAttribute('destructive')
expect(link).toHaveAttribute('id', `menu-link-${variant}`)
expect(link).toHaveAttribute('data-variant', variant)
expect(handleClick).toHaveBeenCalledTimes(1)
})
})

View File

@ -6,8 +6,8 @@ import {
DropdownMenuCheckboxItemIndicator,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuGroupLabel,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuLinkItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
@ -80,13 +80,13 @@ export const WithGroupLabel: Story = {
<TriggerButton />
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuGroupLabel>Actions</DropdownMenuGroupLabel>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuGroupLabel>Export</DropdownMenuGroupLabel>
<DropdownMenuLabel>Export</DropdownMenuLabel>
<DropdownMenuItem>Export as PDF</DropdownMenuItem>
<DropdownMenuItem>Export as CSV</DropdownMenuItem>
</DropdownMenuGroup>
@ -103,7 +103,7 @@ export const WithDestructiveItem: Story = {
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem destructive>Delete</DropdownMenuItem>
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
),
@ -202,7 +202,7 @@ export const WithDisabledItems: Story = {
<DropdownMenuItem>Archive</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem disabled>Restore</DropdownMenuItem>
<DropdownMenuItem destructive>Delete</DropdownMenuItem>
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
),
@ -226,7 +226,7 @@ export const WithIcons: Story = {
Archive
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem destructive>
<DropdownMenuItem variant="destructive">
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" />
Delete
</DropdownMenuItem>
@ -260,7 +260,7 @@ const ComplexDemo = () => {
<TriggerButton label="Actions" />
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuGroupLabel>Edit</DropdownMenuGroupLabel>
<DropdownMenuLabel>Edit</DropdownMenuLabel>
<DropdownMenuItem>
<span aria-hidden className="i-ri-pencil-line size-4 shrink-0 text-text-tertiary" />
Rename
@ -297,7 +297,7 @@ const ComplexDemo = () => {
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuGroupLabel>Sort by</DropdownMenuGroupLabel>
<DropdownMenuLabel>Sort by</DropdownMenuLabel>
<DropdownMenuRadioGroup value={sortOrder} onValueChange={setSortOrder}>
<DropdownMenuRadioItem value="newest">
Newest first
@ -320,7 +320,7 @@ const ComplexDemo = () => {
<DropdownMenuCheckboxItemIndicator />
</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
<DropdownMenuItem destructive>
<DropdownMenuItem variant="destructive">
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" />
Delete
</DropdownMenuItem>

View File

@ -1,12 +1,14 @@
'use client'
import type { ReactNode } from 'react'
import type { OverlayItemVariant } from '@/app/components/base/ui/overlay-shared'
import type { Placement } from '@/app/components/base/ui/placement'
import { Menu } from '@base-ui/react/menu'
import { cn } from '@langgenius/dify-ui/cn'
import {
overlayGroupLabelClassName,
overlayDestructiveClassName,
overlayIndicatorClassName,
overlayLabelClassName,
overlayPopupAnimationClassName,
overlayPopupBaseClassName,
overlayRowClassName,
@ -72,13 +74,13 @@ export function DropdownMenuCheckboxItemIndicator({
)
}
export function DropdownMenuGroupLabel({
export function DropdownMenuLabel({
className,
...props
}: Menu.GroupLabel.Props) {
return (
<Menu.GroupLabel
className={cn(overlayGroupLabelClassName, className)}
className={cn(overlayLabelClassName, className)}
{...props}
/>
)
@ -171,18 +173,19 @@ export function DropdownMenuContent({
}
type DropdownMenuSubTriggerProps = Menu.SubmenuTrigger.Props & {
destructive?: boolean
variant?: OverlayItemVariant
}
export function DropdownMenuSubTrigger({
className,
destructive,
variant = 'default',
children,
...props
}: DropdownMenuSubTriggerProps) {
return (
<Menu.SubmenuTrigger
className={cn(overlayRowClassName, destructive && 'text-text-destructive data-highlighted:bg-state-destructive-hover', className)}
data-variant={variant}
className={cn(overlayRowClassName, overlayDestructiveClassName, className)}
{...props}
>
{children}
@ -225,35 +228,37 @@ export function DropdownMenuSubContent({
}
type DropdownMenuItemProps = Menu.Item.Props & {
destructive?: boolean
variant?: OverlayItemVariant
}
export function DropdownMenuItem({
className,
destructive,
variant = 'default',
...props
}: DropdownMenuItemProps) {
return (
<Menu.Item
className={cn(overlayRowClassName, destructive && 'text-text-destructive data-highlighted:bg-state-destructive-hover', className)}
data-variant={variant}
className={cn(overlayRowClassName, overlayDestructiveClassName, className)}
{...props}
/>
)
}
type DropdownMenuLinkItemProps = Menu.LinkItem.Props & {
destructive?: boolean
variant?: OverlayItemVariant
}
export function DropdownMenuLinkItem({
className,
destructive,
variant = 'default',
closeOnClick = true,
...props
}: DropdownMenuLinkItemProps) {
return (
<Menu.LinkItem
className={cn(overlayRowClassName, destructive && 'text-text-destructive data-highlighted:bg-state-destructive-hover', className)}
data-variant={variant}
className={cn(overlayRowClassName, overlayDestructiveClassName, className)}
closeOnClick={closeOnClick}
{...props}
/>

View File

@ -68,7 +68,7 @@ const renderNumberField = ({
describe('NumberField wrapper', () => {
// Group and input wrappers should preserve the design-system variants and DOM defaults.
describe('Group and input', () => {
it('should apply regular group classes by default and merge custom className', () => {
it('should apply medium group classes by default and merge custom className', () => {
renderNumberField({
groupProps: {
className: 'custom-group',
@ -124,7 +124,7 @@ describe('NumberField wrapper', () => {
// Unit and controls wrappers should preserve layout tokens and HTML passthrough props.
describe('Unit and controls', () => {
it.each([
['regular', 'pr-2'],
['medium', 'pr-2'],
['large', 'pr-2.5'],
] as const)('should apply the %s unit spacing variant', (size, spacingClass) => {
renderNumberField({
@ -215,11 +215,11 @@ describe('NumberField wrapper', () => {
<span id="increment-label">Increment from label</span>
<span id="decrement-label">Decrement from label</span>
<NumberField defaultValue={8}>
<NumberFieldGroup size="regular">
<NumberFieldInput aria-label="Amount" size="regular" />
<NumberFieldGroup size="medium">
<NumberFieldInput aria-label="Amount" size="medium" />
<NumberFieldControls>
<NumberFieldIncrement aria-labelledby="increment-label" size="regular" />
<NumberFieldDecrement aria-labelledby="decrement-label" size="regular" />
<NumberFieldIncrement aria-labelledby="increment-label" size="medium" />
<NumberFieldDecrement aria-labelledby="decrement-label" size="medium" />
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
@ -234,7 +234,7 @@ describe('NumberField wrapper', () => {
})
it.each([
['regular', 'pt-1', 'pb-1'],
['medium', 'pt-1', 'pb-1'],
['large', 'pt-1.5', 'pb-1.5'],
] as const)('should apply the %s control button compound spacing classes', (size, incrementClass, decrementClass) => {
renderNumberField({

View File

@ -15,7 +15,7 @@ type DemoFieldProps = {
label: string
helperText: string
placeholder: string
size: 'regular' | 'large'
size: 'medium' | 'large'
unit?: string
defaultValue?: number | null
min?: number
@ -131,7 +131,7 @@ export const VariantMatrix: Story = {
label="Top K"
helperText="Regular size without suffix. Covers the regular group, input, and control button spacing."
placeholder="Set top K"
size="regular"
size="medium"
defaultValue={3}
min={1}
max={10}
@ -141,7 +141,7 @@ export const VariantMatrix: Story = {
label="Score threshold"
helperText="Regular size with a suffix so the regular unit variant is visible."
placeholder="Set threshold"
size="regular"
size="medium"
unit="%"
defaultValue={85}
min={0}
@ -180,7 +180,7 @@ export const DecimalInputs: Story = {
label="Score threshold"
helperText="Two-decimal precision with a 0.01 step, like retrieval tuning fields."
placeholder="0.00"
size="regular"
size="medium"
defaultValue={0.82}
min={0}
max={1}
@ -204,7 +204,7 @@ export const DecimalInputs: Story = {
label="Penalty"
helperText="Starts empty so the placeholder and empty numeric state are both visible."
placeholder="Optional"
size="regular"
size="medium"
defaultValue={null}
min={0}
max={2}
@ -236,7 +236,7 @@ export const BoundariesAndStates: Story = {
label="HTTP status code"
helperText="Integer-only style usage with tighter bounds from 100 to 599."
placeholder="200"
size="regular"
size="medium"
defaultValue={200}
min={100}
max={599}
@ -247,7 +247,7 @@ export const BoundariesAndStates: Story = {
label="Request timeout"
helperText="Bounded regular input with suffix, common in system settings."
placeholder="Set timeout"
size="regular"
size="medium"
unit="ms"
defaultValue={1200}
min={100}

View File

@ -21,12 +21,12 @@ export const numberFieldGroupVariants = cva(
{
variants: {
size: {
regular: 'rounded-lg',
medium: 'rounded-lg',
large: 'rounded-[10px]',
},
},
defaultVariants: {
size: 'regular',
size: 'medium',
},
},
)
@ -36,7 +36,7 @@ export type NumberFieldGroupProps = BaseNumberField.Group.Props & VariantProps<t
export function NumberFieldGroup({
className,
size = 'regular',
size = 'medium',
...props
}: NumberFieldGroupProps) {
return (
@ -57,12 +57,12 @@ export const numberFieldInputVariants = cva(
{
variants: {
size: {
regular: 'px-3 py-[7px] system-sm-regular',
medium: 'px-3 py-[7px] system-sm-regular',
large: 'px-4 py-2 system-md-regular',
},
},
defaultVariants: {
size: 'regular',
size: 'medium',
},
},
)
@ -71,7 +71,7 @@ export type NumberFieldInputProps = Omit<BaseNumberField.Input.Props, 'size'> &
export function NumberFieldInput({
className,
size = 'regular',
size = 'medium',
...props
}: NumberFieldInputProps) {
return (
@ -87,12 +87,12 @@ export const numberFieldUnitVariants = cva(
{
variants: {
size: {
regular: 'pr-2',
medium: 'pr-2',
large: 'pr-2.5',
},
},
defaultVariants: {
size: 'regular',
size: 'medium',
},
},
)
@ -101,7 +101,7 @@ export type NumberFieldUnitProps = HTMLAttributes<HTMLSpanElement> & VariantProp
export function NumberFieldUnit({
className,
size = 'regular',
size = 'medium',
...props
}: NumberFieldUnitProps) {
return (
@ -143,7 +143,7 @@ const numberFieldControlButtonVariants = cva(
{
variants: {
size: {
regular: '',
medium: '',
large: '',
},
direction: {
@ -153,12 +153,12 @@ const numberFieldControlButtonVariants = cva(
},
compoundVariants: [
{
size: 'regular',
size: 'medium',
direction: 'increment',
className: 'pt-1',
},
{
size: 'regular',
size: 'medium',
direction: 'decrement',
className: 'pb-1',
},
@ -174,7 +174,7 @@ const numberFieldControlButtonVariants = cva(
},
],
defaultVariants: {
size: 'regular',
size: 'medium',
direction: 'increment',
},
},
@ -193,7 +193,7 @@ const decrementAriaLabel = 'Decrement value'
export function NumberFieldIncrement({
className,
children,
size = 'regular',
size = 'medium',
...props
}: NumberFieldButtonProps) {
return (
@ -210,7 +210,7 @@ export function NumberFieldIncrement({
export function NumberFieldDecrement({
className,
children,
size = 'regular',
size = 'medium',
...props
}: NumberFieldButtonProps) {
return (

View File

@ -1,6 +1,9 @@
export type OverlayItemVariant = 'default' | 'destructive'
export const overlayRowClassName = 'mx-1 flex h-8 cursor-pointer select-none items-center gap-1 rounded-lg px-2 outline-hidden data-highlighted:bg-state-base-hover data-disabled:cursor-not-allowed data-disabled:opacity-30'
export const overlayDestructiveClassName = 'data-[variant=destructive]:text-text-destructive data-[variant=destructive]:data-highlighted:bg-state-destructive-hover'
export const overlayIndicatorClassName = 'ml-auto flex shrink-0 items-center text-text-accent'
export const overlayGroupLabelClassName = 'px-3 pb-0.5 pt-1 text-text-tertiary system-xs-medium-uppercase'
export const overlayLabelClassName = 'px-3 pb-0.5 pt-1 text-text-tertiary system-xs-medium-uppercase'
export const overlaySeparatorClassName = 'my-1 h-px bg-divider-subtle'
export const overlayPopupBaseClassName = 'max-h-(--available-height) overflow-y-auto overflow-x-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur py-1 text-sm text-text-secondary shadow-lg outline-hidden focus:outline-hidden focus-visible:outline-hidden backdrop-blur-[5px]'
export const overlayPopupAnimationClassName = 'origin-(--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 motion-reduce:transition-none'

View File

@ -5,7 +5,7 @@ import type { Placement } from '@/app/components/base/ui/placement'
import { Select as BaseSelect } from '@base-ui/react/select'
import { cn } from '@langgenius/dify-ui/cn'
import {
overlayGroupLabelClassName,
overlayLabelClassName,
overlaySeparatorClassName,
} from '@/app/components/base/ui/overlay-shared'
import { parsePlacement } from '@/app/components/base/ui/placement'
@ -17,18 +17,18 @@ export const SelectGroup = BaseSelect.Group
const selectSizeClassName: Record<string, string> = {
small: 'h-6 gap-px rounded-md px-2 py-1 system-xs-regular',
regular: 'h-8 gap-0.5 rounded-lg px-3 py-2 system-sm-regular',
medium: 'h-8 gap-0.5 rounded-lg px-3 py-2 system-sm-regular',
large: 'h-9 gap-0.5 rounded-[10px] px-4 py-2 system-md-regular',
}
type SelectTriggerProps = BaseSelect.Trigger.Props & {
size?: 'small' | 'regular' | 'large'
size?: 'small' | 'medium' | 'large'
}
export function SelectTrigger({
className,
children,
size = 'regular',
size = 'medium',
...props
}: SelectTriggerProps) {
return (
@ -55,13 +55,13 @@ export function SelectTrigger({
)
}
export function SelectGroupLabel({
export function SelectLabel({
className,
...props
}: BaseSelect.GroupLabel.Props) {
return (
<BaseSelect.GroupLabel
className={cn(overlayGroupLabelClassName, className)}
className={cn(overlayLabelClassName, className)}
{...props}
/>
)

View File

@ -54,7 +54,7 @@ const Operations = ({
{showDelete && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem destructive onClick={handleDelete}>
<DropdownMenuItem variant="destructive" onClick={handleDelete}>
<span aria-hidden className="i-ri-delete-bin-line size-4" />
{t('operation.delete', { ns: 'common' })}
</DropdownMenuItem>

View File

@ -49,14 +49,13 @@ const InputCombined: FC<Props> = ({
readOnly={readOnly}
onValueChange={value => onChange(value ?? 0)}
>
<NumberFieldGroup size="regular">
<NumberFieldGroup>
<NumberFieldInput
size="regular"
className={cn(className, 'rounded-l-md')}
/>
<NumberFieldControls className="overflow-hidden">
<NumberFieldIncrement size="regular" className="pt-0 pb-0" />
<NumberFieldDecrement size="regular" className="pt-0 pb-0" />
<NumberFieldIncrement className="pt-0 pb-0" />
<NumberFieldDecrement className="pt-0 pb-0" />
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>

View File

@ -33,7 +33,7 @@ const KeyWordNumber = ({
return (
<div className="flex items-center gap-x-1">
<div className="flex grow items-center gap-x-0.5">
<div className="truncate text-text-secondary system-xs-medium">
<div className="truncate system-xs-medium text-text-secondary">
{t('form.numberOfKeywords', { ns: 'datasetSettings' })}
</div>
<Tooltip
@ -57,11 +57,11 @@ const KeyWordNumber = ({
value={keywordNumber}
onValueChange={handleInputChange}
>
<NumberFieldGroup size="regular">
<NumberFieldInput size="regular" />
<NumberFieldGroup>
<NumberFieldInput />
<NumberFieldControls>
<NumberFieldIncrement size="regular" />
<NumberFieldDecrement size="regular" />
<NumberFieldIncrement />
<NumberFieldDecrement />
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>

View File

@ -71,7 +71,7 @@ vi.mock('@/app/components/base/ui/select', async (importOriginal) => {
<div data-testid="workplace-selector-content">{children}</div>
),
SelectGroup: ({ children }: { children: ReactNode }) => <div>{children}</div>,
SelectGroupLabel: ({ children }: { children: ReactNode }) => <div>{children}</div>,
SelectLabel: ({ children }: { children: ReactNode }) => <div>{children}</div>,
SelectItem: ({
children,
value,

View File

@ -4,9 +4,9 @@ import {
Select,
SelectContent,
SelectGroup,
SelectGroupLabel,
SelectItem,
SelectItemText,
SelectLabel,
SelectTrigger,
} from '@/app/components/base/ui/select'
import { toast } from '@/app/components/base/ui/toast'
@ -57,9 +57,9 @@ const WorkplaceSelector = () => {
</SelectTrigger>
<SelectContent popupClassName="w-[280px]">
<SelectGroup>
<SelectGroupLabel>
<SelectLabel>
{t('userProfile.workspace', { ns: 'common' })}
</SelectGroupLabel>
</SelectLabel>
{workspaces.map(workspace => (
<SelectItem key={workspace.id} value={workspace.id} className="gap-2 py-1 pr-2 pl-3">
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-components-icon-bg-blue-solid text-[13px]">

View File

@ -73,7 +73,7 @@ const OperationDropdown: FC<Props> = ({
{(source === PluginSource.marketplace || source === PluginSource.github) && enable_marketplace && (
<DropdownMenuSeparator />
)}
<DropdownMenuItem destructive onClick={onRemove}>
<DropdownMenuItem variant="destructive" onClick={onRemove}>
{t('detailPanel.operation.remove', { ns: 'plugin' })}
</DropdownMenuItem>
</DropdownMenuContent>

View File

@ -48,7 +48,7 @@ const EdgeContextmenu = () => {
popupClassName="rounded-lg"
>
<ContextMenuItem
destructive
variant="destructive"
className="justify-between gap-4 px-3"
onClick={() => handleEdgeDeleteById(edgeMenu.edgeId)}
>

View File

@ -159,11 +159,11 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
max={def.max}
onValueChange={nextValue => onChange(nextValue ?? defaultValue)}
>
<NumberFieldGroup size="regular">
<NumberFieldInput size="regular" className="w-12" />
<NumberFieldGroup>
<NumberFieldInput className="w-12" />
<NumberFieldControls>
<NumberFieldIncrement size="regular" />
<NumberFieldDecrement size="regular" />
<NumberFieldIncrement />
<NumberFieldDecrement />
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>

View File

@ -72,11 +72,11 @@ const TopKAndScoreThreshold = ({
value={topK}
onValueChange={value => handleTopKChange(value ?? 0)}
>
<NumberFieldGroup size="regular">
<NumberFieldInput size="regular" />
<NumberFieldGroup>
<NumberFieldInput />
<NumberFieldControls>
<NumberFieldIncrement size="regular" />
<NumberFieldDecrement size="regular" />
<NumberFieldIncrement />
<NumberFieldDecrement />
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
@ -107,11 +107,11 @@ const TopKAndScoreThreshold = ({
value={scoreThreshold ?? null}
onValueChange={value => handleScoreThresholdChange(value ?? 0)}
>
<NumberFieldGroup size="regular">
<NumberFieldInput size="regular" />
<NumberFieldGroup>
<NumberFieldInput />
<NumberFieldControls>
<NumberFieldIncrement size="regular" />
<NumberFieldDecrement size="regular" />
<NumberFieldIncrement />
<NumberFieldDecrement />
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>

View File

@ -78,7 +78,7 @@ const Panel: FC<NodePanelProps<WebhookTriggerNodeType>> = ({
return (
<div className="mt-2">
<div className="space-y-4 px-4 pb-3 pt-2">
<div className="space-y-4 px-4 pt-2 pb-3">
{/* Webhook URL Section */}
<Field title={t(`${i18nPrefix}.webhookUrl`, { ns: 'workflow' })}>
<div className="space-y-1">
@ -138,7 +138,7 @@ const Panel: FC<NodePanelProps<WebhookTriggerNodeType>> = ({
</div>
</Tooltip>
{isPrivateOrLocalAddress(inputs.webhook_debug_url) && (
<div className="mt-1 px-0 py-[2px] text-text-warning system-xs-regular">
<div className="mt-1 px-0 py-[2px] system-xs-regular text-text-warning">
{t(`${i18nPrefix}.debugUrlPrivateAddressWarning`, { ns: 'workflow' })}
</div>
)}
@ -197,7 +197,7 @@ const Panel: FC<NodePanelProps<WebhookTriggerNodeType>> = ({
<Field title={t(`${i18nPrefix}.responseConfiguration`, { ns: 'workflow' })}>
<div className="space-y-3">
<div className="flex items-center justify-between">
<label className="text-text-tertiary system-sm-medium">
<label className="system-sm-medium text-text-tertiary">
{t(`${i18nPrefix}.statusCode`, { ns: 'workflow' })}
</label>
<NumberField
@ -212,20 +212,19 @@ const Panel: FC<NodePanelProps<WebhookTriggerNodeType>> = ({
handleStatusCodeChange(normalizeStatusCode(value ?? DEFAULT_STATUS_CODE))
}}
>
<NumberFieldGroup size="regular">
<NumberFieldGroup>
<NumberFieldInput
size="regular"
className="h-8"
/>
<NumberFieldControls>
<NumberFieldIncrement size="regular" />
<NumberFieldDecrement size="regular" />
<NumberFieldIncrement />
<NumberFieldDecrement />
</NumberFieldControls>
</NumberFieldGroup>
</NumberField>
</div>
<div>
<label className="mb-2 block text-text-tertiary system-sm-medium">
<label className="mb-2 block system-sm-medium text-text-tertiary">
{t(`${i18nPrefix}.responseBody`, { ns: 'workflow' })}
</label>
<ParagraphInput

View File

@ -12,8 +12,8 @@ import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuGroupLabel,
ContextMenuItem,
ContextMenuLabel,
ContextMenuSeparator,
} from '@/app/components/base/ui/context-menu'
import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow'
@ -413,9 +413,9 @@ const SelectionContextmenu = () => {
{menuSections.map((section, sectionIndex) => (
<ContextMenuGroup key={section.titleKey}>
{sectionIndex > 0 && <ContextMenuSeparator />}
<ContextMenuGroupLabel>
<ContextMenuLabel>
{t(section.titleKey, { defaultValue: section.titleKey, ns: 'workflow' })}
</ContextMenuGroupLabel>
</ContextMenuLabel>
{section.items.map((item) => {
return (
<ContextMenuItem

View File

@ -3631,9 +3631,6 @@
"app/components/datasets/settings/index-method/keyword-number.tsx": {
"no-restricted-imports": {
"count": 1
},
"tailwindcss/enforce-consistent-class-order": {
"count": 1
}
},
"app/components/datasets/settings/permission-selector/index.tsx": {
@ -7251,9 +7248,6 @@
"app/components/workflow/nodes/trigger-webhook/panel.tsx": {
"no-restricted-imports": {
"count": 2
},
"tailwindcss/enforce-consistent-class-order": {
"count": 4
}
},
"app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx": {