Fix: Remove antd from dataset-page (#8830)

### What problem does this PR solve?

remove antd from dataset-page
[#3221](https://github.com/infiniflow/ragflow/issues/3221)
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-07-15 16:12:50 +08:00
committed by GitHub
parent 148fde8b1b
commit ed8d7291ff
27 changed files with 916 additions and 460 deletions

View File

@ -22,6 +22,7 @@ const buttonVariants = cva(
tertiary:
'bg-colors-background-sentiment-solid-primary text-colors-text-persist-light hover:bg-colors-background-sentiment-solid-primary/80',
icon: 'bg-colors-background-inverse-standard text-foreground hover:bg-colors-background-inverse-standard/80',
dashed: 'border border-dashed border-input hover:bg-accent',
},
size: {
default: 'h-8 px-2.5 py-1.5 ',
@ -49,7 +50,10 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
className={cn(
'bg-background-card',
buttonVariants({ variant, size, className }),
)}
ref={ref}
{...props}
/>

View File

@ -0,0 +1,64 @@
// src/components/ui/divider.tsx
import React from 'react';
type Direction = 'horizontal' | 'vertical';
type DividerType = 'horizontal' | 'vertical' | 'text';
interface DividerProps {
direction?: Direction;
type?: DividerType;
text?: React.ReactNode;
color?: string;
margin?: string;
className?: string;
}
const Divider: React.FC<DividerProps> = ({
direction = 'horizontal',
type = 'horizontal',
text,
color = 'border-muted-foreground/50',
margin = 'my-4',
className = '',
}) => {
const baseClasses = 'flex items-center';
const directionClass = direction === 'horizontal' ? 'flex-row' : 'flex-col';
const colorClass = color.startsWith('border-') ? color : `border-${color}`;
const marginClass = margin || '';
const textClass = 'px-4 text-sm text-muted-foreground';
// Default vertical style
if (direction === 'vertical') {
return (
<div
className={`h-full ${colorClass} border-l ${marginClass} ${className}`}
>
{type === 'text' && (
<div className="transform -rotate-90 px-2 whitespace-nowrap">
{text}
</div>
)}
</div>
);
}
// Horizontal with text
if (type === 'text') {
return (
<div
className={`${baseClasses} ${directionClass} ${marginClass} ${className}`}
>
<div className={`flex-1 ${colorClass} border-t`}></div>
<div className={textClass}>{text}</div>
<div className={`flex-1 ${colorClass} border-t`}></div>
</div>
);
}
// Default horizontal
return (
<div className={`${colorClass} border-t ${marginClass} ${className}`} />
);
};
export default Divider;

View File

@ -18,7 +18,7 @@ const HoverCardContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]',
'z-50 w-fit max-w-96 overflow-auto break-words whitespace-pre-wrap rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]',
className,
)}
{...props}

View File

