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:
Deon Sanchez 2025-04-01 12:28:06 -06:00 committed by GitHub
commit 6dac50a3fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 246 additions and 52 deletions

View file

@ -709,6 +709,7 @@
},
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
"version": "1.3.0",
"extraneous": true,
"inBundle": true,
"license": "MIT",
"engines": {

View file

@ -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}
/>
))
) : (

View file

@ -230,8 +230,8 @@ const ConnectionComponent = ({
<ListSelectionComponent
open={open}
onSelection={handleSelection}
onClose={handleCloseListSelectionDialog}
onSelection={handleSelection}
searchCategories={searchCategory}
setSelectedList={setSelectedItem}
selectedList={selectedItem}

View file

@ -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}

View file

@ -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",