Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9e11959
fix: enhance dropdown component with refresh button and clean up para…
deon-sanchez Jun 11, 2025
3e0ed43
Merge branch 'main' into lfoss-1414
deon-sanchez Jun 11, 2025
d2a7bc5
[autofix.ci] apply automated fixes
autofix-ci[bot] Jun 11, 2025
4c01935
refactor(OutputComponent): replace DropdownMenu with Popover and Comm…
deon-sanchez Jun 11, 2025
ba2a74b
Merge branch 'lfoss-1414' of https://github.com/langflow-ai/langflow …
deon-sanchez Jun 11, 2025
bc42b8d
Merge branch 'main' of https://github.com/langflow-ai/langflow into l…
deon-sanchez Jun 12, 2025
d4bb558
refactor: update Memory Chatbot configuration and remove unused Refre…
deon-sanchez Jun 12, 2025
c254d9a
refactor: update dropdown component layout for improved styling
deon-sanchez Jun 12, 2025
5b4067d
refactor: streamline dropdown component structure and enhance button …
deon-sanchez Jun 12, 2025
6314159
refactor: enhance dropdown component styling for better readability
deon-sanchez Jun 12, 2025
49d1695
refactor: adjust dropdown component styling for improved usability
deon-sanchez Jun 12, 2025
180d573
refactor: enhance dropdown component rendering and styling
deon-sanchez Jun 12, 2025
80b406d
Merge branch 'main' into lfoss-1414
deon-sanchez Jun 12, 2025
862deaa
Merge branch 'main' into lfoss-1414
deon-sanchez Jun 12, 2025
bd332b0
Merge branch 'main' of https://github.com/langflow-ai/langflow into l…
deon-sanchez Jun 13, 2025
f7b085d
Merge branch 'main' of https://github.com/langflow-ai/langflow into l…
deon-sanchez Jun 16, 2025
c3c6430
Merge branch 'main' into lfoss-1414
deon-sanchez Jun 17, 2025
fa91da9
Merge branch 'main' into lfoss-1414
deon-sanchez Jun 18, 2025
c9ea98d
Merge branch 'main' into lfoss-1414
deon-sanchez Jun 23, 2025
1e7bcc4
Merge branch 'main' into lfoss-1414
deon-sanchez Jun 30, 2025
5560536
Merge branch 'main' into lfoss-1414
deon-sanchez Jun 30, 2025
9aa1e87
Merge branch 'main' into lfoss-1414
deon-sanchez Jun 30, 2025
ace2de5
Merge branch 'main' of https://github.com/langflow-ai/langflow into l…
deon-sanchez Jun 30, 2025
099e116
feat: add data-testid attributes for refresh buttons and simplify mem…
deon-sanchez Jun 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { ForwardedIconComponent } from "@/components/common/genericIconComponent";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
Command,
CommandGroup,
CommandItem,
CommandList,
} from "@/components/ui/command";

