feat: composio clean up 🧹 (#7371)
* feat: enhance animations and improve ListSelectionComponent functionality - Added overlay and content animations to tailwind.config.mjs for smoother transitions. - Updated ListSelectionComponent to reset search input when the dialog opens and improved item display styling. - Refactored ConnectionComponent to utilize the new onSelection prop in ListSelectionComponent for better selection handling. * fix: refine animation timings and enhance ListSelectionComponent layout - Adjusted animation durations in tailwind.config.mjs for smoother transitions. - Updated ListSelectionComponent to increase maximum height for improved content display. * feat: enhance ListSelectionComponent with hover functionality and keyboard navigation - Added mouse enter and leave handlers to ListItem for improved user interaction. - Implemented keyboard navigation to select items using the Enter key when an item is hovered. - Updated styling for ListItem to enhance visual feedback during hover states. * feat: enhance ListItem with hover state and selection feedback - Added hover state management to ListItem, allowing visual feedback when an item is hovered. - Updated the rendering logic to display a "Select" label and an icon when the item is hovered. - Improved the selection indicator for better user experience. * feat: enhance ListSelectionComponent with keyboard navigation and focus management - Added keyboard navigation to allow users to select items using the Arrow keys and Enter key. - Implemented focus management to highlight the currently focused item in the list. - Updated the component to reset focus and search input when the dialog opens or the filtered list changes. - Improved item rendering to visually indicate focus state. * feat: enhance ListSelectionComponent with improved keyboard navigation and hover management - Added state management for keyboard navigation to improve user experience when selecting items. - Implemented hover state clearing during keyboard navigation to prevent visual conflicts. - Updated ListItem to conditionally apply hover styles based on keyboard navigation state. - Enhanced focus management to reset when the filtered list changes or when the dialog opens. * fix: refine animation timings and update ListSelectionComponent layout - Adjusted clipPath values in tailwind.config.mjs for improved overlay animations. - Increased animation durations for overlay and content transitions to enhance smoothness. - Removed unnecessary padding in ListSelectionComponent for a cleaner layout. * refactor: update ListSelectionComponent styles for improved visual consistency - Changed text color and background color classes in ListItem to enhance readability and maintain design consistency. - Updated icon color to align with the new text styling for better visual integration. * [autofix.ci] apply automated fixes * fix: improve ListSelectionComponent behavior and cleanup event listeners * fix: adjust minimum height of ListSelectionComponent dialog for better usability --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
7a87880e49
commit
6dac50a3fc
5 changed files with 246 additions and 52 deletions
1
src/frontend/package-lock.json
generated
1
src/frontend/package-lock.json
generated
|
|
@ -709,6 +709,7 @@
|
|||
},
|
||||
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
|
||||
"version": "1.3.0",
|
||||
"extraneous": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
||||
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||
import SearchBarComponent from "@/components/core/parameterRenderComponent/components/searchBarComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog-with-no-close";
|
||||
import { cn } from "@/utils/utils";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
// Update interface with better types
|
||||
interface ListSelectionComponentProps {
|
||||
open: boolean;
|
||||
options: any[];
|
||||
onClose: () => void;
|
||||
options: any[];
|
||||
setSelectedList: (action: any[]) => void;
|
||||
selectedList: any[];
|
||||
searchCategories?: string[];
|
||||
|
|
@ -22,41 +23,99 @@ const ListItem = ({
|
|||
isSelected,
|
||||
onClick,
|
||||
className,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
isFocused,
|
||||
isKeyboardNavActive,
|
||||
}: {
|
||||
item: any;
|
||||
isSelected: boolean;
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
}) => (
|
||||
<Button
|
||||
key={item.id}
|
||||
unstyled
|
||||
size="sm"
|
||||
className={cn("w-full rounded-md py-3 pl-3 pr-3 hover:bg-muted", className)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{item.icon && (
|
||||
<ForwardedIconComponent name={item.icon} className="h-5 w-5" />
|
||||
onMouseEnter: () => void;
|
||||
onMouseLeave: () => void;
|
||||
isFocused: boolean;
|
||||
isKeyboardNavActive: boolean;
|
||||
}) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const itemRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
// Clear hover state when keyboard navigation is active
|
||||
useEffect(() => {
|
||||
if (isKeyboardNavActive) {
|
||||
setIsHovered(false);
|
||||
}
|
||||
}, [isKeyboardNavActive]);
|
||||
|
||||
// Scroll into view when focused by keyboard
|
||||
useEffect(() => {
|
||||
if (isFocused && itemRef.current) {
|
||||
itemRef.current.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
}, [isFocused]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={itemRef}
|
||||
key={item.id}
|
||||
unstyled
|
||||
size="sm"
|
||||
className={cn(
|
||||
"group w-full rounded-md py-3 pl-3 pr-3",
|
||||
!isKeyboardNavActive && "hover:bg-muted", // Only apply hover styles when not in keyboard nav
|
||||
isFocused && "bg-muted",
|
||||
className,
|
||||
)}
|
||||
<span className="truncate font-semibold">{item.name}</span>
|
||||
{"metaData" in item && item.metaData && (
|
||||
<span className="text-gray-500">{item.metaData}</span>
|
||||
)}
|
||||
{isSelected ? (
|
||||
<ForwardedIconComponent
|
||||
name="check"
|
||||
className={cn(
|
||||
"ml-auto flex h-4 w-4",
|
||||
item.link === "validated" && "text-green-500",
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<span className="ml-auto flex h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
onClick={onClick}
|
||||
onMouseEnter={() => {
|
||||
if (!isKeyboardNavActive) {
|
||||
setIsHovered(true);
|
||||
onMouseEnter();
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setIsHovered(false);
|
||||
onMouseLeave();
|
||||
}}
|
||||
// Disable pointer events during keyboard navigation
|
||||
style={{ pointerEvents: isKeyboardNavActive ? "none" : "auto" }}
|
||||
>
|
||||
<div className="flex w-full items-center gap-2">
|
||||
{item.icon && (
|
||||
<ForwardedIconComponent name={item.icon} className="h-5 w-5" />
|
||||
)}
|
||||
<div className="truncate text-sm">{item.name}</div>
|
||||
{"metaData" in item && item.metaData && (
|
||||
<div className="text-gray-500">{item.metaData}</div>
|
||||
)}
|
||||
{isHovered || isFocused ? (
|
||||
<div className="ml-auto flex items-center justify-start rounded-md">
|
||||
<div className="flex items-center pr-1.5 text-sm text-muted-foreground">
|
||||
Select
|
||||
</div>
|
||||
<div className="flex items-center justify-center rounded-md bg-border p-1">
|
||||
<ForwardedIconComponent
|
||||
name="corner-down-left"
|
||||
className="h-3 w-3 text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// Always show the check icon when selected, regardless of hover/focus state
|
||||
isSelected && (
|
||||
<ForwardedIconComponent
|
||||
name="check"
|
||||
className={cn(
|
||||
"ml-auto flex h-4 w-4",
|
||||
item.link === "validated" && "text-green-500",
|
||||
)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const ListSelectionComponent = ({
|
||||
open,
|
||||
|
|
@ -69,6 +128,10 @@ const ListSelectionComponent = ({
|
|||
limit = 1,
|
||||
}: ListSelectionComponentProps) => {
|
||||
const [search, setSearch] = useState("");
|
||||
const [hoveredItem, setHoveredItem] = useState<any | null>(null);
|
||||
const [focusedIndex, setFocusedIndex] = useState<number>(-1);
|
||||
const [isKeyboardNavActive, setIsKeyboardNavActive] = useState(false);
|
||||
const listContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const filteredList = useMemo(() => {
|
||||
if (!search.trim()) {
|
||||
|
|
@ -110,19 +173,96 @@ const ListSelectionComponent = ({
|
|||
},
|
||||
]);
|
||||
onClose();
|
||||
setSearch("");
|
||||
}
|
||||
},
|
||||
[selectedList, setSelectedList, onClose, limit],
|
||||
[selectedList, setSelectedList, limit, onClose],
|
||||
);
|
||||
|
||||
const handleCloseDialog = useCallback(() => {
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
// Reset focus state when filtered list changes
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
if (filteredList.length > 0) {
|
||||
setFocusedIndex(0);
|
||||
setHoveredItem(filteredList[0]);
|
||||
} else {
|
||||
setFocusedIndex(-1);
|
||||
setHoveredItem(null);
|
||||
}
|
||||
}
|
||||
}, [open, filteredList.length]);
|
||||
|
||||
// Reset search when dialog opens
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setSearch("");
|
||||
setIsKeyboardNavActive(false);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
// Handle keyboard navigation
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (filteredList.length === 0) return;
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowDown":
|
||||
e.preventDefault();
|
||||
setIsKeyboardNavActive(true);
|
||||
setFocusedIndex((prev) => {
|
||||
const newIndex = prev < filteredList.length - 1 ? prev + 1 : 0;
|
||||
setHoveredItem(filteredList[newIndex]);
|
||||
return newIndex;
|
||||
});
|
||||
break;
|
||||
case "ArrowUp":
|
||||
e.preventDefault();
|
||||
setIsKeyboardNavActive(true);
|
||||
setFocusedIndex((prev) => {
|
||||
const newIndex = prev > 0 ? prev - 1 : filteredList.length - 1;
|
||||
setHoveredItem(filteredList[newIndex]);
|
||||
return newIndex;
|
||||
});
|
||||
break;
|
||||
case "Enter":
|
||||
if (hoveredItem) {
|
||||
e.preventDefault();
|
||||
handleSelectAction(hoveredItem);
|
||||
if (onSelection) {
|
||||
onSelection(hoveredItem);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "Escape":
|
||||
e.preventDefault();
|
||||
onClose();
|
||||
break;
|
||||
}
|
||||
},
|
||||
[filteredList, hoveredItem, handleSelectAction, onSelection, onClose],
|
||||
);
|
||||
|
||||
// Detect mouse movement to switch from keyboard to mouse navigation
|
||||
useEffect(() => {
|
||||
const handleMouseMove = () => {
|
||||
if (isKeyboardNavActive) {
|
||||
setIsKeyboardNavActive(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (open) {
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", handleMouseMove);
|
||||
};
|
||||
}
|
||||
}, [open, isKeyboardNavActive]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleCloseDialog}>
|
||||
<DialogContent className="flex !w-auto w-fit min-w-[20vw] max-w-[50vw] flex-col">
|
||||
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
|
||||
<DialogContent
|
||||
className="flex max-h-[65vh] min-h-[15vh] flex-col rounded-xl"
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<SearchBarComponent
|
||||
searchCategories={searchCategories}
|
||||
|
|
@ -133,13 +273,16 @@ const ListSelectionComponent = ({
|
|||
unstyled
|
||||
size="icon"
|
||||
className="ml-auto h-[38px]"
|
||||
onClick={handleCloseDialog}
|
||||
onClick={onClose}
|
||||
>
|
||||
<ForwardedIconComponent name="x" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex max-h-[80vh] flex-col gap-1 overflow-y-auto">
|
||||
<div
|
||||
ref={listContainerRef}
|
||||
className="flex flex-col gap-1 overflow-y-auto"
|
||||
>
|
||||
{filteredList.length > 0 ? (
|
||||
filteredList.map((item, index) => (
|
||||
<ListItem
|
||||
|
|
@ -154,6 +297,17 @@ const ListSelectionComponent = ({
|
|||
handleSelectAction(item);
|
||||
onSelection?.(item);
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setHoveredItem(item);
|
||||
setFocusedIndex(index);
|
||||
setIsKeyboardNavActive(false);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHoveredItem(null);
|
||||
// Don't reset focused index on mouse leave
|
||||
}}
|
||||
isFocused={focusedIndex === index}
|
||||
isKeyboardNavActive={isKeyboardNavActive}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -230,8 +230,8 @@ const ConnectionComponent = ({
|
|||
|
||||
<ListSelectionComponent
|
||||
open={open}
|
||||
onSelection={handleSelection}
|
||||
onClose={handleCloseListSelectionDialog}
|
||||
onSelection={handleSelection}
|
||||
searchCategories={searchCategory}
|
||||
setSelectedList={setSelectedItem}
|
||||
selectedList={selectedItem}
|
||||
|
|
|
|||
|
|
@ -6,15 +6,8 @@ const Dialog = DialogPrimitive.Root;
|
|||
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
const DialogPortal = ({
|
||||
children,
|
||||
...props
|
||||
}: DialogPrimitive.DialogPortalProps) => (
|
||||
<DialogPrimitive.Portal {...props}>
|
||||
<div className="nopan nodelete nodrag noflow fixed inset-0 z-50 flex items-start justify-center sm:items-center">
|
||||
{children}
|
||||
</div>
|
||||
</DialogPrimitive.Portal>
|
||||
const DialogPortal = ({ ...props }: DialogPrimitive.DialogPortalProps) => (
|
||||
<DialogPrimitive.Portal {...props} />
|
||||
);
|
||||
DialogPortal.displayName = DialogPrimitive.Portal.displayName;
|
||||
|
||||
|
|
@ -25,7 +18,7 @@ const DialogOverlay = React.forwardRef<
|
|||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"nopan nodelete nodrag noflow fixed inset-0 bottom-0 left-0 right-0 top-0 z-50 overflow-auto bg-black/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-overlayHide data-[state=open]:animate-overlayShow",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -42,7 +35,7 @@ const DialogContent = React.forwardRef<
|
|||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed z-50 flex w-full max-w-lg flex-col gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] sm:rounded-lg md:w-full",
|
||||
"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",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,47 @@ const config = {
|
|||
"2xl": "1400px",
|
||||
},
|
||||
keyframes: {
|
||||
// Overlay animations
|
||||
overlayShow: {
|
||||
from: { opacity: 0 },
|
||||
to: { opacity: 1 },
|
||||
},
|
||||
overlayHide: {
|
||||
from: { opacity: 1 },
|
||||
to: { opacity: 0 },
|
||||
},
|
||||
|
||||
// Content animations - now including both scale and clip in one animation
|
||||
contentShow: {
|
||||
from: {
|
||||
opacity: 0,
|
||||
transform: "translate(-50%, -50%) scale(0.95)",
|
||||
clipPath: "inset(50% 0)",
|
||||
boxShadow: "0 4px 8px -2px rgba(0, 0, 0, 0.1)", // Smaller shadow
|
||||
},
|
||||
to: {
|
||||
opacity: 1,
|
||||
transform: "translate(-50%, -50%) scale(1)",
|
||||
clipPath: "inset(0% 0)",
|
||||
boxShadow:
|
||||
"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
|
||||
},
|
||||
},
|
||||
contentHide: {
|
||||
from: {
|
||||
opacity: 1,
|
||||
transform: "translate(-50%, -50%) scale(1)",
|
||||
clipPath: "inset(0% 0)",
|
||||
boxShadow:
|
||||
"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
|
||||
},
|
||||
to: {
|
||||
opacity: 0,
|
||||
transform: "translate(-50%, -50%) scale(0.95)",
|
||||
clipPath: "inset(50% 0)",
|
||||
boxShadow: "0 4px 8px -2px rgba(0, 0, 0, 0.1)",
|
||||
},
|
||||
},
|
||||
wiggle: {
|
||||
"0%, 100%": { transform: "scale(100%)" },
|
||||
"50%": { transform: "scale(120%)" },
|
||||
|
|
@ -52,6 +93,11 @@ const config = {
|
|||
},
|
||||
},
|
||||
animation: {
|
||||
// Animation definitions
|
||||
overlayShow: "overlayShow 400ms cubic-bezier(0.16, 1, 0.3, 1)",
|
||||
overlayHide: "overlayHide 500ms cubic-bezier(0.16, 1, 0.3, 1)",
|
||||
contentShow: "contentShow 400ms cubic-bezier(0.16, 1, 0.3, 1)",
|
||||
contentHide: "contentHide 500ms cubic-bezier(0.16, 1, 0.3, 1)",
|
||||
wiggle: "wiggle 150ms ease-in-out 1",
|
||||
"slow-wiggle": "wiggle 500ms ease-in-out 1",
|
||||
"border-beam": "border-beam calc(var(--duration)*1s) infinite linear",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue