Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[UI v2] feat: Updates combobox filtering logic for automations
Browse files Browse the repository at this point in the history
devinvillarosa committed Jan 16, 2025

Unverified

This user has not yet uploaded their public signing key.
1 parent f3870b9 commit 65bb8cd
Showing 4 changed files with 100 additions and 79 deletions.
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ import {
} from "@/components/ui/form";
import { Skeleton } from "@/components/ui/skeleton";
import { useQuery } from "@tanstack/react-query";
import { useDeferredValue, useMemo, useState } from "react";
import { useFormContext } from "react-hook-form";

const INFER_AUTOMATION = {
@@ -44,37 +45,35 @@ const getButtonLabel = (
return INFER_AUTOMATION.name;
}
const automation = data?.find((automation) => automation.id === fieldValue);
if (automation?.name) {
if (automation) {
return automation.name;
}
return undefined;
};

/** Because ShadCN only filters by `value` and not by a specific field, we need to write custom logic to filter objects by id */
const filterAutomations = (
value: string | null,
search: string,
data: Array<Automation> | undefined,
) => {
const searchTerm = search.toLowerCase();
const automation = data?.find((automation) => automation.id === value);
if (!automation) {
return 0;
}
const automationName = automation.name.toLowerCase();
if (automationName.includes(searchTerm)) {
return 1;
}
return 0;
};

export const AutomationsSelectStateFields = ({
action,
index,
}: AutomationsSelectStateFieldsProps) => {
const [search, setSearch] = useState("");
const form = useFormContext<AutomationWizardSchema>();
const { data, isSuccess } = useQuery(buildListAutomationsQuery());

// nb: because automations API does not have filtering _like by name, do client-side filtering
const deferredSearch = useDeferredValue(search);
const filteredData = useMemo(() => {
if (!data) {
return [];
}
return data.filter((automation) =>
automation.name.toLowerCase().includes(deferredSearch.toLowerCase()),
);
}, [data, deferredSearch]);

const isInferredOptionFiltered = INFER_AUTOMATION.name
.toLowerCase()
.includes(deferredSearch.toLowerCase());

return (
<FormField
control={form.control}
@@ -89,30 +88,38 @@ export const AutomationsSelectStateFields = ({
selected={Boolean(buttonLabel)}
aria-label={`Select Automation to ${action}`}
>
{getButtonLabel(data, field.value) ?? "Select automation"}
{buttonLabel ?? "Select automation"}
</ComboboxTrigger>
<ComboboxContent
filter={(value, search) =>
filterAutomations(value, search, data)
}
>
<ComboboxCommandInput placeholder="Search for an automation..." />
<ComboboxContent>
<ComboboxCommandInput
value={search}
onValueChange={setSearch}
placeholder="Search for an automation..."
/>
<ComboboxCommandEmtpy>No automation found</ComboboxCommandEmtpy>
<ComboboxCommandList>
<ComboboxCommandGroup>
<ComboboxCommandItem
selected={field.value === INFER_AUTOMATION.value}
onSelect={field.onChange}
value={INFER_AUTOMATION.value}
>
{INFER_AUTOMATION.name}
</ComboboxCommandItem>
{isInferredOptionFiltered && (
<ComboboxCommandItem
selected={field.value === INFER_AUTOMATION.value}
onSelect={(value) => {
field.onChange(value);
setSearch("");
}}
value={INFER_AUTOMATION.value}
>
{INFER_AUTOMATION.name}
</ComboboxCommandItem>
)}
{isSuccess ? (
data.map((automation) => (
filteredData.map((automation) => (
<ComboboxCommandItem
key={automation.id}
selected={field.value === automation.id}
onSelect={field.onChange}
onSelect={(value) => {
field.onChange(value);
setSearch("");
}}
value={automation.id}
>
{automation.name}
23 changes: 18 additions & 5 deletions ui-v2/src/components/ui/combobox/combobox.tsx
Original file line number Diff line number Diff line change
@@ -67,21 +67,34 @@ const ComboboxTrigger = ({
};

const ComboboxContent = ({
filter,
children,
}: {
filter?: (value: string, search: string, keywords?: string[]) => number;
children: React.ReactNode;
}) => {
return (
<PopoverContent fullWidth>
<Command filter={filter}>{children}</Command>
<Command shouldFilter={false}>{children}</Command>
</PopoverContent>
);
};

const ComboboxCommandInput = ({ placeholder }: { placeholder?: string }) => {
return <CommandInput placeholder={placeholder} className="h-9" />;
const ComboboxCommandInput = ({
value,
onValueChange,
placeholder,
}: {
value?: string;
onValueChange?: (value: string) => void;
placeholder?: string;
}) => {
return (
<CommandInput
value={value}
onValueChange={onValueChange}
placeholder={placeholder}
className="h-9"
/>
);
};

const ComboboxCommandList = ({ children }: { children: React.ReactNode }) => {
79 changes: 40 additions & 39 deletions ui-v2/src/components/ui/combobox/comboxbox.stories.tsx
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react";

import { Automation } from "@/api/automations";
import { createFakeAutomation } from "@/mocks";
import { useState } from "react";
import { useDeferredValue, useMemo, useState } from "react";
import {
Combobox,
ComboboxCommandEmtpy,
@@ -27,13 +27,7 @@ const INFER_AUTOMATION = {
name: "Infer Automation" as const,
} as const;

const MOCK_DATA = [
createFakeAutomation(),
createFakeAutomation(),
createFakeAutomation(),
createFakeAutomation(),
createFakeAutomation(),
];
const MOCK_DATA = Array.from({ length: 5 }, createFakeAutomation);

const getButtonLabel = (data: Array<Automation>, fieldValue: string) => {
if (fieldValue === INFER_AUTOMATION.value) {
@@ -46,55 +40,62 @@ const getButtonLabel = (data: Array<Automation>, fieldValue: string) => {
return undefined;
};

/** Because ShadCN only filters by `value` and not by a specific field, we need to write custom logic to filter objects by id */
const filterAutomations = (
value: string,
search: string,
data: Array<Automation> | undefined,
) => {
const searchTerm = search.toLowerCase();
const automation = data?.find((automation) => automation.id === value);
if (!automation) {
return 0;
}
const automationName = automation.name.toLowerCase();
if (automationName.includes(searchTerm)) {
return 1;
}
return 0;
};

function ComboboxStory() {
const [search, setSearch] = useState("");
const [selectedAutomationId, setSelectedAutomationId] = useState<
typeof UNASSIGNED | (string & {})
>(INFER_AUTOMATION.value);

const deferredSearch = useDeferredValue(search);

const filteredData = useMemo(() => {
return MOCK_DATA.filter((automation) =>
automation.name.toLowerCase().includes(deferredSearch.toLowerCase()),
);
}, [deferredSearch]);

console.log({ filteredData });

const isInferredOptionFiltered = INFER_AUTOMATION.name
.toLowerCase()
.includes(deferredSearch.toLowerCase());

const buttonLabel = getButtonLabel(MOCK_DATA, selectedAutomationId);

return (
<Combobox>
<ComboboxTrigger selected={Boolean(buttonLabel)}>
{buttonLabel ?? "Select automation"}
</ComboboxTrigger>
<ComboboxContent
filter={(value, search) => filterAutomations(value, search, MOCK_DATA)}
>
<ComboboxCommandInput placeholder="Search for an automation..." />
<ComboboxContent>
<ComboboxCommandInput
value={search}
onValueChange={setSearch}
placeholder="Search for an automation..."
/>
<ComboboxCommandEmtpy>No automation found</ComboboxCommandEmtpy>
<ComboboxCommandList>
<ComboboxCommandGroup>
<ComboboxCommandItem
selected={selectedAutomationId === INFER_AUTOMATION.value}
onSelect={setSelectedAutomationId}
value={INFER_AUTOMATION.value}
>
{INFER_AUTOMATION.name}
</ComboboxCommandItem>
{MOCK_DATA.map((automation) => (
{isInferredOptionFiltered && (
<ComboboxCommandItem
selected={selectedAutomationId === INFER_AUTOMATION.value}
onSelect={(value) => {
setSelectedAutomationId(value);
setSearch("");
}}
value={INFER_AUTOMATION.value}
>
{INFER_AUTOMATION.name}
</ComboboxCommandItem>
)}
{filteredData.map((automation) => (
<ComboboxCommandItem
key={automation.id}
selected={selectedAutomationId === automation.id}
onSelect={setSelectedAutomationId}
onSelect={(value) => {
setSelectedAutomationId(value);
setSearch("");
}}
value={automation.id}
>
{automation.name}

0 comments on commit 65bb8cd

Please sign in to comment.