refactor: Enhance InputList Component UI/UX (#5726)

* [LFOSS-74]: input list UI improvements

* ✨ (frontend): Add new component 'DeleteButtonInputList' to improve user experience by providing a button to delete items in a list
📝 (frontend): Comment out unused code related to duplicating input items for now, to be revisited in a future release
📝 (frontend): Add a comment to indicate that the 'DropdownMenuInputList' feature will be added back in a future release
📝 (frontend): Update CSS styling for 'hit-area-icon' class to adjust size to h-7 and w-7 for consistency
📝 (frontend): Adjust color values in CSS to improve visual appearance, changing smooth-red to a new shade

* ✨ (keypairListComponent/index.tsx): simplify conditional styling logic for list items to improve readability and maintainability

* 📝 (delete-button-input-list.tsx): Remove unused imports and update function parameter type for removeInput to specify React.MouseEvent
📝 (delete-button-input-list.tsx): Update button className and IconComponent className for better styling and readability
📝 (delete-button-input-list.tsx): Update ICON_STROKE_WIDTH constant value from 1.25 to 1.5 for better icon appearance
📝 (index.tsx): Update InputListComponent to render DeleteButtonInputList component conditionally based on value length
📝 (constants.ts): Update ICON_STROKE_WIDTH constant value from 1.25 to 1.5 for better icon appearance
📝 (inputListComponent.spec.ts): Update test cases to use updated test IDs for delete buttons and adjust expected input values to match changes in component behavior

* [autofix.ci] apply automated fixes

* 🔧 (applies.css): adjust positioning of input-slider-text to fix alignment issue and improve user experience

* 🐛 (button-input-list.tsx): fix button position by adjusting translate-x value to align properly on the page

* ✨ (index.tsx): add visual indicator for focused input element in the list to improve user experience

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
This commit is contained in:
Cristhian Zanforlin Lousa 2025-01-20 10:25:55 -03:00 • committed by GitHub
commit 5c3c680c41
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 116 additions and 80 deletions

View file

@ -0,0 +1,45 @@
import IconComponent from "@/components/common/genericIconComponent";
import { ICON_STROKE_WIDTH } from "@/constants/constants";
import { cn } from "@/utils/utils";
import { getTestId } from "../helpers/get-test-id";
export const DeleteButtonInputList = ({
index,
removeInput,
disabled,
editNode,
componentName,
}: {
index: number;
removeInput: (e: React.MouseEvent) => void;
disabled: boolean;
editNode: boolean;
componentName: string;
}) => {
return (
<button
disabled={disabled}
onClick={removeInput}
data-testid={getTestId("delete", index, editNode, componentName)}
className={cn(
"hit-area-icon delete-btn-group flex items-center justify-center",
disabled
? "pointer-events-none bg-background hover:bg-background"
: "bg-background hover:bg-smooth-red hover:text-destructive",
editNode && "h-4 w-4",
)}
>
<IconComponent
name="X"
className={cn(
"icon-size justify-self-center text-muted-foreground",
!disabled &&
"hover:cursor-pointer [.delete-btn-group:hover_&]:text-destructive",
)}
strokeWidth={ICON_STROKE_WIDTH}
/>
</button>
);
};
export default DeleteButtonInputList;

View file

@ -10,6 +10,7 @@ import { GRADIENT_CLASS } from "@/constants/constants";
import { cn } from "../../../../../utils/utils";
import { getPlaceholder } from "../../helpers/get-placeholder-disabled";
import { InputListComponentType, InputProps } from "../../types";
import { DeleteButtonInputList } from "./components/delete-button-input-list";
export default function InputListComponent({
value = [""],
@ -66,16 +67,16 @@ export default function InputListComponent({
[value, handleOnNewValue],
);
const handleDuplicateInput = useCallback(
(index: number, e: React.MouseEvent | KeyboardEvent) => {
e.preventDefault();
const newInputList = _.cloneDeep(value);
newInputList.splice(index, 0, newInputList[index]);
handleOnNewValue({ value: newInputList });
setDropdownOpen(null);
},
[value, handleOnNewValue],
);
// const handleDuplicateInput = useCallback(
// (index: number, e: React.MouseEvent | KeyboardEvent) => {
// e.preventDefault();
// const newInputList = _.cloneDeep(value);
// newInputList.splice(index, 0, newInputList[index]);
// handleOnNewValue({ value: newInputList });
// setDropdownOpen(null);
// },
// [value, handleOnNewValue],
// );
return (
<div className={cn("w-full", editNode && "max-h-52")}>
@ -92,16 +93,29 @@ export default function InputListComponent({
<div className="mt-2 flex w-full flex-col gap-3">
{value.map((singleValue, index) => (
<div key={index} className="relative flex w-full items-center gap-2">
<div className="group flex flex-1 items-center rounded-md bg-background">
<div key={index} className="flex w-full items-center">
{focusedIndex !== index && !disabled && (
<div
className={cn(
"absolute z-50 h-6 w-16",
editNode ? "translate-x-[12rem]" : "translate-x-[11.1rem]",
)}
style={{
pointerEvents: "none",
background: GRADIENT_CLASS,
}}
aria-hidden="true"
/>
)}
<div className="group relative flex-1">
<Input
ref={index === 0 ? inputRef : null}
disabled={disabled}
type="text"
value={singleValue}
className={cn(
"w-full text-primary",
editNode ? "input-edit-node pr-6" : "pr-10",
"w-full pr-10 text-primary",
editNode ? "input-edit-node" : "",
disabled ? "disabled-state" : "",
)}
placeholder={getPlaceholder(disabled, placeholder)}
@ -113,20 +127,20 @@ export default function InputListComponent({
onBlur={() => setFocusedIndex(null)}
/>
{focusedIndex !== index && !disabled && (
<div
className={cn(
"absolute h-6 w-16",
editNode ? "translate-x-[11rem]" : "translate-x-[11.1rem]",
)}
style={{
pointerEvents: "none",
background: GRADIENT_CLASS,
}}
aria-hidden="true"
/>
{value.length > 1 && (
<div className="absolute right-2 top-1/2 -translate-y-1/2">
<DeleteButtonInputList
index={index}
removeInput={(e) => removeInput(index, e)}
disabled={disabled}
editNode={editNode}
componentName={componentName || ""}
/>
</div>
)}
{/*
We will add this back in a future release
{!disabled && (
<DropdownMenuInputList
index={index}
@ -137,7 +151,7 @@ export default function InputListComponent({
removeInput={removeInput}
canDelete={value.length > 1}
/>
)}
)} */}
</div>
</div>
))}

View file

@ -998,7 +998,7 @@ export const GRADIENT_CLASS =
export const RECEIVING_INPUT_VALUE = "Receiving input";
export const ICON_STROKE_WIDTH = 1.25;
export const ICON_STROKE_WIDTH = 1.5;
export const DEFAULT_PLACEHOLDER = "Type something...";

View file

@ -1254,7 +1254,7 @@
}
.hit-area-icon {
@apply h-8 w-8 rounded-md;
@apply h-7 w-7 rounded-md;
}
.node-toolbar-buttons {
@ -1274,7 +1274,7 @@
}
.input-slider-text {
@apply absolute bottom-[4.2rem] right-3 w-14 cursor-text rounded-sm px-2 py-[1px] text-center hover:ring-[1px] hover:ring-slider-input-border;
@apply absolute right-3 w-14 -translate-y-3.5 cursor-text rounded-sm px-2 py-[1px] text-center hover:ring-[1px] hover:ring-slider-input-border;
}
.btn-add-input-list {

View file

@ -276,7 +276,7 @@
--sidebar-ring: 217.2 91.2% 59.8%;
--placeholder: 240 5% 64.9%;
--hard-zinc: 240 5.2% 33.9%;
--smooth-red: 0 93.3% 94.1%;
--smooth-red: 0 74.7% 15.5%;
--datatype-pink: 327.4 87.1% 81.8%;
--datatype-pink-foreground: 333.3 71.4% 50.6%;

View file

@ -54,20 +54,16 @@ test(
expect(false).toBeTruthy();
}
await page.getByTestId("input-list-dropdown-menu-0-edit").click();
await page.getByTestId("input-list-dropdown-menu-0-delete").click();
await page.getByTestId("input-list-delete-btn-edit_urls-0").click();
expect(
await page.getByTestId("input-list-dropdown-menu-2-edit").count(),
await page.getByTestId("input-list-delete-btn-edit_urls-2").count(),
).toBe(0);
await page.getByTestId("input-list-dropdown-menu-1-edit").click();
await page.getByTestId("input-list-dropdown-menu-1-delete").click();
await page.getByTestId("input-list-delete-btn-edit_urls-1").click();
expect(
await page.getByTestId("input-list-dropdown-menu-1-edit").count(),
await page.getByTestId("input-list-delete-btn-edit_urls-1").count(),
).toBe(0);
await page.getByText("Close").last().click();
@ -76,35 +72,32 @@ test(
await page.getByTestId("input-list-plus-btn_urls-0").click();
await page.getByTestId("input-list-plus-btn_urls-0").click();
expect(
await page.getByTestId("input-list-dropdown-menu-0-view").count(),
).toBe(1);
expect(await page.getByTestId("input-list-delete-btn_urls-0").count()).toBe(
1,
);
expect(
await page.getByTestId("input-list-dropdown-menu-1-view").count(),
).toBe(1);
expect(await page.getByTestId("input-list-delete-btn_urls-1").count()).toBe(
1,
);
expect(
await page.getByTestId("input-list-dropdown-menu-2-view").count(),
).toBe(1);
expect(await page.getByTestId("input-list-delete-btn_urls-2").count()).toBe(
1,
);
expect(
await page.getByTestId("input-list-dropdown-menu-3-view").count(),
).toBe(1);
expect(await page.getByTestId("input-list-delete-btn_urls-3").count()).toBe(
1,
);
expect(
await page.getByTestId("input-list-dropdown-menu-4-view").count(),
).toBe(0);
await page.getByTestId("input-list-dropdown-menu-0-view").click();
await page.getByTestId("input-list-dropdown-menu-0-duplicate").click();
expect(await page.getByTestId("input-list-delete-btn_urls-4").count()).toBe(
0,
);
expect(await page.getByTestId("inputlist_str_urls_0").inputValue()).toBe(
"test1 test1 test1 test1",
);
expect(await page.getByTestId("inputlist_str_urls_1").inputValue()).toBe(
"test1 test1 test1 test1",
"",
);
await page.getByTestId("edit-button-modal").click();
@ -115,22 +108,6 @@ test(
expect(
await page.getByTestId("inputlist_str_edit_urls_1").inputValue(),
).toBe("test1 test1 test1 test1");
await page.getByTestId("input-list-dropdown-menu-1-edit").click();
await page.getByTestId("input-list-dropdown-menu-1-duplicate").click();
expect(
await page.getByTestId("inputlist_str_edit_urls_0").inputValue(),
).toBe("test1 test1 test1 test1");
expect(
await page.getByTestId("inputlist_str_edit_urls_1").inputValue(),
).toBe("test1 test1 test1 test1");
expect(
await page.getByTestId("inputlist_str_edit_urls_2").inputValue(),
).toBe("test1 test1 test1 test1");
).toBe("");
},
);