fix: update Gmail icon to Google and improve ListSelectionComponent styles (#7591)

* fix: update Gmail icon to Google and improve ListSelectionComponent styles

* fix: remove unnecessary info and improve required field handling in Composio components

* style: update ListSelectionComponent and SortableListItem for improved layout and styling

* fix: enhance ListSelectionComponent and SortableListComponent to support search functionality

* style: adjust height and padding in SortableListItem for better visual consistency

* ✅ (intComponent.spec.ts): update test assertions to match expected behavior after changes in the component's functionality

---------

Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
Deon Sanchez 2025-04-14 17:08:43 -06:00 • committed by GitHub
commit 28675d873f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 64 additions and 45 deletions

View file

@ -38,7 +38,6 @@ class ComposioBaseComponent(Component):
name="api_key", name="api_key",
display_name="Composio API Key", display_name="Composio API Key",
required=True, required=True,
info="Refer to https://docs.composio.dev/faq/api_key/api_key",
real_time_refresh=True, real_time_refresh=True,
value="COMPOSIO_API_KEY", value="COMPOSIO_API_KEY",
), ),
@ -53,12 +52,11 @@ class ComposioBaseComponent(Component):
placeholder="Select action", placeholder="Select action",
options=[], options=[],
value="disabled", value="disabled",
info="Select action to pass to the agent",
helper_text="Please connect before selecting actions.", helper_text="Please connect before selecting actions.",
helper_text_metadata={"variant": "destructive"}, helper_text_metadata={"variant": "destructive"},
show=True, show=True,
required=False,
real_time_refresh=True, real_time_refresh=True,
required=True,
limit=1, limit=1,
), ),
] ]

View file

@ -16,9 +16,8 @@ class ComposioGmailAPIComponent(ComposioBaseComponent):
"""Gmail API component for interacting with Gmail services.""" """Gmail API component for interacting with Gmail services."""
display_name: str = "Gmail" display_name: str = "Gmail"
description: str = "Gmail API"
name = "GmailAPI" name = "GmailAPI"
icon = "Gmail" icon = "Google"
documentation: str = "https://docs.composio.dev" documentation: str = "https://docs.composio.dev"
app_name = "gmail" app_name = "gmail"

View file

