Skip to content

Commit 2ff11f1

Browse files
committed
Task page search filter no longer debounces for better performance
1 parent 97a29ad commit 2ff11f1

2 files changed

Lines changed: 12 additions & 85 deletions

File tree

apps/webapp/app/hooks/useFuzzyFilter.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,15 @@ import { matchSorter } from "match-sorter";
2626
export function useFuzzyFilter<T extends Object>({
2727
items,
2828
keys,
29+
filterText: controlledFilterText,
2930
}: {
3031
items: T[];
3132
keys: (Extract<keyof T, string> | (string & {}))[];
33+
/** Optional controlled filter text. If provided, internal state is ignored. */
34+
filterText?: string;
3235
}) {
33-
const [filterText, setFilterText] = useState("");
36+
const [internalFilterText, setInternalFilterText] = useState("");
37+
const filterText = controlledFilterText ?? internalFilterText;
3438

3539
const filteredItems = useMemo<T[]>(() => {
3640
const filterTerms = filterText
@@ -43,7 +47,6 @@ export function useFuzzyFilter<T extends Object>({
4347
return items;
4448
}
4549

46-
// sort by the score of the first term
4750
return filterTerms.reduceRight(
4851
(results, term) =>
4952
matchSorter(results, term, {
@@ -55,7 +58,7 @@ export function useFuzzyFilter<T extends Object>({
5558

5659
return {
5760
filterText,
58-
setFilterText,
61+
setFilterText: setInternalFilterText,
5962
filteredItems,
6063
};
6164
}

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam._index/route.tsx

Lines changed: 6 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ import {
55
ChevronUpIcon,
66
ExclamationTriangleIcon,
77
LightBulbIcon,
8-
MagnifyingGlassIcon,
98
UserPlusIcon,
109
VideoCameraIcon,
11-
XMarkIcon,
1210
} from "@heroicons/react/20/solid";
1311
import { json, type MetaFunction } from "@remix-run/node";
1412
import { Link, useFetcher, useRevalidator } from "@remix-run/react";
@@ -38,7 +36,6 @@ import { Callout } from "~/components/primitives/Callout";
3836
import { formatDateTime } from "~/components/primitives/DateTime";
3937
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "~/components/primitives/Dialog";
4038
import { Header2, Header3 } from "~/components/primitives/Headers";
41-
import { Input } from "~/components/primitives/Input";
4239
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
4340
import { Paragraph } from "~/components/primitives/Paragraph";
4441
import { PopoverMenuItem } from "~/components/primitives/Popover";
@@ -50,6 +47,7 @@ import {
5047
ResizablePanelGroup,
5148
collapsibleHandleClassName,
5249
} from "~/components/primitives/Resizable";
50+
import { SearchInput } from "~/components/primitives/SearchInput";
5351
import { Spinner } from "~/components/primitives/Spinner";
5452
import { StepNumber } from "~/components/primitives/StepNumber";
5553
import {
@@ -62,7 +60,6 @@ import {
6260
TableHeaderCell,
6361
TableRow,
6462
} from "~/components/primitives/Table";
65-
import { ShortcutKey } from "~/components/primitives/ShortcutKey";
6663
import { SimpleTooltip } from "~/components/primitives/Tooltip";
6764
import TooltipPortal from "~/components/primitives/TooltipPortal";
6865
import { TaskFileName } from "~/components/runs/v3/TaskPath";
@@ -76,6 +73,7 @@ import { useEventSource } from "~/hooks/useEventSource";
7673
import { useFuzzyFilter } from "~/hooks/useFuzzyFilter";
7774
import { useOrganization } from "~/hooks/useOrganizations";
7875
import { useProject } from "~/hooks/useProject";
76+
import { useSearchParams } from "~/hooks/useSearchParam";
7977
import { findProjectBySlug } from "~/models/project.server";
8078
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
8179
import {
@@ -89,7 +87,6 @@ import {
8987
uiPreferencesStorage,
9088
} from "~/services/preferences/uiPreferences.server";
9189
import { requireUserId } from "~/services/session.server";
92-
import { motion } from "framer-motion";
9390
import { cn } from "~/utils/cn";
9491
import {
9592
docsPath,
@@ -177,9 +174,11 @@ export default function Page() {
177174
const environment = useEnvironment();
178175
const { tasks, activity, runningStats, durations, usefulLinksPreference } =
179176
useTypedLoaderData<typeof loader>();
180-
const { filterText, setFilterText, filteredItems } = useFuzzyFilter<TaskListItem>({
177+
const { value } = useSearchParams();
178+
const { filteredItems } = useFuzzyFilter<TaskListItem>({
181179
items: tasks,
182180
keys: ["slug", "filePath", "triggerSource"],
181+
filterText: value("search") ?? "",
183182
});
184183

185184
const hasTasks = tasks.length > 0;
@@ -245,12 +244,7 @@ export default function Page() {
245244
{tasks.length === 0 ? <UserHasNoTasks /> : null}
246245
<div className="max-h-full overflow-hidden">
247246
<div className="flex items-center justify-between gap-1 p-2">
248-
<AnimatedSearchField
249-
value={filterText}
250-
onChange={setFilterText}
251-
placeholder="Search tasks…"
252-
autoFocus
253-
/>
247+
<SearchInput placeholder="Search tasks…" autoFocus />
254248
{!showUsefulLinks && (
255249
<Button
256250
variant="secondary/small"
@@ -868,73 +862,3 @@ function FailedToLoadStats() {
868862
);
869863
}
870864

871-
function AnimatedSearchField({
872-
value,
873-
onChange,
874-
placeholder,
875-
autoFocus,
876-
}: {
877-
value: string;
878-
onChange: (value: string) => void;
879-
placeholder?: string;
880-
autoFocus?: boolean;
881-
}) {
882-
const [isFocused, setIsFocused] = useState(false);
883-
884-
return (
885-
<motion.div
886-
initial={{ width: "auto" }}
887-
animate={{ width: isFocused && value.length > 0 ? "24rem" : "auto" }}
888-
transition={{ type: "spring", stiffness: 300, damping: 30 }}
889-
className="relative h-6 min-w-52"
890-
>
891-
<Input
892-
type="text"
893-
variant="secondary-small"
894-
placeholder={placeholder}
895-
value={value}
896-
onChange={(e) => onChange(e.target.value)}
897-
fullWidth
898-
autoFocus={autoFocus}
899-
className={cn(isFocused && "placeholder:text-text-dimmed/70")}
900-
onFocus={() => setIsFocused(true)}
901-
onBlur={() => setIsFocused(false)}
902-
onKeyDown={(e) => {
903-
if (e.key === "Escape") {
904-
e.stopPropagation();
905-
onChange("");
906-
e.currentTarget.blur();
907-
}
908-
}}
909-
icon={<MagnifyingGlassIcon className="size-4 text-text-bright" />}
910-
accessory={
911-
value.length > 0 ? (
912-
<SimpleTooltip
913-
asChild
914-
button={
915-
<button
916-
type="button"
917-
onPointerDown={(e) => {
918-
e.preventDefault();
919-
onChange("");
920-
}}
921-
className="-mr-1 flex size-4.5 items-center justify-center rounded-[2px] border border-text-dimmed/40 text-text-dimmed transition hover:bg-charcoal-600 hover:text-text-bright"
922-
>
923-
<XMarkIcon className="size-3" />
924-
</button>
925-
}
926-
content={
927-
<div className="flex items-center gap-1">
928-
<span className="text-text-dimmed">Clear field</span>
929-
<ShortcutKey shortcut={{ key: "esc" }} variant="small" />
930-
</div>
931-
}
932-
className="px-2 py-1.5 text-xs"
933-
disableHoverableContent
934-
/>
935-
) : undefined
936-
}
937-
/>
938-
</motion.div>
939-
);
940-
}

0 commit comments

Comments
 (0)