Skip to content

Commit 1090d01

Browse files
committed
feat: add search in add connection modal
1 parent 286b9f8 commit 1090d01

1 file changed

Lines changed: 104 additions & 44 deletions

File tree

client/src/features/connectedServices/ConnectedServicesPage.tsx

Lines changed: 104 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
useContext,
2424
useEffect,
2525
useMemo,
26+
useRef,
2627
useState,
2728
type ReactNode,
2829
} from "react";
@@ -33,6 +34,7 @@ import {
3334
Plugin,
3435
PlusLg,
3536
Send,
37+
XCircleFill,
3638
XLg,
3739
} from "react-bootstrap-icons";
3840
import { Link, useSearchParams } from "react-router";
@@ -44,6 +46,9 @@ import {
4446
CardHeader,
4547
CardText,
4648
Col,
49+
Input,
50+
InputGroup,
51+
Label,
4752
ListGroup,
4853
ListGroupItem,
4954
Modal,
@@ -465,16 +470,38 @@ function AddIntegrationModal({
465470
}: AddIntegrationModalProps) {
466471
const [showAllIntegrations, setShowAllIntegrations] = useState(false);
467472
const isListExpanded = isOpen && showAllIntegrations;
473+
const searchInputRef = useRef<HTMLInputElement | null>(null);
474+
const [userSearchInput, setUserSearchInput] = useState("");
475+
476+
const normalizedSearchInput = useMemo(
477+
() => userSearchInput.trim().toLowerCase(),
478+
[userSearchInput]
479+
);
480+
481+
const filteredProviders = useMemo(() => {
482+
if (!normalizedSearchInput) return providers;
483+
484+
return providers.filter(({ provider }) =>
485+
[provider.display_name, provider.url].some((value) =>
486+
value.toLowerCase().includes(normalizedSearchInput)
487+
)
488+
);
489+
}, [normalizedSearchInput, providers]);
468490

469491
const visibleProviders = useMemo(() => {
470-
if (isListExpanded) return providers;
471-
return providers.slice(0, DEFAULT_MODAL_PROVIDERS_COUNT);
472-
}, [isListExpanded, providers]);
492+
if (isListExpanded) return filteredProviders;
493+
return filteredProviders.slice(0, DEFAULT_MODAL_PROVIDERS_COUNT);
494+
}, [filteredProviders, isListExpanded]);
473495

474496
const handleClosed = useCallback(() => {
475497
setShowAllIntegrations(false);
476498
}, []);
477499

500+
const onResetSearch = useCallback(() => {
501+
setUserSearchInput("");
502+
searchInputRef.current?.focus();
503+
}, []);
504+
478505
return (
479506
<Modal
480507
centered
@@ -495,50 +522,83 @@ function AddIntegrationModal({
495522
) : (
496523
<>
497524
<p>Add a new code, data, or compute integration.</p>
498-
<ListGroup
499-
className={cx(
500-
"bg-white",
501-
"rounded-3",
502-
"border",
503-
"border-rk-green"
504-
)}
505-
>
506-
{visibleProviders.map(({ provider, connection }) => (
507-
<ListGroupItem
508-
key={provider.id}
509-
action={true}
510-
data-cy="provider-item"
525+
<div className="mb-4">
526+
<Label for="add-integration-search-input">Search</Label>
527+
<InputGroup>
528+
<Input
529+
className="lg"
530+
data-cy="add-integration-search-input"
531+
id="add-integration-search-input"
532+
innerRef={searchInputRef}
533+
placeholder="Search by name or url"
534+
type="text"
535+
value={userSearchInput}
536+
onChange={(e) => setUserSearchInput(e.target.value)}
537+
/>
538+
<Button
539+
color="outline-secondary"
540+
className="border-secondary-subtle"
541+
data-cy="search-clear-button"
542+
onClick={onResetSearch}
543+
id="search-button"
544+
type="button"
511545
>
512-
<div className={cx("d-flex", "align-items-center", "gap-3")}>
513-
<div className={cx("flex-grow-1")}>
514-
<ProviderRowHeader provider={provider} />
546+
<XCircleFill className={cx("bi")} />
547+
</Button>
548+
</InputGroup>
549+
</div>
550+
{normalizedSearchInput && filteredProviders.length === 0 ? (
551+
<span className={cx("small", "text-muted")}>
552+
No integrations found for &quot;{userSearchInput.trim()}&quot;.
553+
</span>
554+
) : (
555+
<ListGroup
556+
className={cx(
557+
"bg-white",
558+
"rounded-3",
559+
"border",
560+
"border-rk-green"
561+
)}
562+
>
563+
{visibleProviders.map(({ provider, connection }) => (
564+
<ListGroupItem
565+
key={provider.id}
566+
action={true}
567+
data-cy="provider-item"
568+
>
569+
<div
570+
className={cx("d-flex", "align-items-center", "gap-3")}
571+
>
572+
<div className={cx("flex-grow-1")}>
573+
<ProviderRowHeader provider={provider} />
574+
</div>
575+
<ConnectButton
576+
provider={provider}
577+
connectionStatus={connection?.status}
578+
onConnectStart={onToggle}
579+
/>
515580
</div>
516-
<ConnectButton
517-
provider={provider}
518-
connectionStatus={connection?.status}
519-
onConnectStart={onToggle}
581+
</ListGroupItem>
582+
))}
583+
{filteredProviders.length > DEFAULT_MODAL_PROVIDERS_COUNT && (
584+
<button
585+
onClick={() => setShowAllIntegrations(!showAllIntegrations)}
586+
className={cx(
587+
"text-primary",
588+
"list-group-item",
589+
"text-start",
590+
"text-decoration-underline"
591+
)}
592+
>
593+
{!isListExpanded ? "See all integrations" : "Show less "}
594+
<ChevronFlippedIcon
595+
className="ms-1"
596+
flipped={showAllIntegrations}
520597
/>
521-
</div>
522-
</ListGroupItem>
523-
))}
524-
{providers.length > DEFAULT_MODAL_PROVIDERS_COUNT && (
525-
<button
526-
onClick={() => setShowAllIntegrations(!showAllIntegrations)}
527-
className={cx(
528-
"text-primary",
529-
"list-group-item",
530-
"text-start",
531-
"text-decoration-underline"
532-
)}
533-
>
534-
{!isListExpanded ? "See all integrations" : "Show less "}
535-
<ChevronFlippedIcon
536-
className="ms-1"
537-
flipped={showAllIntegrations}
538-
/>
539-
</button>
540-
)}
541-
</ListGroup>
598+
</button>
599+
)}
600+
</ListGroup>
601+
)}
542602
</>
543603
)}
544604
</ModalBody>

0 commit comments

Comments
 (0)