mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-04-22 14:10:01 +08:00
### What problem does this PR solve? Feat: Alter TreeView component #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
53ac27c3ff
commit
8daec9a4c5
@ -14,7 +14,7 @@ const selectedTreeVariants = cva(
|
|||||||
'before:opacity-100 before:bg-accent/70 text-accent-foreground',
|
'before:opacity-100 before:bg-accent/70 text-accent-foreground',
|
||||||
);
|
);
|
||||||
|
|
||||||
interface TreeDataItem {
|
export interface TreeDataItem {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
icon?: any;
|
icon?: any;
|
||||||
@ -25,251 +25,6 @@ interface TreeDataItem {
|
|||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccordionTrigger = React.forwardRef<
|
|
||||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
|
||||||
>(({ className, children, ...props }, ref) => (
|
|
||||||
<AccordionPrimitive.Header>
|
|
||||||
<AccordionPrimitive.Trigger
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
'flex flex-1 w-full items-center py-2 transition-all first:[&[data-state=open]>svg]:rotate-90',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<ChevronRight className="h-4 w-4 shrink-0 transition-transform duration-200 text-accent-foreground/50 mr-1" />
|
|
||||||
{children}
|
|
||||||
</AccordionPrimitive.Trigger>
|
|
||||||
</AccordionPrimitive.Header>
|
|
||||||
));
|
|
||||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
|
||||||
|
|
||||||
const AccordionContent = React.forwardRef<
|
|
||||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
|
||||||
>(({ className, children, ...props }, ref) => (
|
|
||||||
<AccordionPrimitive.Content
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<div className="pb-1 pt-0">{children}</div>
|
|
||||||
</AccordionPrimitive.Content>
|
|
||||||
));
|
|
||||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
|
||||||
|
|
||||||
const TreeIcon = ({
|
|
||||||
item,
|
|
||||||
isOpen,
|
|
||||||
isSelected,
|
|
||||||
default: defaultIcon,
|
|
||||||
}: {
|
|
||||||
item: TreeDataItem;
|
|
||||||
isOpen?: boolean;
|
|
||||||
isSelected?: boolean;
|
|
||||||
default?: any;
|
|
||||||
}) => {
|
|
||||||
let Icon = defaultIcon;
|
|
||||||
if (isSelected && item.selectedIcon) {
|
|
||||||
Icon = item.selectedIcon;
|
|
||||||
} else if (isOpen && item.openIcon) {
|
|
||||||
Icon = item.openIcon;
|
|
||||||
} else if (item.icon) {
|
|
||||||
Icon = item.icon;
|
|
||||||
}
|
|
||||||
return Icon ? <Icon className="h-4 w-4 shrink-0 mr-2" /> : <></>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TreeActions = ({
|
|
||||||
children,
|
|
||||||
isSelected,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
isSelected: boolean;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
isSelected ? 'block' : 'hidden',
|
|
||||||
'absolute right-3 group-hover:block',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TreeNode = ({
|
|
||||||
item,
|
|
||||||
handleSelectChange,
|
|
||||||
expandedItemIds,
|
|
||||||
selectedItemId,
|
|
||||||
defaultNodeIcon,
|
|
||||||
defaultLeafIcon,
|
|
||||||
}: {
|
|
||||||
item: TreeDataItem;
|
|
||||||
handleSelectChange: (item: TreeDataItem | undefined) => void;
|
|
||||||
expandedItemIds: string[];
|
|
||||||
selectedItemId?: string;
|
|
||||||
defaultNodeIcon?: any;
|
|
||||||
defaultLeafIcon?: any;
|
|
||||||
}) => {
|
|
||||||
const [value, setValue] = React.useState(
|
|
||||||
expandedItemIds.includes(item.id) ? [item.id] : [],
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<AccordionPrimitive.Root
|
|
||||||
type="multiple"
|
|
||||||
value={value}
|
|
||||||
onValueChange={(s) => setValue(s)}
|
|
||||||
>
|
|
||||||
<AccordionPrimitive.Item value={item.id}>
|
|
||||||
<AccordionTrigger
|
|
||||||
className={cn(
|
|
||||||
treeVariants(),
|
|
||||||
selectedItemId === item.id && selectedTreeVariants(),
|
|
||||||
)}
|
|
||||||
onClick={() => {
|
|
||||||
handleSelectChange(item);
|
|
||||||
item.onClick?.();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TreeIcon
|
|
||||||
item={item}
|
|
||||||
isSelected={selectedItemId === item.id}
|
|
||||||
isOpen={value.includes(item.id)}
|
|
||||||
default={defaultNodeIcon}
|
|
||||||
/>
|
|
||||||
<span className="text-sm truncate">{item.name}</span>
|
|
||||||
<TreeActions isSelected={selectedItemId === item.id}>
|
|
||||||
{item.actions}
|
|
||||||
</TreeActions>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="ml-4 pl-1 border-l">
|
|
||||||
<TreeItem
|
|
||||||
data={item.children ? item.children : item}
|
|
||||||
selectedItemId={selectedItemId}
|
|
||||||
handleSelectChange={handleSelectChange}
|
|
||||||
expandedItemIds={expandedItemIds}
|
|
||||||
defaultLeafIcon={defaultLeafIcon}
|
|
||||||
defaultNodeIcon={defaultNodeIcon}
|
|
||||||
/>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionPrimitive.Item>
|
|
||||||
</AccordionPrimitive.Root>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type TreeItemProps = TreeProps & {
|
|
||||||
selectedItemId?: string;
|
|
||||||
handleSelectChange: (item: TreeDataItem | undefined) => void;
|
|
||||||
expandedItemIds: string[];
|
|
||||||
defaultNodeIcon?: any;
|
|
||||||
defaultLeafIcon?: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(
|
|
||||||
(
|
|
||||||
{
|
|
||||||
className,
|
|
||||||
data,
|
|
||||||
selectedItemId,
|
|
||||||
handleSelectChange,
|
|
||||||
expandedItemIds,
|
|
||||||
defaultNodeIcon,
|
|
||||||
defaultLeafIcon,
|
|
||||||
...props
|
|
||||||
},
|
|
||||||
ref,
|
|
||||||
) => {
|
|
||||||
if (!(data instanceof Array)) {
|
|
||||||
data = [data];
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div ref={ref} role="tree" className={className} {...props}>
|
|
||||||
<ul>
|
|
||||||
{data.map((item) => (
|
|
||||||
<li key={item.id}>
|
|
||||||
{item.children ? (
|
|
||||||
<TreeNode
|
|
||||||
item={item}
|
|
||||||
selectedItemId={selectedItemId}
|
|
||||||
expandedItemIds={expandedItemIds}
|
|
||||||
handleSelectChange={handleSelectChange}
|
|
||||||
defaultNodeIcon={defaultNodeIcon}
|
|
||||||
defaultLeafIcon={defaultLeafIcon}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<TreeLeaf
|
|
||||||
item={item}
|
|
||||||
selectedItemId={selectedItemId}
|
|
||||||
handleSelectChange={handleSelectChange}
|
|
||||||
defaultLeafIcon={defaultLeafIcon}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
TreeItem.displayName = 'TreeItem';
|
|
||||||
|
|
||||||
const TreeLeaf = React.forwardRef<
|
|
||||||
HTMLDivElement,
|
|
||||||
React.HTMLAttributes<HTMLDivElement> & {
|
|
||||||
item: TreeDataItem;
|
|
||||||
selectedItemId?: string;
|
|
||||||
handleSelectChange: (item: TreeDataItem | undefined) => void;
|
|
||||||
defaultLeafIcon?: any;
|
|
||||||
}
|
|
||||||
>(
|
|
||||||
(
|
|
||||||
{
|
|
||||||
className,
|
|
||||||
item,
|
|
||||||
selectedItemId,
|
|
||||||
handleSelectChange,
|
|
||||||
defaultLeafIcon,
|
|
||||||
...props
|
|
||||||
},
|
|
||||||
ref,
|
|
||||||
) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
'ml-5 flex text-left items-center py-2 cursor-pointer before:right-1',
|
|
||||||
treeVariants(),
|
|
||||||
className,
|
|
||||||
selectedItemId === item.id && selectedTreeVariants(),
|
|
||||||
)}
|
|
||||||
onClick={() => {
|
|
||||||
handleSelectChange(item);
|
|
||||||
item.onClick?.();
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<TreeIcon
|
|
||||||
item={item}
|
|
||||||
isSelected={selectedItemId === item.id}
|
|
||||||
default={defaultLeafIcon}
|
|
||||||
/>
|
|
||||||
<span className="flex-grow text-sm truncate">{item.name}</span>
|
|
||||||
<TreeActions isSelected={selectedItemId === item.id}>
|
|
||||||
{item.actions}
|
|
||||||
</TreeActions>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
TreeLeaf.displayName = 'TreeLeaf';
|
|
||||||
|
|
||||||
type TreeProps = React.HTMLAttributes<HTMLDivElement> & {
|
type TreeProps = React.HTMLAttributes<HTMLDivElement> & {
|
||||||
data: TreeDataItem[] | TreeDataItem;
|
data: TreeDataItem[] | TreeDataItem;
|
||||||
initialSelectedItemId?: string;
|
initialSelectedItemId?: string;
|
||||||
@ -355,4 +110,249 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeProps>(
|
|||||||
);
|
);
|
||||||
TreeView.displayName = 'TreeView';
|
TreeView.displayName = 'TreeView';
|
||||||
|
|
||||||
|
type TreeItemProps = TreeProps & {
|
||||||
|
selectedItemId?: string;
|
||||||
|
handleSelectChange: (item: TreeDataItem | undefined) => void;
|
||||||
|
expandedItemIds: string[];
|
||||||
|
defaultNodeIcon?: any;
|
||||||
|
defaultLeafIcon?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
className,
|
||||||
|
data,
|
||||||
|
selectedItemId,
|
||||||
|
handleSelectChange,
|
||||||
|
expandedItemIds,
|
||||||
|
defaultNodeIcon,
|
||||||
|
defaultLeafIcon,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
if (!(data instanceof Array)) {
|
||||||
|
data = [data];
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div ref={ref} role="tree" className={className} {...props}>
|
||||||
|
<ul>
|
||||||
|
{data.map((item) => (
|
||||||
|
<li key={item.id}>
|
||||||
|
{item.children ? (
|
||||||
|
<TreeNode
|
||||||
|
item={item}
|
||||||
|
selectedItemId={selectedItemId}
|
||||||
|
expandedItemIds={expandedItemIds}
|
||||||
|
handleSelectChange={handleSelectChange}
|
||||||
|
defaultNodeIcon={defaultNodeIcon}
|
||||||
|
defaultLeafIcon={defaultLeafIcon}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<TreeLeaf
|
||||||
|
item={item}
|
||||||
|
selectedItemId={selectedItemId}
|
||||||
|
handleSelectChange={handleSelectChange}
|
||||||
|
defaultLeafIcon={defaultLeafIcon}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
TreeItem.displayName = 'TreeItem';
|
||||||
|
|
||||||
|
const TreeNode = ({
|
||||||
|
item,
|
||||||
|
handleSelectChange,
|
||||||
|
expandedItemIds,
|
||||||
|
selectedItemId,
|
||||||
|
defaultNodeIcon,
|
||||||
|
defaultLeafIcon,
|
||||||
|
}: {
|
||||||
|
item: TreeDataItem;
|
||||||
|
handleSelectChange: (item: TreeDataItem | undefined) => void;
|
||||||
|
expandedItemIds: string[];
|
||||||
|
selectedItemId?: string;
|
||||||
|
defaultNodeIcon?: any;
|
||||||
|
defaultLeafIcon?: any;
|
||||||
|
}) => {
|
||||||
|
const [value, setValue] = React.useState(
|
||||||
|
expandedItemIds.includes(item.id) ? [item.id] : [],
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<AccordionPrimitive.Root
|
||||||
|
type="multiple"
|
||||||
|
value={value}
|
||||||
|
onValueChange={(s) => setValue(s)}
|
||||||
|
>
|
||||||
|
<AccordionPrimitive.Item value={item.id}>
|
||||||
|
<AccordionTrigger
|
||||||
|
className={cn(
|
||||||
|
treeVariants(),
|
||||||
|
selectedItemId === item.id && selectedTreeVariants(),
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
handleSelectChange(item);
|
||||||
|
item.onClick?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TreeIcon
|
||||||
|
item={item}
|
||||||
|
isSelected={selectedItemId === item.id}
|
||||||
|
isOpen={value.includes(item.id)}
|
||||||
|
default={defaultNodeIcon}
|
||||||
|
/>
|
||||||
|
<span className="text-sm truncate">{item.name}</span>
|
||||||
|
<TreeActions isSelected={selectedItemId === item.id}>
|
||||||
|
{item.actions}
|
||||||
|
</TreeActions>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="ml-4 pl-1 border-l">
|
||||||
|
<TreeItem
|
||||||
|
data={item.children ? item.children : item}
|
||||||
|
selectedItemId={selectedItemId}
|
||||||
|
handleSelectChange={handleSelectChange}
|
||||||
|
expandedItemIds={expandedItemIds}
|
||||||
|
defaultLeafIcon={defaultLeafIcon}
|
||||||
|
defaultNodeIcon={defaultNodeIcon}
|
||||||
|
/>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionPrimitive.Item>
|
||||||
|
</AccordionPrimitive.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TreeLeaf = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & {
|
||||||
|
item: TreeDataItem;
|
||||||
|
selectedItemId?: string;
|
||||||
|
handleSelectChange: (item: TreeDataItem | undefined) => void;
|
||||||
|
defaultLeafIcon?: any;
|
||||||
|
}
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
className,
|
||||||
|
item,
|
||||||
|
selectedItemId,
|
||||||
|
handleSelectChange,
|
||||||
|
defaultLeafIcon,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'ml-5 flex text-left items-center py-2 cursor-pointer before:right-1',
|
||||||
|
treeVariants(),
|
||||||
|
className,
|
||||||
|
selectedItemId === item.id && selectedTreeVariants(),
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
handleSelectChange(item);
|
||||||
|
item.onClick?.();
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<TreeIcon
|
||||||
|
item={item}
|
||||||
|
isSelected={selectedItemId === item.id}
|
||||||
|
default={defaultLeafIcon}
|
||||||
|
/>
|
||||||
|
<span className="flex-grow text-sm truncate">{item.name}</span>
|
||||||
|
<TreeActions isSelected={selectedItemId === item.id}>
|
||||||
|
{item.actions}
|
||||||
|
</TreeActions>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
TreeLeaf.displayName = 'TreeLeaf';
|
||||||
|
|
||||||
|
const AccordionTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Header>
|
||||||
|
<AccordionPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'flex flex-1 w-full items-center py-2 transition-all first:[&[data-state=open]>svg]:rotate-90',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronRight className="h-4 w-4 shrink-0 transition-transform duration-200 text-accent-foreground/50 mr-1" />
|
||||||
|
{children}
|
||||||
|
</AccordionPrimitive.Trigger>
|
||||||
|
</AccordionPrimitive.Header>
|
||||||
|
));
|
||||||
|
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
|
const AccordionContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className="pb-1 pt-0">{children}</div>
|
||||||
|
</AccordionPrimitive.Content>
|
||||||
|
));
|
||||||
|
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
const TreeIcon = ({
|
||||||
|
item,
|
||||||
|
isOpen,
|
||||||
|
isSelected,
|
||||||
|
default: defaultIcon,
|
||||||
|
}: {
|
||||||
|
item: TreeDataItem;
|
||||||
|
isOpen?: boolean;
|
||||||
|
isSelected?: boolean;
|
||||||
|
default?: any;
|
||||||
|
}) => {
|
||||||
|
let Icon = defaultIcon;
|
||||||
|
if (isSelected && item.selectedIcon) {
|
||||||
|
Icon = item.selectedIcon;
|
||||||
|
} else if (isOpen && item.openIcon) {
|
||||||
|
Icon = item.openIcon;
|
||||||
|
} else if (item.icon) {
|
||||||
|
Icon = item.icon;
|
||||||
|
}
|
||||||
|
return Icon ? <Icon className="h-4 w-4 shrink-0 mr-2" /> : <></>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TreeActions = ({
|
||||||
|
children,
|
||||||
|
isSelected,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
isSelected: boolean;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
isSelected ? 'block' : 'hidden',
|
||||||
|
'absolute right-3 group-hover:block',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export { TreeView, type TreeDataItem };
|
export { TreeView, type TreeDataItem };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user