Browse Source

Feat: Alter TreeView component #3221 (#6272)

### What problem does this PR solve?

Feat: Alter TreeView component #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
tags/v0.18.0
balibabu 7 months ago
parent
commit
8daec9a4c5
No account linked to committer's email address
1 changed files with 209 additions and 209 deletions
  1. 209
    209
      web/src/components/ui/tree-view.tsx

+ 209
- 209
web/src/components/ui/tree-view.tsx View File

@@ -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,84 +25,147 @@ 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;
type TreeProps = React.HTMLAttributes<HTMLDivElement> & {
data: TreeDataItem[] | TreeDataItem;
initialSelectedItemId?: string;
onSelectChange?: (item: TreeDataItem | undefined) => void;
expandAll?: boolean;
defaultNodeIcon?: any;
defaultLeafIcon?: any;
};

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',
const TreeView = React.forwardRef<HTMLDivElement, TreeProps>(
(
{
data,
initialSelectedItemId,
onSelectChange,
expandAll,
defaultLeafIcon,
defaultNodeIcon,
className,
)}
{...props}
>
<div className="pb-1 pt-0">{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
...props
},
ref,
) => {
const [selectedItemId, setSelectedItemId] = React.useState<
string | undefined
>(initialSelectedItemId);

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 handleSelectChange = React.useCallback(
(item: TreeDataItem | undefined) => {
setSelectedItemId(item?.id);
if (onSelectChange) {
onSelectChange(item);
}
},
[onSelectChange],
);

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 expandedItemIds = React.useMemo(() => {
if (!initialSelectedItemId) {
return [] as string[];
}

const ids: string[] = [];

function walkTreeItems(
items: TreeDataItem[] | TreeDataItem,
targetId: string,
) {
if (items instanceof Array) {
for (let i = 0; i < items.length; i++) {
ids.push(items[i]!.id);
if (walkTreeItems(items[i]!, targetId) && !expandAll) {
return true;
}
if (!expandAll) ids.pop();
}
} else if (!expandAll && items.id === targetId) {
return true;
} else if (items.children) {
return walkTreeItems(items.children, targetId);
}
}

walkTreeItems(data, initialSelectedItemId);
return ids;
}, [data, expandAll, initialSelectedItemId]);

return (
<div className={cn('overflow-hidden relative p-2', className)}>
<TreeItem
data={data}
ref={ref}
selectedItemId={selectedItemId}
handleSelectChange={handleSelectChange}
expandedItemIds={expandedItemIds}
defaultLeafIcon={defaultLeafIcon}
defaultNodeIcon={defaultNodeIcon}
{...props}
/>
</div>
);
},
);
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,
@@ -164,62 +227,6 @@ const TreeNode = ({
);
};

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> & {
@@ -270,89 +277,82 @@ const TreeLeaf = React.forwardRef<
);
TreeLeaf.displayName = 'TreeLeaf';

type TreeProps = React.HTMLAttributes<HTMLDivElement> & {
data: TreeDataItem[] | TreeDataItem;
initialSelectedItemId?: string;
onSelectChange?: (item: TreeDataItem | undefined) => void;
expandAll?: boolean;
defaultNodeIcon?: any;
defaultLeafIcon?: any;
};
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 TreeView = React.forwardRef<HTMLDivElement, TreeProps>(
(
{
data,
initialSelectedItemId,
onSelectChange,
expandAll,
defaultLeafIcon,
defaultNodeIcon,
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
},
ref,
) => {
const [selectedItemId, setSelectedItemId] = React.useState<
string | undefined
>(initialSelectedItemId);

const handleSelectChange = React.useCallback(
(item: TreeDataItem | undefined) => {
setSelectedItemId(item?.id);
if (onSelectChange) {
onSelectChange(item);
}
},
[onSelectChange],
);

const expandedItemIds = React.useMemo(() => {
if (!initialSelectedItemId) {
return [] as string[];
}

const ids: string[] = [];

function walkTreeItems(
items: TreeDataItem[] | TreeDataItem,
targetId: string,
) {
if (items instanceof Array) {
for (let i = 0; i < items.length; i++) {
ids.push(items[i]!.id);
if (walkTreeItems(items[i]!, targetId) && !expandAll) {
return true;
}
if (!expandAll) ids.pop();
}
} else if (!expandAll && items.id === targetId) {
return true;
} else if (items.children) {
return walkTreeItems(items.children, targetId);
}
}
)}
{...props}
>
<div className="pb-1 pt-0">{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;

walkTreeItems(data, initialSelectedItemId);
return ids;
}, [data, expandAll, initialSelectedItemId]);
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" /> : <></>;
};

return (
<div className={cn('overflow-hidden relative p-2', className)}>
<TreeItem
data={data}
ref={ref}
selectedItemId={selectedItemId}
handleSelectChange={handleSelectChange}
expandedItemIds={expandedItemIds}
defaultLeafIcon={defaultLeafIcon}
defaultNodeIcon={defaultNodeIcon}
{...props}
/>
</div>
);
},
);
TreeView.displayName = 'TreeView';
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…
Cancel
Save