New "more" nodeToolbar button (#952)

This PR introduces a new feature to enhance the editing capabilities of
nodes in our application. We've added a "More" button to the
nodeToolbar, which, when clicked, reveals additional options for editing
nodes.
This commit is contained in:
anovazzi1 2023-09-21 20:05:41 -03:00 • committed by GitHub
commit a12a7fd1cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 309 additions and 150 deletions

View file

@ -0,0 +1,108 @@
"use client";
import * as SelectPrimitive from "@radix-ui/react-select";
import * as React from "react";
import { cn } from "../../utils/utils";
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn("flex w-full items-center justify-between", className)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild></SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-3 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
};

View file

@ -1,12 +1,5 @@
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { import { ReactNode, forwardRef, useContext, useEffect, useState } from "react";
ReactNode,
forwardRef,
useContext,
useEffect,
useRef,
useState,
} from "react";
import CodeAreaComponent from "../../components/codeAreaComponent"; import CodeAreaComponent from "../../components/codeAreaComponent";
import DictComponent from "../../components/dictComponent"; import DictComponent from "../../components/dictComponent";
import Dropdown from "../../components/dropdownComponent"; import Dropdown from "../../components/dropdownComponent";
@ -49,19 +42,22 @@ const EditNodeModal = forwardRef(
setData, setData,
nodeLength, nodeLength,
children, children,
open,
onClose,
}: { }: {
data: NodeDataType; data: NodeDataType;
setData: (data: NodeDataType) => void; setData: (data: NodeDataType) => void;
nodeLength: number; nodeLength: number;
children: ReactNode; children: ReactNode;
open?: boolean;
onClose?: (close: boolean) => void;
}, },
ref ref
) => { ) => {
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(open ?? false);
const [myData, setMyData] = useState(data);
const { setTabsState, tabId } = useContext(TabsContext); const { setTabsState, tabId } = useContext(TabsContext);
const { reactFlowInstance } = useContext(typesContext); const { reactFlowInstance } = useContext(typesContext);
const myData = useRef(data);
let disabled = let disabled =
reactFlowInstance reactFlowInstance
?.getEdges() ?.getEdges()
@ -70,17 +66,18 @@ const EditNodeModal = forwardRef(
function changeAdvanced(n) { function changeAdvanced(n) {
let newData = cloneDeep(data); let newData = cloneDeep(data);
newData.node!.template[n].advanced = !newData.node!.template[n].advanced; newData.node!.template[n].advanced = !newData.node!.template[n].advanced;
myData.current = newData; setMyData(newData);
} }
const handleOnNewValue = (newValue: any, name) => { const handleOnNewValue = (newValue: any, name) => {
let newData = cloneDeep(data); let newData = cloneDeep(data);
newData.node!.template[name].value = newValue; newData.node!.template[name].value = newValue;
myData.current = newData; setMyData(newData);
}; };
useEffect(() => { useEffect(() => {
myData.current = data; setMyData(data); // reset data to what it is on node when opening modal
onClose!(modalOpen);
}, [modalOpen]); }, [modalOpen]);
const [obj, setObj] = useState({ const [obj, setObj] = useState({
@ -109,11 +106,11 @@ const EditNodeModal = forwardRef(
setOpen={setModalOpen} setOpen={setModalOpen}
onChangeOpenModal={(open) => { onChangeOpenModal={(open) => {
let newData = cloneDeep(data); let newData = cloneDeep(data);
myData.current = newData; setMyData(newData);
}} }}
> >
<BaseModal.Trigger>{children}</BaseModal.Trigger> <BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={myData.current.node?.description!}> <BaseModal.Header description={myData.node?.description!}>
<span className="pr-2">{myData.type}</span> <span className="pr-2">{myData.type}</span>
<Badge variant="secondary">ID: {myData.id}</Badge> <Badge variant="secondary">ID: {myData.id}</Badge>
</BaseModal.Header> </BaseModal.Header>
@ -148,65 +145,58 @@ const EditNodeModal = forwardRef(
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody className="p-0"> <TableBody className="p-0">
{Object.keys(myData.current.node!.template) {Object.keys(myData.node!.template)
.filter( .filter(
(templateParam) => (templateParam) =>
templateParam.charAt(0) !== "_" && templateParam.charAt(0) !== "_" &&
myData.current.node?.template[templateParam].show && myData.node?.template[templateParam].show &&
(myData.current.node.template[templateParam] (myData.node.template[templateParam].type ===
.type === "str" || "str" ||
myData.current.node.template[templateParam] myData.node.template[templateParam].type ===
.type === "bool" || "bool" ||
myData.current.node.template[templateParam] myData.node.template[templateParam].type ===
.type === "float" || "float" ||
myData.current.node.template[templateParam] myData.node.template[templateParam].type ===
.type === "code" || "code" ||
myData.current.node.template[templateParam] myData.node.template[templateParam].type ===
.type === "prompt" || "prompt" ||
myData.current.node.template[templateParam] myData.node.template[templateParam].type ===
.type === "file" || "file" ||
myData.current.node.template[templateParam] myData.node.template[templateParam].type ===
.type === "int") "int")
) )
.map((templateParam, index) => ( .map((templateParam, index) => (
<TableRow key={index} className="h-10"> <TableRow key={index} className="h-10">
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3"> <TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
{myData.current.node?.template[templateParam].name {myData.node?.template[templateParam].name
? myData.current.node.template[templateParam] ? myData.node.template[templateParam].name
.name : myData.node?.template[templateParam]
: myData.current.node?.template[templateParam]
.display_name} .display_name}
</TableCell> </TableCell>
<TableCell className="w-[300px] p-0 text-center text-xs text-foreground "> <TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
{myData.current.node?.template[templateParam] {myData.node?.template[templateParam].type ===
.type === "str" && "str" &&
!myData.current.node.template[templateParam] !myData.node.template[templateParam].options ? (
.options ? (
<div className="mx-auto"> <div className="mx-auto">
{myData.current.node.template[templateParam] {myData.node.template[templateParam].list ? (
.list ? (
<InputListComponent <InputListComponent
editNode={true} editNode={true}
disabled={disabled} disabled={disabled}
value={ value={
!myData.current.node.template[ !myData.node.template[templateParam]
templateParam .value ||
].value || myData.node.template[templateParam]
myData.current.node.template[ .value === ""
templateParam
].value === ""
? [""] ? [""]
: myData.current.node.template[ : myData.node.template[templateParam]
templateParam .value
].value
} }
onChange={(value: string[]) => { onChange={(value: string[]) => {
handleOnNewValue(value, templateParam); handleOnNewValue(value, templateParam);
}} }}
/> />
) : myData.current.node?.template[ ) : myData.node?.template[templateParam]
templateParam .type === "NestedDict" ? (
].type === "NestedDict" ? (
<div className="mt-2 w-full"> <div className="mt-2 w-full">
<DictComponent <DictComponent
disabled={disabled} disabled={disabled}
@ -217,23 +207,20 @@ const EditNodeModal = forwardRef(
}} }}
/> />
</div> </div>
) : myData.current.node?.template[ ) : myData.node?.template[templateParam]
templateParam .type === "dict" ? (
].type === "dict" ? (
<div className="mt-2 w-full"> <div className="mt-2 w-full">
<KeypairListComponent <KeypairListComponent
disabled={disabled} disabled={disabled}
editNode={false} editNode={false}
value={ value={
myData.current.node.template[ myData.node.template[templateParam]
templateParam .value?.length === 0 ||
].value?.length === 0 || !myData.node.template[templateParam]
!myData.current.node.template[ .value
templateParam
].value
? dictArr ? dictArr
: convertObjToArray( : convertObjToArray(
myData.current.node.template[ myData.node.template[
templateParam templateParam
].value ].value
) )
@ -247,23 +234,21 @@ const EditNodeModal = forwardRef(
setDictArr(newValue); setDictArr(newValue);
} else { } else {
setDictArr(newValue); setDictArr(newValue);
myData.current.node!.template[ myData.node!.template[
templateParam templateParam
].value = newValue; ].value = newValue;
} }
}} }}
/> />
</div> </div>
) : myData.current.node.template[ ) : myData.node.template[templateParam]
templateParam .multiline ? (
].multiline ? (
<TextAreaComponent <TextAreaComponent
disabled={disabled} disabled={disabled}
editNode={true} editNode={true}
value={ value={
myData.current.node.template[ myData.node.template[templateParam]
templateParam .value ?? ""
].value ?? ""
} }
onChange={(value: string | string[]) => { onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam); handleOnNewValue(value, templateParam);
@ -274,14 +259,12 @@ const EditNodeModal = forwardRef(
editNode={true} editNode={true}
disabled={disabled} disabled={disabled}
password={ password={
myData.current.node.template[ myData.node.template[templateParam]
templateParam .password ?? false
].password ?? false
} }
value={ value={
myData.current.node.template[ myData.node.template[templateParam]
templateParam .value ?? ""
].value ?? ""
} }
onChange={(value) => { onChange={(value) => {
handleOnNewValue(value, templateParam); handleOnNewValue(value, templateParam);
@ -289,16 +272,14 @@ const EditNodeModal = forwardRef(
/> />
)} )}
</div> </div>
) : myData.current.node?.template[templateParam] ) : myData.node?.template[templateParam].type ===
.type === "bool" ? ( "bool" ? (
<div className="ml-auto"> <div className="ml-auto">
{" "} {" "}
<ToggleShadComponent <ToggleShadComponent
disabled={disabled} disabled={disabled}
enabled={ enabled={
myData.current.node.template[ myData.node.template[templateParam].value
templateParam
].value
} }
setEnabled={(isEnabled) => { setEnabled={(isEnabled) => {
handleOnNewValue( handleOnNewValue(
@ -309,84 +290,76 @@ const EditNodeModal = forwardRef(
size="small" size="small"
/> />
</div> </div>
) : myData.current.node?.template[templateParam] ) : myData.node?.template[templateParam].type ===
.type === "float" ? ( "float" ? (
<div className="mx-auto"> <div className="mx-auto">
<FloatComponent <FloatComponent
disabled={disabled} disabled={disabled}
editNode={true} editNode={true}
value={ value={
myData.current.node.template[ myData.node.template[templateParam]
templateParam .value ?? ""
].value ?? ""
} }
onChange={(value) => { onChange={(value) => {
handleOnNewValue(value, templateParam); handleOnNewValue(value, templateParam);
}} }}
/> />
</div> </div>
) : myData.current.node?.template[templateParam] ) : myData.node?.template[templateParam].type ===
.type === "str" && "str" &&
myData.current.node.template[templateParam] myData.node.template[templateParam].options ? (
.options ? (
<div className="mx-auto"> <div className="mx-auto">
<Dropdown <Dropdown
numberOfOptions={nodeLength} numberOfOptions={nodeLength}
editNode={true} editNode={true}
options={ options={
myData.current.node.template[ myData.node.template[templateParam]
templateParam .options
].options
} }
onSelect={(value) => onSelect={(value) =>
handleOnNewValue(value, templateParam) handleOnNewValue(value, templateParam)
} }
value={ value={
myData.current.node.template[ myData.node.template[templateParam]
templateParam .value ?? "Choose an option"
].value ?? "Choose an option"
} }
></Dropdown> ></Dropdown>
</div> </div>
) : myData.current.node?.template[templateParam] ) : myData.node?.template[templateParam].type ===
.type === "int" ? ( "int" ? (
<div className="mx-auto"> <div className="mx-auto">
<IntComponent <IntComponent
disabled={disabled} disabled={disabled}
editNode={true} editNode={true}
value={ value={
myData.current.node.template[ myData.node.template[templateParam]
templateParam .value ?? ""
].value ?? ""
} }
onChange={(value) => { onChange={(value) => {
handleOnNewValue(value, templateParam); handleOnNewValue(value, templateParam);
}} }}
/> />
</div> </div>
) : myData.current.node?.template[templateParam] ) : myData.node?.template[templateParam].type ===
.type === "file" ? ( "file" ? (
<div className="mx-auto"> <div className="mx-auto">
<InputFileComponent <InputFileComponent
editNode={true} editNode={true}
disabled={disabled} disabled={disabled}
value={ value={
myData.current.node.template[ myData.node.template[templateParam]
templateParam .value ?? ""
].value ?? ""
} }
onChange={(value: string | string[]) => { onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam); handleOnNewValue(value, templateParam);
}} }}
fileTypes={ fileTypes={
myData.current.node.template[ myData.node.template[templateParam]
templateParam .fileTypes
].fileTypes
} }
suffixes={ suffixes={
myData.current.node.template[ myData.node.template[templateParam]
templateParam .suffixes
].suffixes
} }
onFileChange={(filePath: string) => { onFileChange={(filePath: string) => {
data.node!.template[ data.node!.template[
@ -395,29 +368,28 @@ const EditNodeModal = forwardRef(
}} }}
></InputFileComponent> ></InputFileComponent>
</div> </div>
) : myData.current.node?.template[templateParam] ) : myData.node?.template[templateParam].type ===
.type === "prompt" ? ( "prompt" ? (
<div className="mx-auto"> <div className="mx-auto">
<PromptAreaComponent <PromptAreaComponent
field_name={templateParam} field_name={templateParam}
editNode={true} editNode={true}
disabled={disabled} disabled={disabled}
nodeClass={myData.current.node} nodeClass={myData.node}
setNodeClass={(nodeClass) => { setNodeClass={(nodeClass) => {
myData.current.node = nodeClass; myData.node = nodeClass;
}} }}
value={ value={
myData.current.node.template[ myData.node.template[templateParam]
templateParam .value ?? ""
].value ?? ""
} }
onChange={(value: string | string[]) => { onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam); handleOnNewValue(value, templateParam);
}} }}
/> />
</div> </div>
) : myData.current.node?.template[templateParam] ) : myData.node?.template[templateParam].type ===
.type === "code" ? ( "code" ? (
<div className="mx-auto"> <div className="mx-auto">
<CodeAreaComponent <CodeAreaComponent
dynamic={ dynamic={
@ -431,17 +403,16 @@ const EditNodeModal = forwardRef(
disabled={disabled} disabled={disabled}
editNode={true} editNode={true}
value={ value={
myData.current.node.template[ myData.node.template[templateParam]
templateParam .value ?? ""
].value ?? ""
} }
onChange={(value: string | string[]) => { onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam); handleOnNewValue(value, templateParam);
}} }}
/> />
</div> </div>
) : myData.current.node?.template[templateParam] ) : myData.node?.template[templateParam].type ===
.type === "Any" ? ( "Any" ? (
"-" "-"
) : ( ) : (
<div className="hidden"></div> <div className="hidden"></div>
@ -451,9 +422,8 @@ const EditNodeModal = forwardRef(
<div className="items-center text-center"> <div className="items-center text-center">
<ToggleShadComponent <ToggleShadComponent
enabled={ enabled={
!myData.current.node?.template[ !myData.node?.template[templateParam]
templateParam .advanced
].advanced
} }
setEnabled={(e) => setEnabled={(e) =>
changeAdvanced(templateParam) changeAdvanced(templateParam)

View file

@ -2,10 +2,16 @@ import { useContext, useState } from "react";
import { useReactFlow, useUpdateNodeInternals } from "reactflow"; import { useReactFlow, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../../../components/ShadTooltipComponent"; import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent"; import IconComponent from "../../../../components/genericIconComponent";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from "../../../../components/ui/select-custom";
import { TabsContext } from "../../../../contexts/tabsContext"; import { TabsContext } from "../../../../contexts/tabsContext";
import EditNodeModal from "../../../../modals/EditNodeModal"; import EditNodeModal from "../../../../modals/EditNodeModal";
import { nodeToolbarPropsType } from "../../../../types/components"; import { nodeToolbarPropsType } from "../../../../types/components";
import { classNames } from "../../../../utils/utils"; import { classNames, getRandomKeyByssmm } from "../../../../utils/utils";
export default function NodeToolbarComponent({ export default function NodeToolbarComponent({
data, data,
@ -40,9 +46,24 @@ export default function NodeToolbarComponent({
if (countHandles > 1) return false; if (countHandles > 1) return false;
return true; return true;
} }
const isMinimal = canMinimize();
const { paste } = useContext(TabsContext); const { paste } = useContext(TabsContext);
const reactFlowInstance = useReactFlow(); const reactFlowInstance = useReactFlow();
const [showModalAdvanced, setShowModalAdvanced] = useState(false);
const [selectedValue, setSelectedValue] = useState("");
const handleSelectChange = (event) => {
setSelectedValue(event);
if (event.includes("advanced")) {
return setShowModalAdvanced(true);
}
setShowModalAdvanced(false);
if (event.includes("show")) {
setShowNode((prev) => !prev);
updateNodeInternals(data.id);
}
};
return ( return (
<> <>
<div className="w-26 h-10"> <div className="w-26 h-10">
@ -110,6 +131,83 @@ export default function NodeToolbarComponent({
</a> </a>
</ShadTooltip> </ShadTooltip>
{isMinimal ? (
<Select onValueChange={handleSelectChange} value={selectedValue}>
<ShadTooltip content="More" side="top">
<SelectTrigger>
<div>
<div
className={classNames(
"relative -ml-px inline-flex h-8 w-[31px] items-center rounded-r-md bg-background text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10" +
(nodeLength == 0
? " text-muted-foreground"
: " text-foreground")
)}
>
<IconComponent
name="MoreHorizontal"
className="relative left-2 h-4 w-4"
/>
</div>
</div>
</SelectTrigger>
</ShadTooltip>
<SelectContent>
<SelectItem value={getRandomKeyByssmm() + "advanced"}>
<div className="flex">
<IconComponent
name="Settings2"
className="relative top-0.5 mr-2 h-4 w-4"
/>{" "}
Edit{" "}
</div>{" "}
</SelectItem>
{isMinimal && (
<SelectItem value={getRandomKeyByssmm() + "show"}>
<div className="flex">
<IconComponent
name={showNode ? "Minimize2" : "Maximize2"}
className="relative top-0.5 mr-2 h-4 w-4"
/>
{showNode ? "Minimize" : "Expand"}
</div>
</SelectItem>
)}
</SelectContent>
</Select>
) : (
<ShadTooltip content="Edit" side="top">
<div>
<button
onClick={() => setShowModalAdvanced(true)}
className={classNames(
"relative -ml-px inline-flex items-center rounded-r-md bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10" +
(nodeLength == 0
? " text-muted-foreground"
: " text-foreground")
)}
>
<IconComponent name="Settings2" className="h-4 w-4 " />
</button>
</div>
</ShadTooltip>
)}
{showModalAdvanced && (
<EditNodeModal
data={data}
setData={setData}
nodeLength={nodeLength}
open={showModalAdvanced}
onClose={(modal) => {
setShowModalAdvanced(modal);
}}
>
<></>
</EditNodeModal>
)}
{/*
<ShadTooltip content="Edit" side="top"> <ShadTooltip content="Edit" side="top">
<div> <div>
<EditNodeModal <EditNodeModal
@ -130,24 +228,7 @@ export default function NodeToolbarComponent({
</div> </div>
</EditNodeModal> </EditNodeModal>
</div> </div>
</ShadTooltip> </ShadTooltip> */}
{canMinimize() && (
<ShadTooltip content={showNode ? "Minimize" : "Expand"} side="top">
<button
className="relative inline-flex items-center rounded-r-md bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
onClick={(event) => {
setShowNode((prev) => !prev);
updateNodeInternals(data.id);
}}
>
<IconComponent
name={showNode ? "Minimize2" : "Maximize2"}
className="h-4 w-4"
/>
</button>
</ShadTooltip>
)}
</span> </span>
</div> </div>
</> </>