Skip to content

Commit

Permalink
Merge pull request #1641 from rockingrohit9639/feature/improve-curren…
Browse files Browse the repository at this point in the history
…cy-selector

feat(currency-selector): create new currency selector with search
  • Loading branch information
DonKoko authored Feb 11, 2025
2 parents a143dba + 750598b commit 79e5986
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,33 @@ export function FieldSelector({
setSelectedIndex(0); // Reset selection when search changes
};

// Ensure selected item is visible in viewport
const scrollToIndex = (index: number) => {
setTimeout(() => {
const selectedElement = document.getElementById(`column-option-${index}`);
if (selectedElement) {
selectedElement.scrollIntoView({ block: "nearest" });
}
}, 0);
};

const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
switch (event.key) {
case "ArrowDown":
event.preventDefault();
setSelectedIndex((prev) =>
prev < filteredColumns.length - 1 ? prev + 1 : prev
);
setSelectedIndex((prev) => {
const newIndex = prev < filteredColumns.length - 1 ? prev + 1 : prev;
scrollToIndex(newIndex);
return newIndex;
});
break;
case "ArrowUp":
event.preventDefault();
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : prev));
setSelectedIndex((prev) => {
const newIndex = prev > 0 ? prev - 1 : prev;
scrollToIndex(newIndex);
return newIndex;
});
break;
case "Enter":
event.preventDefault();
Expand All @@ -80,16 +96,6 @@ export function FieldSelector({
}
};

// Ensure selected item is visible in viewport
useEffect(() => {
const selectedElement = document.getElementById(
`column-option-${selectedIndex}`
);
if (selectedElement) {
selectedElement.scrollIntoView({ block: "nearest" });
}
}, [selectedIndex]);

const displayText = filter.isNew
? "Select column"
: parseColumnName(fieldName);
Expand Down
163 changes: 163 additions & 0 deletions app/components/workspace/currency-selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { useMemo, useRef, useState } from "react";
import type { KeyboardEvent } from "react";

import {
Popover,
PopoverContent,
PopoverPortal,
PopoverTrigger,
} from "@radix-ui/react-popover";
import { useLoaderData } from "@remix-run/react";
import { CheckIcon, ChevronDownIcon, SearchIcon } from "lucide-react";
import type { loader } from "~/routes/_layout+/account-details.workspace.$workspaceId.edit";
import { tw } from "~/utils/tw";
import When from "../when/when";

type CurrencySelectorProps = {
className?: string;
defaultValue: string;
name?: string;
};

export default function CurrencySelector({
className,
defaultValue,
name,
}: CurrencySelectorProps) {
const triggerRef = useRef<HTMLButtonElement>(null);
const { curriences } = useLoaderData<typeof loader>();

const [isOpen, setIsOpen] = useState(false);
const [selectedCurrency, setSelectedCurrency] = useState(defaultValue);
const [selectedIndex, setSelectedIndex] = useState<number>(0);

const [searchQuery, setSearchQuery] = useState("");

const filteredCurrencies = useMemo(() => {
if (!searchQuery) {
return curriences;
}

return curriences.filter((currency) =>
currency.toLowerCase().includes(searchQuery.toLowerCase())
);
}, [curriences, searchQuery]);

function handleSelect(currency: string) {
setSelectedCurrency(currency);
setIsOpen(false);
}

// Ensure selected item is visible in viewport
const scrollToIndex = (index: number) => {
setTimeout(() => {
const selectedElement = document.getElementById(
`currency-option-${index}`
);
if (selectedElement) {
selectedElement.scrollIntoView({ block: "nearest" });
}
}, 0);
};

const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
switch (event.key) {
case "ArrowDown":
event.preventDefault();
setSelectedIndex((prev) => {
const newIndex =
prev < filteredCurrencies.length - 1 ? prev + 1 : prev;
scrollToIndex(newIndex);
return newIndex;
});
break;
case "ArrowUp":
event.preventDefault();
setSelectedIndex((prev) => {
const newIndex = prev > 0 ? prev - 1 : prev;
scrollToIndex(newIndex);
return newIndex;
});
break;
case "Enter":
event.preventDefault();
if (filteredCurrencies[selectedIndex]) {
setSelectedCurrency(filteredCurrencies[selectedIndex]);
setIsOpen(false);
}
break;
}
};

