Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -26,21 +26,28 @@ export const ConnectionCard: React.FC<Props> = ({

return (
<div
className="group relative rounded-xl border border-border/70 bg-card/50 hover:bg-card hover:border-border hover:shadow-sm transition-all duration-150 cursor-pointer overflow-hidden"
className="group relative rounded-xl border border-border/50 hover:border-border/80 hover:shadow-sm transition-all duration-150 cursor-pointer overflow-hidden bg-card/40 hover:bg-card/70"
onClick={() => onConnect(connection)}
>
{/* Color strip */}
{/* Subtle color tint overlay */}
{connection.color && (
<div
className="absolute inset-0 opacity-[0.05] pointer-events-none"
style={{ backgroundColor: connection.color }}
/>
)}

{/* Color accent strip */}
{connection.color && (
<div
className="absolute left-0 top-0 bottom-0 w-[3px] rounded-l-xl"
style={{ backgroundColor: connection.color }}
/>
)}

<div className={cn("flex items-center gap-3 px-3.5 py-3", connection.color && "pl-5")}>
{/* Color dot (when no strip, still show dot) */}
<div className={cn("flex items-center gap-2.5 px-3.5 py-2.5", connection.color && "pl-5")}>
{!connection.color && (
<div className="w-2 h-2 rounded-full bg-border shrink-0" />
<div className="w-1.5 h-1.5 rounded-full bg-border/60 shrink-0" />
)}

{/* Info */}
Expand All @@ -50,24 +57,24 @@ export const ConnectionCard: React.FC<Props> = ({
{connection.name}
</span>
{connection.isFavorite && (
<Star className="h-3 w-3 fill-warning text-warning shrink-0" />
<Star className="h-2.5 w-2.5 fill-warning text-warning shrink-0" />
)}
{connection.label && (
<span className="text-[10px] px-1.5 py-px bg-primary/8 text-primary/70 rounded font-medium shrink-0 border border-primary/10">
<span className="text-[10px] px-1.5 py-px bg-primary/8 text-primary/70 rounded-full font-medium shrink-0 border border-primary/10">
{connection.label}
</span>
)}
</div>
{connection.lastUsedAt && (
<span className="text-[11px] text-muted-foreground/60 flex items-center gap-1">
<span className="text-[10px] text-muted-foreground/40 flex items-center gap-1">
<Clock className="h-2.5 w-2.5" />
{formatDistanceToNow(new Date(connection.lastUsedAt), { addSuffix: true })}
</span>
)}
</div>

{/* Actions — visible on hover */}
<div className="flex items-center gap-0.5 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
{/* Hover actions */}
<div className="flex items-center gap-0.5 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity duration-150">
<IconButton
icon={
<FavIcon
Expand All @@ -79,8 +86,8 @@ export const ConnectionCard: React.FC<Props> = ({
}
label={connection.isFavorite ? "Unfavorite" : "Favorite"}
className={cn(
"rounded-lg",
!connection.isFavorite && "text-muted-foreground/50 hover:text-warning",
"rounded-md",
!connection.isFavorite && "text-muted-foreground/40 hover:text-warning",
)}
onClick={(e) => {
e.stopPropagation();
Expand All @@ -90,7 +97,7 @@ export const ConnectionCard: React.FC<Props> = ({
<IconButton
icon={<Pencil className="h-3.5 w-3.5" />}
label="Edit"
className="rounded-lg text-muted-foreground/50 hover:text-foreground"
className="rounded-md text-muted-foreground/40 hover:text-foreground"
onClick={(e) => {
e.stopPropagation();
onEdit(connection);
Expand All @@ -100,7 +107,7 @@ export const ConnectionCard: React.FC<Props> = ({
variant="danger"
icon={<Trash2 className="h-3.5 w-3.5" />}
label="Delete"
className="rounded-lg"
className="rounded-md"
onClick={(e) => {
e.stopPropagation();
onDelete(connection.id);
Expand All @@ -113,7 +120,7 @@ export const ConnectionCard: React.FC<Props> = ({
size="sm"
icon={<Plug2 className="h-3 w-3" />}
loading={isConnecting}
className="rounded-lg shrink-0 ml-1"
className="rounded-lg shrink-0 text-xs h-7 px-2.5"
onClick={(e) => {
e.stopPropagation();
onConnect(connection);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
"use client";

import type { SavedConnection } from "@compooss/types";
import { IconButton, Input, cn } from "@compooss/ui";
import { ArrowDownAZ, Clock, Search, Star, X } from "lucide-react";
import { cn } from "@compooss/ui";
import { Clock, Search, Star, X } from "lucide-react";
import { useMemo, useState } from "react";
import { ConnectionCard } from "./connection-card.component";

type SortKey = "name" | "lastUsedAt" | "createdAt";

type Props = {
connections: SavedConnection[];
onConnect: (connection: SavedConnection) => void;
Expand All @@ -27,7 +25,6 @@ export const ConnectionList: React.FC<Props> = ({
}) => {
const [search, setSearch] = useState("");
const [showFavoritesOnly, setShowFavoritesOnly] = useState(false);
const [sortBy, setSortBy] = useState<SortKey>("lastUsedAt");

const filtered = useMemo(() => {
let result = connections;
Expand All @@ -46,22 +43,11 @@ export const ConnectionList: React.FC<Props> = ({
}

return [...result].sort((a, b) => {
if (sortBy === "name") return a.name.localeCompare(b.name);
if (sortBy === "lastUsedAt") {
const aTime = a.lastUsedAt
? new Date(a.lastUsedAt).getTime()
: 0;
const bTime = b.lastUsedAt
? new Date(b.lastUsedAt).getTime()
: 0;
return bTime - aTime;
}
return (
new Date(b.createdAt).getTime() -
new Date(a.createdAt).getTime()
);
const aTime = a.lastUsedAt ? new Date(a.lastUsedAt).getTime() : 0;
const bTime = b.lastUsedAt ? new Date(b.lastUsedAt).getTime() : 0;
return bTime - aTime;
});
}, [connections, search, showFavoritesOnly, sortBy]);
}, [connections, search, showFavoritesOnly]);

const favorites = useMemo(
() => filtered.filter((c) => c.isFavorite),
Expand Down Expand Up @@ -91,13 +77,15 @@ export const ConnectionList: React.FC<Props> = ({
) => {
if (items.length === 0) return null;
return (
<div className="mb-5">
<h3 className="text-[11px] font-semibold text-muted-foreground/70 uppercase tracking-wider mb-2 flex items-center gap-1.5 px-1">
{icon}
{title}
<span className="text-muted-foreground/40 ml-auto tabular-nums">{items.length}</span>
</h3>
<div className="space-y-2">
<div className="mb-4">
<div className="flex items-center gap-1.5 mb-1.5 px-0.5">
<span className="text-muted-foreground/50">{icon}</span>
<span className="text-[10px] font-semibold text-muted-foreground/60 uppercase tracking-wider">
{title}
</span>
<span className="text-[10px] text-muted-foreground/30 tabular-nums ml-auto">{items.length}</span>
</div>
<div className="space-y-1.5">
{items.map((conn) => (
<ConnectionCard
key={conn.id}
Expand All @@ -116,55 +104,46 @@ export const ConnectionList: React.FC<Props> = ({

return (
<div className="flex flex-col h-full">
{/* Search & Filters */}
<div className="flex items-center gap-2 mb-4">
<div className="flex items-center gap-2 bg-secondary/80 rounded-lg px-3 py-2 flex-1 border border-transparent focus-within:border-primary/20 transition-colors">
<Search className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
<Input
variant="search"
{/* Search + favorites toggle */}
<div className="px-3 pt-3 pb-3 flex items-center gap-2">
<div className="relative flex items-center flex-1">
<Search className="absolute left-3 h-3.5 w-3.5 text-muted-foreground/40 pointer-events-none" />
<input
type="text"
placeholder="Search..."
placeholder="Search connections..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full h-8 bg-secondary/50 text-sm text-foreground placeholder:text-muted-foreground/40 border border-transparent focus:border-primary/20 focus:outline-none rounded-lg pl-9 pr-8 transition-colors"
/>
{search && (
<IconButton
variant="ghost"
size="sm"
icon={<X className="h-3 w-3" />}
label="Clear search"
<button
type="button"
className="absolute right-2.5 text-muted-foreground/40 hover:text-foreground transition-colors"
onClick={() => setSearch("")}
/>
>
<X className="h-3.5 w-3.5" />
</button>
)}
</div>

<button
type="button"
onClick={() => setShowFavoritesOnly(!showFavoritesOnly)}
title={showFavoritesOnly ? "Show all" : "Favorites only"}
className={cn(
"p-2 rounded-lg transition-all border",
"h-8 w-8 flex items-center justify-center rounded-lg transition-all shrink-0",
showFavoritesOnly
? "bg-warning/10 text-warning border-warning/20"
: "text-muted-foreground hover:text-foreground hover:bg-secondary border-transparent",
? "bg-warning/10 text-warning"
: "text-muted-foreground/40 hover:text-muted-foreground hover:bg-secondary",
)}
title="Filter favorites"
>
<Star className={cn("h-3.5 w-3.5", showFavoritesOnly && "fill-warning")} />
</button>

<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value as SortKey)}
className="text-[11px] bg-secondary/80 text-foreground border border-transparent rounded-lg px-2.5 py-2 outline-hidden cursor-pointer hover:bg-secondary transition-colors"
>
<option value="lastUsedAt">Recent</option>
<option value="name">Name</option>
<option value="createdAt">Created</option>
</select>
</div>

<div className="mx-3 border-t border-border/40 mb-0" />

{/* Connection list */}
<div className="flex-1 overflow-y-auto scrollbar-thin">
<div className="flex-1 overflow-y-auto scrollbar-thin px-3 py-3">
{filtered.length === 0 ? (
<div className="text-center py-12 text-muted-foreground">
<div className="w-10 h-10 rounded-xl bg-secondary flex items-center justify-center mx-auto mb-3">
Expand All @@ -178,7 +157,7 @@ export const ConnectionList: React.FC<Props> = ({
</p>
</div>
) : showFavoritesOnly ? (
<div className="space-y-2">
<div className="space-y-1.5">
{filtered.map((conn) => (
<ConnectionCard
key={conn.id}
Expand All @@ -203,11 +182,7 @@ export const ConnectionList: React.FC<Props> = ({
recent,
<Clock className="h-3 w-3" />,
)}
{renderSection(
"All Connections",
rest,
<ArrowDownAZ className="h-3 w-3" />,
)}
{renderSection("All Connections", rest, null)}
</>
)}
</div>
Expand Down
Loading
Loading