import {
Popover,
PopoverContentWithoutPortal,
PopoverTrigger,
} from "@/components/ui/popover";
import useFlowStore from "@/stores/flowStore";
import { useRef } from "react";
import ShadTooltip from "../../../../components/common/shadTooltipComponent";
import { outputComponentType } from "../../../../types/components";
import { cn } from "../../../../utils/utils";
Expand Down Expand Up @@ -56,18 +63,21 @@ export default function OutputComponent({
const hasGroupOutputs = outputs?.some?.((output) => output.group_outputs);
const isConditionalRouter = nodeType === "ConditionalRouter";
const hasOutputs = outputs.length > 1;
const refButton = useRef<HTMLButtonElement>(null);

const shouldShowDropdown =
hasOutputs && !hasLoopOutput && !hasGroupOutputs && !isConditionalRouter;

return (
<div>
{shouldShowDropdown ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Popover>
<PopoverTrigger asChild>
<Button
unstyled
className="group flex items-center gap-2"
role="combobox"
ref={refButton}
className="no-focus-visible group flex items-center gap-2"
data-testid={`dropdown-output-${outputName?.toLowerCase()}`}
>
<div className="flex items-center gap-1 truncate rounded-md px-2 py-1 text-[13px] font-medium group-hover:bg-primary/10">
Expand All @@ -78,27 +88,38 @@ export default function OutputComponent({
/>
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="min-w-[200px] max-w-[250px]">
{outputs.map((output) => (
<DropdownMenuItem
key={output.name}
data-testid={`dropdown-item-output-${outputName?.toLowerCase()}-${output.display_name?.toLowerCase()}`}
className="cursor-pointer justify-between px-3 py-2"
onClick={() => {
handleSelectOutput && handleSelectOutput(output);
}}
>
<span className="truncate text-[13px]">
{output.display_name ?? output.name}
</span>
<span className="ml-4 text-[13px] text-muted-foreground">
{output.types.join(", ")}
</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</PopoverTrigger>
<PopoverContentWithoutPortal
side="bottom"
align="end"
className="noflow nowheel nopan nodelete nodrag w-full min-w-[200px] max-w-[250px] p-0"
>
<Command>
<CommandList>
<CommandGroup defaultChecked={false} className="p-0">
{outputs.map((output) => (
<CommandItem
key={output.name}
data-testid={`dropdown-item-output-${outputName?.toLowerCase()}-${output.display_name?.toLowerCase()}`}
className="cursor-pointer justify-between rounded-none px-3 py-2"
onSelect={() => {
handleSelectOutput && handleSelectOutput(output);
}}
value={output.name}
>
<span className="truncate text-[13px]">
{output.display_name ?? output.name}
</span>
<span className="ml-4 text-[13px] text-muted-foreground">
{output.types.join(", ")}
</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContentWithoutPortal>
</Popover>
) : (
singleOutput
)}
Expand Down
190 changes: 109 additions & 81 deletions src/frontend/src/components/core/dropdownComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export default function Dropdown({
const fuse = new Fuse(validOptions, { keys: ["name", "value"] });
const PopoverContentDropdown =
children || editNode ? PopoverContent : PopoverContentWithoutPortal;
const { helperText } = baseInputProps;
const { helperText, hasRefreshButton } = baseInputProps;

// API and store hooks
const postTemplateValue = usePostTemplateValue({
Expand Down Expand Up @@ -357,7 +357,7 @@ export default function Dropdown({
);

const renderSearchInput = () => (
<div className="flex items-center border-b px-3">
<div className="flex items-center border-b px-2.5">
<ForwardedIconComponent
name="search"
className="mr-2 h-4 w-4 shrink-0 opacity-50"
Expand All @@ -366,66 +366,18 @@ export default function Dropdown({
onChange={searchRoleByTerm}
onKeyDown={handleInputKeyDown}
placeholder="Search options..."
className="flex h-9 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
className="flex h-9 w-full rounded-md bg-transparent py-3 text-[13px] outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
autoComplete="off"
data-testid="dropdown_search_input"
/>
</div>
);

const renderCustomOptionDialog = () => (
<CommandGroup className="flex flex-col">
<CommandItem className="flex cursor-pointer items-center justify-start gap-2 truncate py-3 text-xs font-semibold text-muted-foreground">
<Button
className="w-full"
unstyled
onClick={() => {
setOpenDialog(true);
}}
>
<div className="flex items-center gap-2 pl-1">
<ForwardedIconComponent
name="Plus"
className="h-3 w-3 text-primary"
/>
{`New ${firstWord}`}
</div>
</Button>
</CommandItem>
<CommandItem className="flex cursor-pointer items-center justify-start gap-2 truncate py-3 text-xs font-semibold text-muted-foreground">
<Button
className="w-full"
unstyled
onClick={() => {
handleRefreshButtonPress();
}}
>
<div className="flex items-center gap-2 pl-1">
<ForwardedIconComponent
name="RefreshCcw"
className={cn("refresh-icon h-3 w-3 text-primary")}
/>
Refresh list
</div>
</Button>
</CommandItem>
<NodeDialog
open={openDialog}
dialogInputs={dialogInputs}
onClose={() => {
setOpenDialog(false);
setOpen(false);
}}
nodeId={nodeId!}
name={name!}
nodeClass={nodeClass!}
/>
</CommandGroup>
);
console.log(filteredMetadata);

const renderOptionsList = () => (
<CommandList>
<CommandGroup defaultChecked={false}>
<CommandList className="max-h-[300px] overflow-y-auto">
<CommandGroup defaultChecked={false} className="p-0">
{filteredOptions?.length > 0 ? (
filteredOptions?.map((option, index) => (
<ShadTooltip
Expand All @@ -441,7 +393,7 @@ export default function Dropdown({
onSelect(currentValue);
setOpen(false);
}}
className="items-center"
className="w-full items-center rounded-none"
data-testid={`${option}-${index}-option`}
>
<div className="flex w-full items-center gap-2">
Expand All @@ -452,14 +404,18 @@ export default function Dropdown({
/>
)}
<div
className={cn("flex truncate", {
"flex-col":
filteredMetadata && filteredMetadata?.length > 0,
"w-full pl-2": !filteredMetadata?.[index]?.icon,
className={cn("flex w-full", {
"pl-2": !filteredMetadata?.[index]?.icon,
})}
>
<div className="flex truncate">
{option}{" "}
<div
className={cn("truncate text-[13px]", {
"w-1/2": filteredMetadata?.length !== 0,
})}
>
{option}
</div>
{filteredMetadata?.[index]?.status && (
<span
className={`flex items-center pl-2 text-xs ${getStatusColor(
filteredMetadata?.[index]?.status,
Expand All @@ -471,9 +427,10 @@ export default function Dropdown({
]?.status?.toLowerCase()}
/>
</span>
</div>
{filteredMetadata && filteredMetadata?.length > 0 ? (
<div className="flex w-full items-center overflow-hidden text-muted-foreground">
)}

{filteredMetadata && filteredMetadata?.length > 0 && (
<div className="ml-auto flex items-center overflow-hidden pl-2 text-muted-foreground">
{Object.entries(
filterMetadataKeys(filteredMetadata?.[index] || {}),
)
Expand All @@ -495,25 +452,27 @@ export default function Dropdown({
className="mx-1 h-1 w-1 flex-shrink-0 overflow-visible fill-muted-foreground"
/>
)}
<div
className={cn("text-xs", {
"w-full truncate": i === arr.length - 1,
})}
>{`${String(value)} ${key}`}</div>
<div className="truncate text-xs">
{`${String(value)} ${key}`}
</div>
</div>
))}
</div>
) : (
<div className="ml-auto flex">
<ForwardedIconComponent
name="Check"
className={cn(
"h-4 w-4 shrink-0 text-primary",
value === option ? "opacity-100" : "opacity-0",
)}
/>
</div>
)}
<div
className={cn("pl-2", {
"ml-auto":
!filteredMetadata || filteredMetadata.length === 0,
})}
>
<ForwardedIconComponent
name="Check"
className={cn(
"h-4 w-4 shrink-0 text-primary",
value === option ? "opacity-100" : "opacity-0",
)}
/>
</div>
</div>
</div>
</CommandItem>
Expand All @@ -527,7 +486,55 @@ export default function Dropdown({
)}
</CommandGroup>
<CommandSeparator />
{dialogInputs && dialogInputs?.fields && renderCustomOptionDialog()}
{dialogInputs && dialogInputs?.fields && (
<CommandGroup className="p-0">
<CommandItem className="flex cursor-pointer items-center justify-start gap-2 truncate rounded-none py-2.5 text-xs font-semibold text-muted-foreground">
<Button
className="w-full"
unstyled
onClick={() => {
setOpenDialog(true);
}}
>
<div className="flex items-center gap-2 pl-1">
<ForwardedIconComponent
name="Plus"
className="h-3 w-3 text-primary"
/>
{`New ${firstWord}`}
</div>
</Button>
</CommandItem>
<CommandItem className="flex cursor-pointer items-center justify-start gap-2 truncate rounded-none py-2.5 text-xs font-semibold text-muted-foreground">
<Button
className="w-full"
unstyled
onClick={() => {
handleRefreshButtonPress();
}}
>
<div className="flex items-center gap-2 pl-1">
<ForwardedIconComponent
name="RefreshCcw"
className={cn("refresh-icon h-3 w-3 text-primary")}
/>
Refresh list
</div>
</Button>
</CommandItem>
<NodeDialog
open={openDialog}
dialogInputs={dialogInputs}
onClose={() => {
setOpenDialog(false);
setOpen(false);
}}
nodeId={nodeId!}
name={name!}
nodeClass={nodeClass!}
/>
</CommandGroup>
)}
</CommandList>
);

Expand All @@ -540,9 +547,30 @@ export default function Dropdown({
children ? {} : { minWidth: refButton?.current?.clientWidth ?? "200px" }
}
>
<Command>
<Command className="flex flex-col">
{options?.length > 0 && renderSearchInput()}
{renderOptionsList()}
{!dialogInputs?.fields && hasRefreshButton && (
<div className="sticky bottom-0 border-t bg-background">
<CommandItem className="flex cursor-pointer items-center justify-start gap-2 truncate rounded-b-md py-3 text-xs font-semibold text-muted-foreground">
<Button
className="w-full"
unstyled
onClick={() => {
handleRefreshButtonPress();
}}
>
<div className="flex items-center gap-2 pl-1">
<ForwardedIconComponent
name="RefreshCcw"
className={cn("refresh-icon h-3 w-3 text-primary")}
/>
Refresh list
</div>
</Button>
</CommandItem>
</div>
)}
</Command>
</PopoverContentDropdown>
);
Expand Down
Loading
Loading