Skip to content

Commit

Permalink
Merge pull request #904 from Shelf-nu/updating-dynamic-select-and-dro…
Browse files Browse the repository at this point in the history
…pdown

Updating dynamic select and dropdown
  • Loading branch information
DonKoko authored Apr 9, 2024
2 parents 1cc81b8 + 4f1d988 commit 683babb
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 151 deletions.
2 changes: 2 additions & 0 deletions app/components/assets/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ export const AssetForm = ({
label="Categories"
initialDataKey="categories"
countKey="totalCategories"
closeOnSelect
extraContent={
<Button
to="/categories/new"
Expand Down Expand Up @@ -289,6 +290,7 @@ export const AssetForm = ({
label="Locations"
initialDataKey="locations"
countKey="totalLocations"
closeOnSelect
extraContent={
<Button
to="/locations/new"
Expand Down
7 changes: 5 additions & 2 deletions app/components/dynamic-dropdown/dynamic-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { cloneElement } from "react";
import { ChevronDownIcon } from "@radix-ui/react-icons";
import { useNavigation } from "@remix-run/react";
import type { ModelFilterItem, ModelFilterProps } from "~/hooks";
import { useModelFilters } from "~/hooks";
import { useModelFilters } from "~/hooks/use-model-filters";
import type {
ModelFilterItem,
ModelFilterProps,
} from "~/hooks/use-model-filters";
import { isFormProcessing, tw } from "~/utils";
import { EmptyState } from "./empty-state";
import Input from "../forms/input";
Expand Down
306 changes: 174 additions & 132 deletions app/components/dynamic-select/dynamic-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ChevronDownIcon } from "@radix-ui/react-icons";
import {
Popover,
PopoverContent,
PopoverPortal,
PopoverTrigger,
} from "@radix-ui/react-popover";
import { useNavigation } from "@remix-run/react";
Expand Down Expand Up @@ -79,147 +80,188 @@ export default function DynamicSelect({
});

return (
<div className="relative w-full">
<input
type="hidden"
value={selectedValue}
name={fieldName ?? model.name}
/>
<>
<div className="relative w-full">
<input
type="hidden"
value={selectedValue}
name={fieldName ?? model.name}
/>
<MobileStyles open={isPopoverOpen} />

<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
<PopoverTrigger disabled={disabled} asChild>
<div
ref={triggerRef}
className="flex items-center justify-between rounded border border-gray-300 px-[14px] py-2 text-[16px] text-gray-500 hover:cursor-pointer disabled:opacity-50"
>
{items.find((i) => i.id === selectedValue)?.name ?? placeholder}
<ChevronDownIcon />
</div>
</PopoverTrigger>

<PopoverContent
className={tw(
"z-[100] overflow-y-auto rounded-md border border-gray-300 bg-white",
className
)}
style={{
...style,
width: triggerRef?.current?.clientWidth,
}}
align="center"
sideOffset={5}
>
<div className="flex items-center justify-between p-3">
<div className="text-xs font-semibold text-gray-700">{label}</div>
<When truthy={selectedItems?.length > 0 && showSearch}>
<Button
as="button"
variant="link"
className="whitespace-nowrap text-xs font-normal text-gray-500 hover:text-gray-600"
onClick={() => {
setSelectedValue(undefined);
clearFilters();
}}
>
Clear selection
</Button>
</When>
</div>
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
<PopoverTrigger disabled={disabled} asChild>
<div
ref={triggerRef}
className="flex items-center justify-between rounded border border-gray-300 px-[14px] py-2 text-[16px] text-gray-500 hover:cursor-pointer disabled:opacity-50"
>
{items.find((i) => i.id === selectedValue)?.name ?? placeholder}
<ChevronDownIcon />
</div>
</PopoverTrigger>
<PopoverPortal>
<PopoverContent
className={tw(
"z-[100] overflow-y-auto rounded-md border border-gray-300 bg-white",
className
)}
style={{
...style,
width: triggerRef?.current?.clientWidth,
}}
align="center"
sideOffset={5}
>
<div className="flex items-center justify-between p-3">
<div className="text-xs font-semibold text-gray-700">
{label}
</div>
<When truthy={selectedItems?.length > 0 && showSearch}>
<Button
as="button"
variant="link"
className="whitespace-nowrap text-xs font-normal text-gray-500 hover:text-gray-600"
onClick={() => {
setSelectedValue(undefined);
clearFilters();
}}
>
Clear selection
</Button>
</When>
</div>

<When truthy={showSearch}>
<div className="filters-form relative border-y border-y-gray-200 p-3">
<Input
type="text"
label={`Search ${label}`}
placeholder={`Search ${label}`}
hideLabel
className="text-gray-500"
icon={searchIcon}
value={searchQuery}
onChange={handleSearchQueryChange}
autoFocus
/>
<When truthy={Boolean(searchQuery)}>
<Button
icon="x"
variant="tertiary"
disabled={Boolean(searchQuery)}
onClick={() => {
setSelectedValue(undefined);
resetModelFiltersFetcher();
}}
className="z-100 pointer-events-auto absolute right-6 top-0 h-full border-0 p-0 text-center text-gray-400 hover:text-gray-900"
/>
<When truthy={showSearch}>
<div className="filters-form relative border-y border-y-gray-200 p-3">
<Input
type="text"
label={`Search ${label}`}
placeholder={`Search ${label}`}
hideLabel
className="text-gray-500"
icon={searchIcon}
value={searchQuery}
onChange={handleSearchQueryChange}
autoFocus
/>
<When truthy={Boolean(searchQuery)}>
<Button
icon="x"
variant="tertiary"
disabled={Boolean(searchQuery)}
onClick={() => {
setSelectedValue(undefined);
resetModelFiltersFetcher();
}}
className="z-100 pointer-events-auto absolute right-6 top-0 h-full border-0 p-0 text-center text-gray-400 hover:text-gray-900"
/>
</When>
</div>
</When>
</div>
</When>

<div className="max-h-[320px] divide-y overflow-y-auto">
{searchQuery !== "" && items.length === 0 && (
<EmptyState searchQuery={searchQuery} modelName={model.name} />
)}
{items.map((item) => (
<div
key={item.id}
className={tw(
"flex cursor-pointer select-none items-center justify-between gap-4 px-6 py-4 outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-gray-100 focus:bg-gray-100",
item.id === selectedValue && "bg-gray-100"
<div className="max-h-[320px] divide-y overflow-y-auto">
{searchQuery !== "" && items.length === 0 && (
<EmptyState
searchQuery={searchQuery}
modelName={model.name}
/>
)}
onClick={() => {
setSelectedValue(item.id);
handleSelectItemChange(item.id);
if (closeOnSelect) {
setIsPopoverOpen(false);
}
}}
>
<div>
{typeof renderItem === "function" ? (
renderItem({ ...item, metadata: item })
) : (
<div className="flex items-center truncate text-sm font-medium">
{item.name}
{items.map((item) => (
<div
key={item.id}
className={tw(
"flex cursor-pointer select-none items-center justify-between gap-4 px-6 py-4 outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-gray-100 focus:bg-gray-100",
item.id === selectedValue && "bg-gray-100"
)}
onClick={() => {
setSelectedValue(item.id);
handleSelectItemChange(item.id);
if (closeOnSelect) {
setIsPopoverOpen(false);
}
}}
>
<div>
{typeof renderItem === "function" ? (
renderItem({ ...item, metadata: item })
) : (
<div className="flex items-center truncate text-sm font-medium">
{item.name}
</div>
)}
</div>
)}
</div>

<When truthy={item.id === selectedValue}>
<CheckIcon className="text-primary" />
</When>
</div>
))}
<When truthy={item.id === selectedValue}>
<CheckIcon className="text-primary" />
</When>
</div>
))}

{items.length < totalItems && searchQuery === "" && (
<button
type="button"
disabled={isSearching}
onClick={getAllEntries}
className=" flex w-full cursor-pointer select-none items-center justify-between px-6 py-3 text-sm font-medium text-gray-600 outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-gray-100 focus:bg-gray-100"
>
Show all
<span>
{isSearching ? (
<Spinner className="size-4" />
) : (
<ChevronDownIcon className="size-4" />
)}
</span>
</button>
)}
</div>
{items.length < totalItems && searchQuery === "" && (
<button
type="button"
disabled={isSearching}
onClick={getAllEntries}
className=" flex w-full cursor-pointer select-none items-center justify-between px-6 py-3 text-sm font-medium text-gray-600 outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-gray-100 focus:bg-gray-100"
>
Show all
<span>
{isSearching ? (
<Spinner className="size-4" />
) : (
<ChevronDownIcon className="size-4" />
)}
</span>
</button>
)}
</div>

<When truthy={totalItems > 6}>
<div className="border-t p-3 text-gray-500">
Showing {items.length} out of {totalItems}, type to search for
more
</div>
</When>
<When truthy={totalItems > 6}>
<div className="border-t p-3 text-gray-500">
Showing {items.length} out of {totalItems}, type to search for
more
</div>
</When>

<When truthy={typeof extraContent !== "undefined"}>
<div className="border-t px-3 pb-3">{extraContent}</div>
</When>
</PopoverContent>
</Popover>
</div>
<When truthy={typeof extraContent !== "undefined"}>
<div className="border-t px-3 pb-3">{extraContent}</div>
</When>
</PopoverContent>
</PopoverPortal>
</Popover>
</div>
</>
);
}

const MobileStyles = ({ open }: { open: boolean }) =>
open && (
<>
<div
// eslint-disable-next-line tailwindcss/migration-from-tailwind-2
className={tw(
"extra-overlay fixed right-0 top-0 z-[999] h-screen w-screen cursor-pointer bg-black bg-opacity-50 backdrop-blur transition duration-300 ease-in-out md:hidden",
open ? "visible" : "invisible opacity-0"
)}
></div>
<style
dangerouslySetInnerHTML={{
__html: `@media (max-width: 640px) {
body {
overflow: hidden;
}
[data-radix-popper-content-wrapper] {
z-index: 9999 !important;
top: 20px !important;
left: 50% !important;
transform: translate(-50%, 0) !important;
}
}`,
}} // is a hack to fix the dropdown menu not being in the right place on mobile
// can not target [data-radix-popper-content-wrapper] for this file only with css
// so we have to use dangerouslySetInnerHTML
// PR : https://github.com/Shelf-nu/shelf.nu/pull/304
></style>
</>
);
2 changes: 1 addition & 1 deletion app/components/layout/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const Dialog = ({
<div
className={tw(
" relative z-10 size-full bg-white p-6 shadow-lg md:max-h-[85vh] md:rounded",
noScroll ? "md:h-[85vh]" : "overflow-y-auto"
noScroll ? "md:h-[85vh]" : "md:overflow-y-auto"
)}
>
<Button
Expand Down
Loading

0 comments on commit 683babb

Please sign in to comment.