Skip to content

Commit

Permalink
✨ feat(select): implement select async
Browse files Browse the repository at this point in the history
  • Loading branch information
thrownullexception committed Jul 1, 2024
1 parent 146d9d9 commit fadaaf0
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 21 deletions.
5 changes: 3 additions & 2 deletions src/components/app/form/input/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import { Input } from "@/components/ui/input";
interface IProps {
onChange: (value: string) => void;
loading?: boolean;
initialValue?: string;
}

export function FormSearch({ onChange, loading }: IProps) {
export function FormSearch({ onChange, loading, initialValue }: IProps) {
const { _ } = useLingui();

const [value, setValue] = useState("");
const [value, setValue] = useState(initialValue || "");

return (
<div className="relative flex w-full">
Expand Down
22 changes: 14 additions & 8 deletions src/components/app/form/input/select-async.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,30 @@ export function AsyncFormSelect(props: IProps) {

const currentLabelFromSelection = useMemo(() => {
const isValueInFirstDataLoad = fullData.data.find(
({ value }: ISelectData) => value === input.value
({ value }: ISelectData) => String(value) === String(input.value)
);

if (isValueInFirstDataLoad) {
return _(isValueInFirstDataLoad.label);
}

const isValueInSelectionOptions = selectOptions.data.find(
({ value }: ISelectData) => value === input.value
({ value }: ISelectData) => String(value) === String(input.value)
);

if (isValueInSelectionOptions) {
return _(isValueInSelectionOptions.label);
}

return undefined;
}, [url, fullData.isLoading]);
}, [url, fullData, selectOptions, input.value]);

const referenceLabel = useApi(referenceUrl?.(input.value), {
defaultData: "",
enabled: !!referenceUrl && !!input.value && !currentLabelFromSelection,
enabled:
!!referenceUrl &&
!!input.value &&
!(currentLabelFromSelection || fullData.isLoading),
});

useDebounce(
Expand All @@ -69,22 +72,25 @@ export function AsyncFormSelect(props: IProps) {
return <ErrorAlert message={fullData.error || selectOptions.error} />;
}

const isLoading = selectOptions.isLoading || fullData.isLoading;

if (fullData.data.length >= limit) {
return (
<FormSelect
{...props}
selectData={selectOptions.data}
isLoading={isLoading}
onSearch={{
isLoading: selectOptions.isLoading,
onChange: setSearch,
value: search,
valueLabel: referenceLabel.data
? referenceLabel.data
: currentLabelFromSelection,
valueLabel: currentLabelFromSelection || referenceLabel.data,
}}
/>
);
}

return <FormSelect {...props} selectData={fullData.data} />;
return (
<FormSelect {...props} selectData={fullData.data} isLoading={isLoading} />
);
}
11 changes: 6 additions & 5 deletions src/components/app/form/input/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { IBaseFormSelect } from "@/frontend/design-system/components/Form/Select
interface IFormSelect extends IBaseFormSelect {
selectData: ISelectData[];
onSearch?: ISelectProps["onSearch"];
isLoading?: boolean;
}

export function FormSelect(formInput: IFormSelect) {
Expand All @@ -23,7 +24,7 @@ export function FormSelect(formInput: IFormSelect) {
disabled,
label: formLabel,
disabledOptions,
placeholder,
isLoading,
onSearch,
} = formInput;
const { _ } = useLingui();
Expand All @@ -34,11 +35,11 @@ export function FormSelect(formInput: IFormSelect) {
{...input}
{...generateFormArias(meta)}
className={generateClassNames(meta)}
isLoading={isLoading}
options={selectData}
placeholder={
placeholder ||
fakeMessageDescriptor(`--- ${_(msg`Select ${_(formLabel)}`)} ---`)
}
placeholder={fakeMessageDescriptor(
`--- ${_(msg`Select ${_(formLabel)}`)} ---`
)}
disabled={disabled}
disabledOptions={disabledOptions}
onSearch={onSearch}
Expand Down
1 change: 0 additions & 1 deletion src/components/app/table/filters/Date/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export function FilterTableByDate({
/>
{new Date(filterValue?.value || "").toString() !== "Invalid Date" ? (
<>
{/* TODO date not changing */}
<ControlledFormDateInput
onChange={(value) => {
setFilter({
Expand Down
30 changes: 25 additions & 5 deletions src/components/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,39 @@ import * as SelectPrimitive from "@radix-ui/react-select";

import { useLingui } from "@lingui/react";
import { MessageDescriptor } from "@lingui/core";
import { Loader } from "react-feather";
import { msg } from "@lingui/macro";
import { cn } from "@/lib/utils";
import { ISelectData } from "@/shared/types/options";
import { FormSearch } from "../app/form/input/search";
import { EmptyWrapper } from "../app/empty-wrapper";
import { ListSkeleton } from "../app/skeleton/list";

const SelectRoot = SelectPrimitive.Root;

const SelectValue = SelectPrimitive.Value;

const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & {
isLoading?: boolean;
}
>(({ className, children, isLoading, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between font-normal whitespace-nowrap rounded-md border border-border bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-base placeholder:text-muted focus:outline-none focus:ring-1 focus:ring-primary disabled:cursor-not-allowed disabled:bg-soft [&>span]:line-clamp-1",
"flex h-9 w-full items-center justify-between font-normal whitespace-nowrap rounded-md border border-border bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-base data-[placeholder]:text-muted focus:outline-none focus:ring-1 focus:ring-primary disabled:cursor-not-allowed disabled:bg-soft [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<CaretSortIcon className="h-4 w-4 opacity-50" />
{isLoading ? (
<Loader className="animate-spin h-4 w-4 opacity-50" />
) : (
<CaretSortIcon className="h-4 w-4 opacity-50" />
)}
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
Expand Down Expand Up @@ -160,6 +170,7 @@ export interface ISelectProps {
placeholder: MessageDescriptor;
disabled?: boolean;
disabledOptions?: Array<string>;
isLoading?: boolean;
onSearch?: {
onChange: (value: string) => void;
value: string;
Expand All @@ -172,6 +183,7 @@ export function Select({
onChange,
options,
disabled,
isLoading,
value,
name,
placeholder,
Expand All @@ -181,7 +193,9 @@ export function Select({
}: ISelectProps) {
const { _ } = useLingui();

const valueLabel = options.find((option) => option.value === value)?.label;
const valueLabel = options.find(
(option) => String(option.value) === String(value)
)?.label;

return (
<SelectRoot onValueChange={onChange} value={value}>
Expand All @@ -190,6 +204,7 @@ export function Select({
name={name}
id={name}
disabled={disabled}
isLoading={isLoading}
>
<SelectValue placeholder={_(placeholder)}>
{onSearch?.valueLabel || (valueLabel ? _(valueLabel) : null)}
Expand All @@ -199,11 +214,16 @@ export function Select({
{onSearch && (
<div className="mb-1">
<FormSearch
initialValue={onSearch.value}
onChange={onSearch.onChange}
loading={onSearch.isLoading}
/>
</div>
)}
{onSearch?.value && options.length === 0 && !onSearch?.isLoading && (
<EmptyWrapper text={msg`No Search Results`} />
)}
{onSearch?.isLoading && <ListSkeleton count={10} />}
{options.map(({ value: value$1, label }) => (
<SelectItem
key={`${value$1}`}
Expand Down

0 comments on commit fadaaf0

Please sign in to comment.