@ -0,0 +1,199 @@
// src/components/ui/modal.tsx
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { Loader, X } from 'lucide-react';
import { FC, ReactNode, useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
interface ModalProps {
open: boolean;
onOpenChange?: (open: boolean) => void;
title?: ReactNode;
children: ReactNode;
footer?: ReactNode;
className?: string;
size?: 'small' | 'default' | 'large';
closable?: boolean;
closeIcon?: ReactNode;
maskClosable?: boolean;
destroyOnClose?: boolean;
full?: boolean;
confirmLoading?: boolean;
cancelText?: ReactNode | string;
okText?: ReactNode | string;
onOk?: () => void;
onCancel?: () => void;
}
export const Modal: FC<ModalProps> = ({
open,
onOpenChange,
title,
children,
footer,
className = '',
size = 'default',
closable = true,
closeIcon = <X className="w-4 h-4" />,
maskClosable = true,
destroyOnClose = false,
full = false,
onOk,
onCancel,
confirmLoading,
cancelText,
okText,
}) => {
const sizeClasses = {
small: 'max-w-md',
default: 'max-w-2xl',
large: 'max-w-4xl',
};
const { t } = useTranslation();
// Handle ESC key close
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape' && maskClosable) {
onOpenChange?.(false);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [maskClosable, onOpenChange]);
const handleCancel = useCallback(() => {
onOpenChange?.(false);
onCancel?.();
}, [onOpenChange, onCancel]);
const handleOk = useCallback(() => {
onOpenChange?.(true);
onOk?.();
}, [onOpenChange, onOk]);
const handleChange = (open: boolean) => {
onOpenChange?.(open);
if (open) {
handleOk();
}
if (!open) {
handleCancel();
}
};
const footEl = useMemo(() => {
let footerTemp;
if (footer) {
footerTemp = footer;
} else {
footerTemp = (
<div className="flex justify-end gap-2">
<button
type="button"
onClick={() => handleCancel()}
className="px-2 py-1 border border-input rounded-md hover:bg-muted"
>
{cancelText ?? t('modal.cancelText')}
</button>
<button
type="button"
disabled={confirmLoading}
onClick={() => handleOk()}
className="px-2 py-1 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
>
{confirmLoading && (
<Loader className="inline-block mr-2 h-4 w-4 animate-spin" />
)}
{okText ?? t('modal.okText')}
</button>
</div>
);
return (
<div className="flex items-center justify-end border-t border-border px-6 py-4">
{footerTemp}
</div>
);
}
}, [footer, cancelText, t, confirmLoading, okText, handleCancel, handleOk]);
return (
<DialogPrimitive.Root open={open} onOpenChange={handleChange}>
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay
className="fixed inset-0 z-50 bg-colors-background-neutral-weak/50 backdrop-blur-sm flex items-center justify-center p-4"
onClick={() => maskClosable && onOpenChange?.(false)}
>
<DialogPrimitive.Content
className={`relative w-[700px] ${full ? 'max-w-full' : sizeClasses[size]} ${className} bg-colors-background-neutral-standard rounded-lg shadow-lg transition-all`}
onClick={(e) => e.stopPropagation()}
>
{/* title */}
{title && (
<div className="flex items-center justify-between border-b border-border px-6 py-4">
<DialogPrimitive.Title className="text-lg font-medium text-foreground">
{title}
</DialogPrimitive.Title>
{closable && (
<DialogPrimitive.Close asChild>
<button
type="button"
className="flex h-7 w-7 items-center justify-center rounded-full hover:bg-muted"
>
{closeIcon}
</button>
</DialogPrimitive.Close>
)}
</div>
)}
{/* content */}
<div className="p-6 overflow-y-auto max-h-[80vh]">
{destroyOnClose && !open ? null : children}
</div>
{/* footer */}
{footEl}
</DialogPrimitive.Content>
</DialogPrimitive.Overlay>
</DialogPrimitive.Portal>
</DialogPrimitive.Root>
);
};
// example usage
/*
import { Modal } from '@/components/ui/modal';
function Demo() {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(true)}>open modal</button>
<Modal
open={open}
onOpenChange={setOpen}
title="title"
footer={
<div className="flex gap-2">
<button onClick={() => setOpen(false)} className="px-4 py-2 border rounded-md">
cancel
</button>
<button onClick={() => setOpen(false)} className="px-4 py-2 bg-primary text-white rounded-md">
ok
</button>
</div>
}
>
<div className="py-4">弹窗内容区域</div>
</Modal>
<Modal
title={'modal-title'}
onOk={handleOk}
confirmLoading={loading}
destroyOnClose
>
<div className="py-4">弹窗内容区域</div>
</Modal>
</div>
);
}
*/

View File

