mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-04-23 06:30:00 +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',
|
||||
);
|
||||
|
||||
interface TreeDataItem {
|
||||
export interface TreeDataItem {
|
||||
id: string;
|
||||
name: string;
|
||||
icon?: any;
|
||||
@ -25,251 +25,6 @@ interface TreeDataItem {
|
||||
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> & {
|
||||
data: TreeDataItem[] | TreeDataItem;
|
||||
initialSelectedItemId?: string;
|
||||
@ -355,4 +110,249 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeProps>(
|
||||
);
|
||||
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 };
|
||||
|
Loading…
x
Reference in New Issue
Block a user