@ -1,7 +1,9 @@
import ForwardedIconComponent from "@/components/common/genericIconComponent"; import ForwardedIconComponent from "@/components/common/genericIconComponent";
import ShadTooltip from "@/components/common/shadTooltipComponent"; import ShadTooltip from "@/components/common/shadTooltipComponent";
import SearchBarComponent from "@/components/core/parameterRenderComponent/components/searchBarComponent"; import SearchBarComponent from "@/components/core/parameterRenderComponent/components/searchBarComponent";
import { InputProps } from "@/components/core/parameterRenderComponent/types";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { DialogHeader } from "@/components/ui/dialog";
import { Dialog, DialogContent } from "@/components/ui/dialog-with-no-close"; import { Dialog, DialogContent } from "@/components/ui/dialog-with-no-close";
import { cn, testIdCase } from "@/utils/utils"; import { cn, testIdCase } from "@/utils/utils";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@ -64,8 +66,9 @@ const ListItem = ({
unstyled unstyled
size="sm" size="sm"
className={cn( className={cn(
"group w-full rounded-md py-3 pl-3 pr-3", "group flex w-full rounded-md px-2 py-0.5",
!isKeyboardNavActive && "hover:bg-muted", // Only apply hover styles when not in keyboard nav !isKeyboardNavActive && "hover:bg-muted", // Only apply hover styles when not in keyboard nav
!item.metaData && "py-2.5",
isFocused && "bg-muted", isFocused && "bg-muted",
className, className,
)} )}
@ -85,21 +88,30 @@ const ListItem = ({
> >
<div className="flex w-full items-center gap-2"> <div className="flex w-full items-center gap-2">
{item.icon && ( {item.icon && (
<ForwardedIconComponent name={item.icon} className="h-5 w-5" /> <div>
<ForwardedIconComponent name={item.icon} className="mr-2 h-4 w-4" />
</div>
)} )}
<div className="truncate text-sm">{item.name}</div> <div className="flex w-full flex-col truncate">
<div className="flex w-full truncate text-[13px] font-semibold">
<span className="truncate">{item.name}</span>
</div>
{"metaData" in item && item.metaData && ( {"metaData" in item && item.metaData && (
<div className="text-gray-500">{item.metaData}</div> <div className="flex w-full truncate text-[13px] text-gray-500">
<span className="truncate">{item.metaData}</span>
</div>
)} )}
</div>
{isHovered || isFocused ? ( {isHovered || isFocused ? (
<div className="ml-auto flex items-center justify-start rounded-md"> <div className="ml-auto flex items-center justify-start rounded-md">
<div className="flex items-center pr-1.5 text-sm text-muted-foreground"> <div className="flex items-center pr-1.5 text-[13px] font-semibold text-muted-foreground">
Select Select
</div> </div>
<div className="flex items-center justify-center rounded-md bg-border p-1"> <div className="flex items-center justify-center rounded-md">
<ForwardedIconComponent <ForwardedIconComponent
name="corner-down-left" name="corner-down-left"
className="h-3 w-3 text-muted-foreground" className="h-3.5 w-3.5 text-muted-foreground"
/> />
</div> </div>
</div> </div>
@ -129,7 +141,9 @@ const ListSelectionComponent = ({
selectedList = [], selectedList = [],
options, options,
limit = 1, limit = 1,
}: ListSelectionComponentProps) => { ...baseInputProps
}: InputProps<any, ListSelectionComponentProps>) => {
const { nodeClass } = baseInputProps;
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [hoveredItem, setHoveredItem] = useState<any | null>(null); const [hoveredItem, setHoveredItem] = useState<any | null>(null);
const [focusedIndex, setFocusedIndex] = useState<number>(-1); const [focusedIndex, setFocusedIndex] = useState<number>(-1);
@ -263,28 +277,33 @@ const ListSelectionComponent = ({
return ( return (
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}> <Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
<DialogContent <DialogContent
className="flex max-h-[65vh] min-h-[15vh] flex-col rounded-xl" className="flex max-h-[65vh] min-h-[15vh] flex-col rounded-xl p-0"
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
> >
<div className="flex items-center justify-between"> <DialogHeader className="flex w-full justify-between border-b px-3 py-3">
<div className="flex items-center gap-2">
<ForwardedIconComponent
name={nodeClass?.icon || "unknown"}
className="h-[18px] w-[18px] text-muted-foreground"
/>
<div className="text-[13px] font-semibold">
{nodeClass?.display_name}
</div>
</div>
</DialogHeader>
{(filteredList?.length > 20 || search) && (
<div className="flex w-full items-center justify-between px-3">
<SearchBarComponent <SearchBarComponent
searchCategories={searchCategories} searchCategories={searchCategories}
search={search} search={search}
setSearch={setSearch} setSearch={setSearch}
/> />
<Button
unstyled
size="icon"
className="ml-auto h-[38px]"
onClick={onClose}
>
<ForwardedIconComponent name="x" />
</Button>
</div> </div>
)}
<div <div
ref={listContainerRef} ref={listContainerRef}
className="flex flex-col gap-1 overflow-y-auto" className="flex w-full flex-col gap-1 overflow-y-auto px-3 pb-3"
> >
{filteredList.length > 0 ? ( {filteredList.length > 0 ? (
filteredList.map((item, index) => ( filteredList.map((item, index) => (

View file

@ -152,6 +152,7 @@ export default function NodeInputField({
title, title,
nodeId: data.id, nodeId: data.id,
isFlexView, isFlexView,
required,
})} })}
</span> </span>
} }
@ -165,13 +166,13 @@ export default function NodeInputField({
title, title,
nodeId: data.id, nodeId: data.id,
isFlexView, isFlexView,
required,
})} })}
</span> </span>
} }
</span> </span>
</div> </div>
)} )}
<span className={"text-status-red"}>{required ? "*" : ""}</span>
<div> <div>
{info !== "" && ( {info !== "" && (
<ShadTooltip content={<NodeInputInfo info={info} />}> <ShadTooltip content={<NodeInputInfo info={info} />}>

View file

@ -74,6 +74,8 @@ const RenderInputParameters = ({
return keyMap; return keyMap;
}, [templateFields, data.id, data.node?.template]); }, [templateFields, data.id, data.node?.template]);
console.log({ data });
const renderInputParameter = templateFields.map( const renderInputParameter = templateFields.map(
(templateField: string, idx) => { (templateField: string, idx) => {
const template = data.node?.template[templateField]; const template = data.node?.template[templateField];

View file

@ -250,8 +250,6 @@ function GenericNode({
[data.node?.outputs], [data.node?.outputs],
); );
console.log(shownOutputs, hiddenOutputs);
const [hasChangedNodeDescription, setHasChangedNodeDescription] = const [hasChangedNodeDescription, setHasChangedNodeDescription] =
useState(false); useState(false);

View file

@ -236,6 +236,7 @@ const ConnectionComponent = ({
setSelectedList={setSelectedItem} setSelectedList={setSelectedItem}
selectedList={selectedItem} selectedList={selectedItem}
options={options} options={options}
{...baseInputProps}
/> />
</div> </div>
); );

View file

@ -35,7 +35,7 @@ const SearchBarComponent = ({
}; };
return ( return (
<div className="mr-10 flex w-full items-center rounded-md border"> <div className="flex w-full items-center rounded-md border">
{searchCategories && searchCategories.length > 0 && ( {searchCategories && searchCategories.length > 0 && (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>

View file

@ -33,7 +33,7 @@ const SortableListItem = memo(
<li <li
className={cn( className={cn(
"inline-flex h-12 w-full items-center gap-2 text-sm font-medium text-gray-800", "inline-flex h-12 w-full items-center gap-2 text-sm font-medium text-gray-800",
limit === 1 ? "h-10 rounded-md bg-muted" : "group cursor-grab", limit === 1 ? "h-6 rounded-md bg-muted" : "group cursor-grab",
)} )}
> >
{limit !== 1 && ( {limit !== 1 && (
@ -52,8 +52,8 @@ const SortableListItem = memo(
<span <span
className={cn( className={cn(
"truncate text-primary", "truncate text-muted-foreground",
limit === 1 ? "max-w-56 pl-3" : "max-w-48", limit === 1 ? "max-w-56 pl-2" : "max-w-48",
)} )}
> >
{data.name} {data.name}
@ -63,9 +63,9 @@ const SortableListItem = memo(
size="icon" size="icon"
variant={limit !== 1 ? "outline" : "ghost"} variant={limit !== 1 ? "outline" : "ghost"}
className={cn( className={cn(
"ml-auto h-7 w-7 opacity-0 transition-opacity duration-200", "ml-auto h-6 w-6 opacity-0 transition-opacity duration-200",
limit === 1 limit === 1
? "group pr-3 opacity-100" ? "group pr-1 opacity-100"
: "hover:border hover:border-destructive hover:bg-transparent hover:opacity-100", : "hover:border hover:border-destructive hover:bg-transparent hover:opacity-100",
)} )}
onClick={onRemove} onClick={onRemove}
@ -192,6 +192,7 @@ const SortableListComponent = ({
selectedList={listData} selectedList={listData}
options={options} options={options}
limit={limit} limit={limit}
{...baseInputProps}
/> />
</div> </div>
); );

View file

@ -35,7 +35,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-contentHide data-[state=open]:animate-contentShow", "fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-3 rounded-xl border bg-background p-3 shadow-lg duration-200 data-[state=closed]:animate-contentHide data-[state=open]:animate-contentShow",
className, className,
)} )}
{...props} {...props}

View file

@ -88,7 +88,7 @@ test("IntComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => {
await page.locator('//*[@id="showtemperature"]').click(); await page.locator('//*[@id="showtemperature"]').click();
expect( expect(
await page.locator('//*[@id="showtemperature"]').isChecked(), await page.locator('//*[@id="showtemperature"]').isChecked(),
).toBeTruthy(); ).toBeFalsy();
await page.locator('//*[@id="showmodel_kwargs"]').click(); await page.locator('//*[@id="showmodel_kwargs"]').click();
expect( expect(
@ -108,7 +108,7 @@ test("IntComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => {
await page.locator('//*[@id="showtemperature"]').click(); await page.locator('//*[@id="showtemperature"]').click();
expect( expect(
await page.locator('//*[@id="showtemperature"]').isChecked(), await page.locator('//*[@id="showtemperature"]').isChecked(),
).toBeFalsy(); ).toBeTruthy();
await page.locator('//*[@id="showmodel_kwargs"]').click(); await page.locator('//*[@id="showmodel_kwargs"]').click();
expect( expect(
@ -128,7 +128,7 @@ test("IntComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => {
await page.locator('//*[@id="showtemperature"]').click(); await page.locator('//*[@id="showtemperature"]').click();
expect( expect(
await page.locator('//*[@id="showtemperature"]').isChecked(), await page.locator('//*[@id="showtemperature"]').isChecked(),
).toBeTruthy(); ).toBeFalsy();
await page.getByText("Close").last().click(); await page.getByText("Close").last().click();