@ -0,0 +1,57 @@
// src/components/ui/space.tsx
import React from 'react';
type Direction = 'horizontal' | 'vertical';
type Size = 'small' | 'middle' | 'large';
interface SpaceProps {
direction?: Direction;
size?: Size;
align?: string;
justify?: string;
wrap?: boolean;
className?: string;
children: React.ReactNode;
}
const sizeClasses: Record<Size, string> = {
small: 'gap-2',
middle: 'gap-4',
large: 'gap-8',
};
const directionClasses: Record<Direction, string> = {
horizontal: 'flex-row',
vertical: 'flex-col',
};
const Space: React.FC<SpaceProps> = ({
direction = 'horizontal',
size = 'middle',
align,
justify,
wrap = false,
className = '',
children,
}) => {
const baseClasses = 'flex';
const directionClass = directionClasses[direction];
const sizeClass = sizeClasses[size];
const alignClass = align ? `items-${align}` : '';
const justifyClass = justify ? `justify-${justify}` : '';
const wrapClass = wrap ? 'flex-wrap' : '';
const classes = [
baseClasses,
directionClass,
sizeClass,
alignClass,
justifyClass,
wrapClass,
className,
].join(' ');
return <div className={classes}>{children}</div>;
};
export default Space;

View File

@ -1,51 +1,105 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
import {
ChangeEventHandler,
ComponentProps,
FocusEventHandler,
forwardRef,
TextareaHTMLAttributes,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
interface TextareaProps
extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'autoSize'> {
autoSize?: {
minRows?: number;
maxRows?: number;
};
}
const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, autoSize, ...props }, ref) => {
const textareaRef = useRef<HTMLTextAreaElement>(null);
const getLineHeight = (element: HTMLElement): number => {
const style = window.getComputedStyle(element);
return parseInt(style.lineHeight, 10) || 20;
};
const adjustHeight = useCallback(() => {
if (!textareaRef.current) return;
const lineHeight = getLineHeight(textareaRef.current);
const maxHeight = (autoSize?.maxRows || 3) * lineHeight;
textareaRef.current.style.height = 'auto';
const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentProps<'textarea'>
>(({ className, ...props }, ref) => {
return (
<textarea
className={cn(
'flex min-h-[80px] w-full rounded-md border border-input bg-colors-background-inverse-weak px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm overflow-hidden',
className,
)}
ref={ref}
{...props}
/>
);
});
requestAnimationFrame(() => {
if (!textareaRef.current) return;
const scrollHeight = textareaRef.current.scrollHeight;
textareaRef.current.style.height = `${Math.min(scrollHeight, maxHeight)}px`;
});
}, [autoSize]);
useEffect(() => {
if (autoSize) {
adjustHeight();
}
}, [textareaRef, autoSize, adjustHeight]);
useEffect(() => {
if (typeof ref === 'function') {
ref(textareaRef.current);
} else if (ref) {
ref.current = textareaRef.current;
}
}, [ref]);
return (
<textarea
className={cn(
'flex min-h-[80px] w-full rounded-md border border-input bg-colors-background-inverse-weak px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm overflow-hidden',
className,
)}
rows={autoSize?.minRows ?? props.rows ?? undefined}
style={{
maxHeight: autoSize?.maxRows
? `${autoSize.maxRows * 20}px`
: undefined,
overflow: autoSize ? 'auto' : undefined,
}}
ref={textareaRef}
{...props}
/>
);
},
);
Textarea.displayName = 'Textarea';
export { Textarea };
type Value = string | readonly string[] | number | undefined;
export const BlurTextarea = React.forwardRef<
export const BlurTextarea = forwardRef<
HTMLTextAreaElement,
React.ComponentProps<'textarea'> & {
ComponentProps<'textarea'> & {
value: Value;
onChange(value: Value): void;
}
>(({ value, onChange, ...props }, ref) => {
const [val, setVal] = React.useState<Value>();
const [val, setVal] = useState<Value>();
const handleChange: React.ChangeEventHandler<HTMLTextAreaElement> =
React.useCallback((e) => {
const handleChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
(e) => {
setVal(e.target.value);
}, []);
},
[],
);
const handleBlur: React.FocusEventHandler<HTMLTextAreaElement> =
React.useCallback(
(e) => {
onChange?.(e.target.value);
},
[onChange],
);
const handleBlur: FocusEventHandler<HTMLTextAreaElement> = useCallback(
(e) => {
onChange?.(e.target.value);
},
[onChange],
);
React.useEffect(() => {
useEffect(() => {
setVal(value);
}, [value]);