Skip to content

Commit f83f4bf

Browse files
committed
feat: add search in add connection modal
1 parent 82d5ae7 commit f83f4bf

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";
@@ -32,6 +33,7 @@ import {
3233
Plugin,
3334
PlusLg,
3435
Send,
36+
XCircleFill,
3537
XLg,
3638
} from "react-bootstrap-icons";
3739
import { Link, useSearchParams } from "react-router";
@@ -43,6 +45,9 @@ import {
4345
CardHeader,
4446
CardText,
4547
Col,
48+
Input,
49+
InputGroup,
50+
Label,
4651
ListGroup,
4752
ListGroupItem,
4853
Modal,
@@ -457,16 +462,38 @@ function AddIntegrationModal({
457462
}: AddIntegrationModalProps) {
458463
const [showAllIntegrations, setShowAllIntegrations] = useState(false);
459464
const isListExpanded = isOpen && showAllIntegrations;
465+
const searchInputRef = useRef<HTMLInputElement | null>(null);
466+
const [userSearchInput, setUserSearchInput] = useState("");
467+
468+
const normalizedSearchInput = useMemo(
469+
() => userSearchInput.trim().toLowerCase(),
470+
[userSearchInput]
471+
);
472+
473+
const filteredProviders = useMemo(() => {
474+
if (!normalizedSearchInput) return providers;
475+
476+
return providers.filter(({ provider }) =>
477+
[provider.display_name, provider.url].some((value) =>
478+
value.toLowerCase().includes(normalizedSearchInput)
479+
)
480+
);
481+
}, [normalizedSearchInput, providers]);
460482

461483
const visibleProviders = useMemo(() => {
462-
if (isListExpanded) return providers;
463-
return providers.slice(0, DEFAULT_MODAL_PROVIDERS_COUNT);
464-
}, [isListExpanded, providers]);
484+
if (isListExpanded) return filteredProviders;
485+
return filteredProviders.slice(0, DEFAULT_MODAL_PROVIDERS_COUNT);
486+
}, [filteredProviders, isListExpanded]);
465487

466488
const toggleShowAllIntegrations = useCallback(() => {
467489
setShowAllIntegrations((open) => !open);
468490
}, []);
469491

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

0 commit comments

Comments
 (0)