return (
<Popover
open={isOpen}
onOpenChange={(v) => {
scrollToIndex(selectedIndex);
setIsOpen(v);
}}
>
<PopoverTrigger asChild>
<button
ref={triggerRef}
className={tw(
"flex w-full items-center justify-between rounded-md border p-3",
className
)}
>
<span>{selectedCurrency}</span>
<ChevronDownIcon className="inline-block size-4 text-gray-500" />
<input type="hidden" name={name} value={selectedCurrency} />
</button>
</PopoverTrigger>
<PopoverPortal>
<PopoverContent
className="z-[999999] max-h-[400px] overflow-scroll rounded-md border bg-white"
side="bottom"
style={{ width: triggerRef?.current?.clientWidth }}
>
<div className="flex items-center border-b">
<SearchIcon className="ml-4 size-4 text-gray-500" />
<input
placeholder="Search currency..."
className="border-0 px-4 py-2 pl-2 text-[14px] focus:border-0 focus:ring-0"
value={searchQuery}
onChange={(event) => {
setSearchQuery(event.target.value);
}}
onKeyDown={handleKeyDown}
/>
</div>
{filteredCurrencies.map((currency, index) => {
const isSelected = selectedCurrency === currency;
const isHovered = selectedIndex === index;

return (
<div
id={`currency-option-${index}`}
key={currency}
className={tw(
"flex items-center justify-between px-4 py-3 text-sm text-gray-600 hover:cursor-pointer hover:bg-gray-50",
isHovered && "bg-gray-50"
)}
onClick={() => {
handleSelect(currency);
}}
>
<span>{currency}</span>
<When truthy={isSelected}>
<CheckIcon className="size-4 text-primary" />
</When>
</div>
);
})}
{filteredCurrencies.length === 0 && (
<div className="px-4 py-2 text-sm text-gray-500">
No currency found
</div>
)}
</PopoverContent>
</PopoverPortal>
</Popover>
);
}
38 changes: 4 additions & 34 deletions app/components/workspace/edit-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,10 @@ import { ACCEPT_SUPPORTED_IMAGES } from "~/utils/constants";
import { isFormProcessing } from "~/utils/form";
import { tw } from "~/utils/tw";
import { zodFieldIsRequired } from "~/utils/zod";
import CurrencySelector from "./currency-selector";
import FormRow from "../forms/form-row";
import { InnerLabel } from "../forms/inner-label";
import Input from "../forms/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../forms/select";
import { CrispButton } from "../marketing/crisp";
import { Button } from "../shared/button";
import { Card } from "../shared/card";
Expand Down Expand Up @@ -61,8 +55,7 @@ export const WorkspaceEditForm = ({
children,
className,
}: Props) => {
const { curriences, organization, isPersonalWorkspace } =
useLoaderData<typeof loader>();
const { organization, isPersonalWorkspace } = useLoaderData<typeof loader>();
const navigation = useNavigation();

let schema = EditWorkspaceFormSchema(
Expand Down Expand Up @@ -144,33 +137,10 @@ export const WorkspaceEditForm = ({
}
>
<InnerLabel hideLg>Currency</InnerLabel>
<Select
<CurrencySelector
defaultValue={currency || "USD"}
disabled={disabled}
name={zo.fields.currency()}
>
<SelectTrigger
className="px-3.5 py-3"
aria-label="Select currency"
>
<SelectValue placeholder="Choose a field type" />
</SelectTrigger>
<SelectContent
position="popper"
className="w-full min-w-[300px]"
align="start"
>
<div className=" max-h-[320px] overflow-auto">
{curriences.map((value) => (
<SelectItem value={value} key={value}>
<span className="mr-4 text-[14px] text-gray-700">
{value}
</span>
</SelectItem>
))}
</div>
</SelectContent>
</Select>
/>
</FormRow>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterEnum
ALTER TYPE "Currency" ADD VALUE 'PKR';

1 change: 1 addition & 0 deletions app/database/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,7 @@ enum Currency {
SEK // Swedish Krona
SGD // Singapore Dollar
ZAR // South African Rand
PKR // Pakistani Rupee
}

enum InviteStatuses {
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_layout+/settings.general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ export default function GeneralPage() {
const { organization, canExportAssets } = useLoaderData<typeof loader>();
return (
<div className="mb-2.5 flex flex-col justify-between bg-white md:rounded md:border md:border-gray-200 md:px-6 md:py-5">
<div className=" mb-6">
<div className="mb-6">
<h3 className="text-text-lg font-semibold">General</h3>
<p className="text-sm text-gray-600">
Manage general workspace settings.
Expand Down

0 comments on commit 79e5986

Please sign in to comment.