refactor(web): quality closure pass on base UI primitives (#35333)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
yyh
2026-04-17 07:56:11 +08:00
committed by GitHub
parent 6ca066983d
commit b565a51ed9
11 changed files with 147 additions and 84 deletions

View File

@@ -15,7 +15,7 @@
width: 4px;
height: 12px;
transform: translateX(-50%);
background: linear-gradient(to bottom, var(--scroll-area-edge-hint-bg, var(--color-components-panel-bg)), transparent);
background: linear-gradient(to bottom, var(--color-components-panel-bg), transparent);
}
[data-dify-scrollbar][data-orientation='vertical']::after {
@@ -24,7 +24,7 @@
width: 4px;
height: 12px;
transform: translateX(-50%);
background: linear-gradient(to top, var(--scroll-area-edge-hint-bg, var(--color-components-panel-bg)), transparent);
background: linear-gradient(to top, var(--color-components-panel-bg), transparent);
}
[data-dify-scrollbar][data-orientation='horizontal']::before {
@@ -33,7 +33,7 @@
width: 12px;
height: 4px;
transform: translateY(-50%);
background: linear-gradient(to right, var(--scroll-area-edge-hint-bg, var(--color-components-panel-bg)), transparent);
background: linear-gradient(to right, var(--color-components-panel-bg), transparent);
}
[data-dify-scrollbar][data-orientation='horizontal']::after {
@@ -42,7 +42,7 @@
width: 12px;
height: 4px;
transform: translateY(-50%);
background: linear-gradient(to left, var(--scroll-area-edge-hint-bg, var(--color-components-panel-bg)), transparent);
background: linear-gradient(to left, var(--color-components-panel-bg), transparent);
}
[data-dify-scrollbar][data-orientation='vertical']:not([data-overflow-y-start])::before {
@@ -61,12 +61,6 @@
opacity: 1;
}
[data-dify-scrollbar][data-hovering] > [data-orientation],
[data-dify-scrollbar][data-scrolling] > [data-orientation],
[data-dify-scrollbar] > [data-orientation]:active {
background-color: var(--scroll-area-thumb-bg-active, var(--color-state-base-handle-hover));
}
@media (prefers-reduced-motion: reduce) {
[data-dify-scrollbar]::before,
[data-dify-scrollbar]::after {

View File

@@ -1,3 +1,4 @@
@import '../themes/light.css' layer(base);
@import '../themes/dark.css' layer(base);
@import './utilities.css';
@import './components.css';

View File

@@ -1,12 +1,11 @@
import type { CSSProperties } from 'react'
import { noop } from 'es-toolkit/function'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { Slider } from '@/app/components/base/ui/slider'
const weightedScoreSliderStyle: CSSProperties & Record<'--slider-track' | '--slider-range', string> = {
'--slider-track': 'var(--color-util-colors-teal-teal-500)',
'--slider-range': 'var(--color-util-colors-blue-light-blue-light-500)',
const weightedScoreSliderSlotClassNames = {
track: 'bg-util-colors-teal-teal-500',
indicator: 'bg-util-colors-blue-light-blue-light-500',
}
const formatNumber = (value: number) => {
@@ -37,7 +36,7 @@ const WeightedScore = ({
return (
<div>
<div className="space-x-3 rounded-lg border border-components-panel-border px-3 pt-5 pb-2">
<div className="grow" style={weightedScoreSliderStyle}>
<div className="grow">
<Slider
className="grow"
max={1.0}
@@ -47,6 +46,7 @@ const WeightedScore = ({
onValueChange={v => !readonly && onChange({ value: [v, (10 - v * 10) / 10] })}
disabled={readonly}
aria-label={t('weightedScore.semantic', { ns: 'dataset' })}
slotClassNames={weightedScoreSliderSlotClassNames}
/>
</div>
<div className="mt-3 flex justify-between">

View File

@@ -14,16 +14,14 @@ export const AlertDialogDescription = BaseAlertDialog.Description
type AlertDialogContentProps = {
children: ReactNode
className?: string
overlayClassName?: string
popupProps?: Omit<BaseAlertDialog.Popup.Props, 'children' | 'className'>
backdropClassName?: string
backdropProps?: Omit<BaseAlertDialog.Backdrop.Props, 'className'>
}
export function AlertDialogContent({
children,
className,
overlayClassName,
popupProps,
backdropClassName,
backdropProps,
}: AlertDialogContentProps) {
return (
@@ -33,11 +31,10 @@ export function AlertDialogContent({
className={cn(
'fixed inset-0 z-1002 bg-background-overlay',
'transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none',
overlayClassName,
backdropClassName,
)}
/>
<BaseAlertDialog.Popup
{...popupProps}
className={cn(
'fixed top-1/2 left-1/2 z-1002 max-h-[calc(100vh-2rem)] w-[480px] max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg',
'transition-[transform,scale,opacity] duration-150 data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0 motion-reduce:transition-none',

View File

@@ -42,14 +42,14 @@ export function DialogCloseButton({
type DialogContentProps = {
children: ReactNode
className?: string
overlayClassName?: string
backdropProps?: BaseDialog.Backdrop.Props
backdropClassName?: string
backdropProps?: Omit<BaseDialog.Backdrop.Props, 'className'>
}
export function DialogContent({
children,
className,
overlayClassName,
backdropClassName,
backdropProps,
}: DialogContentProps) {
return (
@@ -59,8 +59,7 @@ export function DialogContent({
className={cn(
'fixed inset-0 z-1002 bg-background-overlay',
'transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none',
overlayClassName,
backdropProps?.className,
backdropClassName,
)}
/>
<BaseDialog.Popup

View File

@@ -3,7 +3,6 @@
import type { ReactNode } from 'react'
import { ScrollArea as BaseScrollArea } from '@base-ui/react/scroll-area'
import { cn } from '@langgenius/dify-ui/cn'
import './scroll-area.css'
export const ScrollAreaRoot = BaseScrollArea.Root
type ScrollAreaRootProps = BaseScrollArea.Root.Props
@@ -25,7 +24,7 @@ type ScrollAreaProps = Omit<ScrollAreaRootProps, 'children'> & {
}
const scrollAreaScrollbarClassName = cn(
'flex touch-none overflow-clip p-1 opacity-100 transition-opacity select-none motion-reduce:transition-none',
'group/scrollbar flex touch-none overflow-clip p-1 opacity-100 transition-opacity select-none motion-reduce:transition-none',
'pointer-events-none data-hovering:pointer-events-auto',
'data-scrolling:pointer-events-auto',
'data-[orientation=vertical]:absolute data-[orientation=vertical]:inset-y-0 data-[orientation=vertical]:w-3 data-[orientation=vertical]:justify-center',
@@ -36,6 +35,9 @@ const scrollAreaThumbClassName = cn(
'shrink-0 rounded-sm bg-state-base-handle transition-[background-color] motion-reduce:transition-none',
'data-[orientation=vertical]:w-1',
'data-[orientation=horizontal]:h-1',
'group-data-hovering/scrollbar:bg-state-base-handle-hover',
'group-data-scrolling/scrollbar:bg-state-base-handle-hover',
'active:bg-state-base-handle-hover',
)
const scrollAreaViewportClassName = cn(

View File

@@ -1,9 +1,11 @@
'use client'
import type { VariantProps } from 'class-variance-authority'
import type { ReactNode } from 'react'
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 { cva } from 'class-variance-authority'
import {
overlayLabelClassName,
overlaySeparatorClassName,
@@ -15,34 +17,43 @@ export const SelectValue = BaseSelect.Value
/** @public */
export const SelectGroup = BaseSelect.Group
const selectSizeClassName: Record<string, string> = {
small: 'h-6 gap-px rounded-md px-2 py-1 system-xs-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',
}
const selectTriggerVariants = cva(
[
'group flex w-full items-center border-0 bg-components-input-bg-normal text-left text-components-input-text-filled outline-hidden',
'hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt',
'data-placeholder:text-components-input-text-placeholder',
'data-readonly:cursor-default data-readonly:bg-transparent data-readonly:hover:bg-transparent',
'data-disabled:cursor-not-allowed data-disabled:bg-components-input-bg-disabled data-disabled:text-components-input-text-filled-disabled data-disabled:hover:bg-components-input-bg-disabled',
'data-disabled:data-placeholder:text-components-input-text-disabled',
],
{
variants: {
size: {
small: 'h-6 gap-px rounded-md px-2 py-1 system-xs-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',
},
},
defaultVariants: {
size: 'medium',
},
},
)
type SelectTriggerProps = BaseSelect.Trigger.Props & {
size?: 'small' | 'medium' | 'large'
}
type SelectTriggerProps
= Omit<BaseSelect.Trigger.Props, 'className'>
& VariantProps<typeof selectTriggerVariants>
& { className?: string }
export function SelectTrigger({
className,
children,
size = 'medium',
size,
...props
}: SelectTriggerProps) {
return (
<BaseSelect.Trigger
className={cn(
'group flex w-full items-center border-0 bg-components-input-bg-normal text-left text-components-input-text-filled outline-hidden',
'hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt',
'data-placeholder:text-components-input-text-placeholder',
selectSizeClassName[size],
'data-readonly:cursor-default data-readonly:bg-transparent data-readonly:hover:bg-transparent',
'data-disabled:cursor-not-allowed data-disabled:bg-components-input-bg-disabled data-disabled:text-components-input-text-filled-disabled data-disabled:hover:bg-components-input-bg-disabled',
'data-disabled:data-placeholder:text-components-input-text-disabled',
className,
)}
className={cn(selectTriggerVariants({ size, className }))}
{...props}
>
<span className="min-w-0 grow truncate">

View File

@@ -2,16 +2,98 @@
import { Slider as BaseSlider } from '@base-ui/react/slider'
import { cn } from '@langgenius/dify-ui/cn'
import * as React from 'react'
/** @public */
export const SliderRoot = BaseSlider.Root
type SliderRootProps = BaseSlider.Root.Props<number>
const sliderControlClassName = cn(
'relative flex h-5 w-full touch-none items-center select-none',
'data-disabled:cursor-not-allowed',
)
type SliderControlProps = BaseSlider.Control.Props
/** @public */
export function SliderControl({ className, ...props }: SliderControlProps) {
return (
<BaseSlider.Control
className={cn(sliderControlClassName, className)}
{...props}
/>
)
}
const sliderTrackClassName = cn(
'relative h-1 w-full overflow-hidden rounded-full',
'bg-components-slider-track',
)
type SliderTrackProps = BaseSlider.Track.Props
/** @public */
export function SliderTrack({ className, ...props }: SliderTrackProps) {
return (
<BaseSlider.Track
className={cn(sliderTrackClassName, className)}
{...props}
/>
)
}
const sliderIndicatorClassName = cn(
'h-full rounded-full',
'bg-components-slider-range',
)
type SliderIndicatorProps = BaseSlider.Indicator.Props
/** @public */
export function SliderIndicator({ className, ...props }: SliderIndicatorProps) {
return (
<BaseSlider.Indicator
className={cn(sliderIndicatorClassName, className)}
{...props}
/>
)
}
const sliderThumbClassName = cn(
'block h-5 w-2 shrink-0 rounded-[3px] border-[0.5px]',
'border-components-slider-knob-border bg-components-slider-knob shadow-sm',
'transition-[background-color,border-color,box-shadow,opacity] motion-reduce:transition-none',
'hover:bg-components-slider-knob-hover',
'focus-visible:ring-2 focus-visible:ring-components-slider-knob-border-hover focus-visible:ring-offset-0 focus-visible:outline-hidden',
'active:shadow-md',
'group-data-disabled/slider:border-components-slider-knob-border group-data-disabled/slider:bg-components-slider-knob-disabled group-data-disabled/slider:shadow-none',
)
type SliderThumbProps = BaseSlider.Thumb.Props
/** @public */
export function SliderThumb({ className, ...props }: SliderThumbProps) {
return (
<BaseSlider.Thumb
className={cn(sliderThumbClassName, className)}
{...props}
/>
)
}
type SliderSlotClassNames = {
control?: string
track?: string
indicator?: string
thumb?: string
}
type SliderBaseProps = Pick<
SliderRootProps,
'onValueChange' | 'min' | 'max' | 'step' | 'disabled' | 'name'
> & Pick<SliderThumbProps, 'aria-label' | 'aria-labelledby'> & {
className?: string
slotClassNames?: SliderSlotClassNames
}
type ControlledSliderProps = SliderBaseProps & {
@@ -27,30 +109,6 @@ type UncontrolledSliderProps = SliderBaseProps & {
type SliderProps = ControlledSliderProps | UncontrolledSliderProps
const sliderRootClassName = 'group/slider relative inline-flex w-full data-disabled:opacity-30'
const sliderControlClassName = cn(
'relative flex h-5 w-full touch-none items-center select-none',
'data-disabled:cursor-not-allowed',
)
const sliderTrackClassName = cn(
'relative h-1 w-full overflow-hidden rounded-full',
'bg-(--slider-track,var(--color-components-slider-track))',
)
const sliderIndicatorClassName = cn(
'h-full rounded-full',
'bg-(--slider-range,var(--color-components-slider-range))',
)
const sliderThumbClassName = cn(
'block h-5 w-2 shrink-0 rounded-[3px] border-[0.5px]',
'border-(--slider-knob-border,var(--color-components-slider-knob-border))',
'bg-(--slider-knob,var(--color-components-slider-knob)) shadow-sm',
'transition-[background-color,border-color,box-shadow,opacity] motion-reduce:transition-none',
'hover:bg-(--slider-knob-hover,var(--color-components-slider-knob-hover))',
'focus-visible:ring-2 focus-visible:ring-components-slider-knob-border-hover focus-visible:ring-offset-0 focus-visible:outline-hidden',
'active:shadow-md',
'group-data-disabled/slider:bg-(--slider-knob-disabled,var(--color-components-slider-knob-disabled))',
'group-data-disabled/slider:border-(--slider-knob-border,var(--color-components-slider-knob-border))',
'group-data-disabled/slider:shadow-none',
)
const getSafeValue = (value: number | undefined, min: number) => {
if (value === undefined)
@@ -69,11 +127,12 @@ export function Slider({
disabled = false,
name,
className,
slotClassNames,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledby,
}: SliderProps) {
return (
<BaseSlider.Root
<SliderRoot
value={getSafeValue(value, min)}
defaultValue={getSafeValue(defaultValue, min)}
onValueChange={onValueChange}
@@ -85,16 +144,16 @@ export function Slider({
thumbAlignment="edge-client-only"
className={cn(sliderRootClassName, className)}
>
<BaseSlider.Control className={sliderControlClassName}>
<BaseSlider.Track className={sliderTrackClassName}>
<BaseSlider.Indicator className={sliderIndicatorClassName} />
</BaseSlider.Track>
<BaseSlider.Thumb
<SliderControl className={slotClassNames?.control}>
<SliderTrack className={slotClassNames?.track}>
<SliderIndicator className={slotClassNames?.indicator} />
</SliderTrack>
<SliderThumb
aria-label={ariaLabel}
aria-labelledby={ariaLabelledby}
className={sliderThumbClassName}
className={slotClassNames?.thumb}
/>
</BaseSlider.Control>
</BaseSlider.Root>
</SliderControl>
</SliderRoot>
)
}

View File

@@ -28,7 +28,7 @@ type PricingProps = {
}
const pricingScrollAreaClassNames = {
root: 'relative h-full w-full overflow-hidden [--scroll-area-edge-hint-bg:var(--color-saas-background)]',
root: 'relative h-full w-full overflow-hidden',
viewport: 'overscroll-contain',
content: 'min-h-full min-w-[1200px]',
verticalScrollbar: 'data-[orientation=vertical]:my-2 data-[orientation=vertical]:me-1',

View File

@@ -27,7 +27,7 @@ const MenuDialog = ({
}}
>
<DialogContent
overlayClassName="bg-transparent"
backdropClassName="bg-transparent"
className={cn(
'top-0 left-0 h-full max-h-none w-full max-w-none translate-x-0 translate-y-0 overflow-hidden rounded-none border-none bg-background-sidenav-bg p-0 shadow-none backdrop-blur-md',
className,

View File

@@ -24,7 +24,7 @@ const WorkflowOnboardingModal: FC<WorkflowOnboardingModalProps> = ({
<Dialog open={isShow} onOpenChange={onClose} disablePointerDismissal>
<DialogContent
className="w-[618px] max-w-[618px] rounded-2xl border border-effects-highlight bg-background-default-subtle shadow-lg"
overlayClassName="bg-workflow-canvas-canvas-overlay"
backdropClassName="bg-workflow-canvas-canvas-overlay"
>
<DialogCloseButton />