From 06ed22c1e2f6a04c77bbd7534c667e97b1297296 Mon Sep 17 00:00:00 2001 From: Colin Regourd Date: Fri, 28 Jun 2024 18:30:26 +0200 Subject: [PATCH] Add loader to select --- .changeset/strange-waves-rush.md | 5 + .../[locale]/admin/[[...nextadmin]]/page.tsx | 2 +- apps/example/public/assets/logo.svg | 92 +++++++++++ packages/next-admin/src/components/Form.tsx | 1 + .../next-admin/src/components/LoaderRow.tsx | 7 +- .../inputs/MultiSelect/MultiSelectWidget.tsx | 148 +++++++++--------- .../src/components/inputs/SelectWidget.tsx | 7 +- .../src/components/inputs/Selector.tsx | 36 +---- .../src/hooks/useCloseOnOutsideClick.ts | 33 ++-- 9 files changed, 198 insertions(+), 133 deletions(-) create mode 100644 .changeset/strange-waves-rush.md create mode 100644 apps/example/public/assets/logo.svg diff --git a/.changeset/strange-waves-rush.md b/.changeset/strange-waves-rush.md new file mode 100644 index 00000000..c48a981d --- /dev/null +++ b/.changeset/strange-waves-rush.md @@ -0,0 +1,5 @@ +--- +"@premieroctet/next-admin": patch +--- + +Add loader on select diff --git a/apps/example/app/[locale]/admin/[[...nextadmin]]/page.tsx b/apps/example/app/[locale]/admin/[[...nextadmin]]/page.tsx index 9313f40f..3987db28 100644 --- a/apps/example/app/[locale]/admin/[[...nextadmin]]/page.tsx +++ b/apps/example/app/[locale]/admin/[[...nextadmin]]/page.tsx @@ -17,7 +17,7 @@ export const viewport: Viewport = { }; export const metadata: Metadata = { - icons: "/favicon.ico", + icons: "/assets/logo.svg", }; export default async function AdminPage({ diff --git a/apps/example/public/assets/logo.svg b/apps/example/public/assets/logo.svg new file mode 100644 index 00000000..21a409c6 --- /dev/null +++ b/apps/example/public/assets/logo.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/next-admin/src/components/Form.tsx b/packages/next-admin/src/components/Form.tsx index 4f987519..2fb6c478 100644 --- a/packages/next-admin/src/components/Form.tsx +++ b/packages/next-admin/src/components/Form.tsx @@ -560,6 +560,7 @@ const Form = ({ onSubmit={(e) => console.log("onSubmit", e)} onError={(e) => console.log("onError", e)} ref={formRef} + className="relative" /> )} diff --git a/packages/next-admin/src/components/LoaderRow.tsx b/packages/next-admin/src/components/LoaderRow.tsx index 31230672..d389ac59 100644 --- a/packages/next-admin/src/components/LoaderRow.tsx +++ b/packages/next-admin/src/components/LoaderRow.tsx @@ -12,14 +12,15 @@ const LoadingRow = forwardRef< return (
- - {t("selector.loading")} + + {t("selector.loading")}
); }); diff --git a/packages/next-admin/src/components/inputs/MultiSelect/MultiSelectWidget.tsx b/packages/next-admin/src/components/inputs/MultiSelect/MultiSelectWidget.tsx index 12040131..df80651c 100644 --- a/packages/next-admin/src/components/inputs/MultiSelect/MultiSelectWidget.tsx +++ b/packages/next-admin/src/components/inputs/MultiSelect/MultiSelectWidget.tsx @@ -1,7 +1,5 @@ import { RJSFSchema } from "@rjsf/utils"; import clsx from "clsx"; -import { PropsWithChildren, useRef } from "react"; -import { twMerge } from "tailwind-merge"; import DoubleArrow from "../../../assets/icons/DoubleArrow"; import { useForm } from "../../../context/FormContext"; import { useI18n } from "../../../context/I18nContext"; @@ -26,9 +24,10 @@ type Props = { const MultiSelectWidget = (props: Props) => { const formContext = useForm(); const { formData, onChange, options, name, schema } = props; - const containerRef = useRef(null); + const containerRef = useCloseOnOutsideClick(() => + formContext.setOpen(false, name) + ); const { t } = useI18n(); - useCloseOnOutsideClick(containerRef, () => formContext.setOpen(false, name)); const fieldOptions = formContext.options?.model?.[formContext.resource!]?.edit?.fields?.[ name as Field @@ -53,65 +52,58 @@ const MultiSelectWidget = (props: Props) => { // @ts-expect-error const fieldSortable = displayMode === "list" && !!fieldOptions?.orderField; - const Select = ({ - children, - className, - }: PropsWithChildren>) => ( -
- - {children} -
+ const select = ( + ); return ( -
+
{displayMode === "select" && ( - +
+ {select} + {formData?.map( + (value: any, index: number) => + value && ( + onRemoveClick(value.value)} + deletable={!props.disabled} + /> + ) + )} + {!props.disabled && ( +
+ +
+ )} +
)} {displayMode === "list" && (
@@ -123,15 +115,16 @@ const MultiSelectWidget = (props: Props) => { sortable={fieldSortable} onUpdateFormData={onChange} /> - + +
)} {displayMode === "table" && ( @@ -142,15 +135,16 @@ const MultiSelectWidget = (props: Props) => { onRemoveClick={onRemoveClick} deletable={!props.disabled} /> - + +
)} diff --git a/packages/next-admin/src/components/inputs/SelectWidget.tsx b/packages/next-admin/src/components/inputs/SelectWidget.tsx index ac8a1b69..7fa1ce9a 100644 --- a/packages/next-admin/src/components/inputs/SelectWidget.tsx +++ b/packages/next-admin/src/components/inputs/SelectWidget.tsx @@ -5,7 +5,7 @@ import { import { WidgetProps } from "@rjsf/utils"; import clsx from "clsx"; import Link from "next/link"; -import { useMemo, useRef } from "react"; +import { useMemo } from "react"; import DoubleArrow from "../../assets/icons/DoubleArrow"; import { useConfig } from "../../context/ConfigContext"; import { useForm } from "../../context/FormContext"; @@ -28,8 +28,9 @@ const SelectWidget = ({ const enumOptions = options.enumOptions?.map( (option: any) => option.value as Enumeration ); - const containerRef = useRef(null); - useCloseOnOutsideClick(containerRef, () => formContext.setOpen(false, name)); + const containerRef = useCloseOnOutsideClick(() => { + formContext.setOpen(false, name); + }); const { basePath } = useConfig(); diff --git a/packages/next-admin/src/components/inputs/Selector.tsx b/packages/next-admin/src/components/inputs/Selector.tsx index 5c6c012b..976fa330 100644 --- a/packages/next-admin/src/components/inputs/Selector.tsx +++ b/packages/next-admin/src/components/inputs/Selector.tsx @@ -23,9 +23,7 @@ export const Selector = ({ }: SelectorProps) => { const currentQuery = useRef(""); const searchInput = createRef(); - const loaderRowRef = useRef(null); const { t } = useI18n(); - const [isLastRowReached, setIsLastRowReached] = useState(false); const { allOptions, isPending, @@ -76,35 +74,10 @@ export const Selector = ({ searchPage.current = 1; currentQuery.current = e.target.value; - setIsLastRowReached(false); runSearch(currentQuery.current, true); }, 300); const containerRef = useRef(null); - useEffect(() => { - const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - setIsLastRowReached(true); - observer.disconnect(); - } - }); - }, - { threshold: 1 } - ); - - const current = loaderRowRef.current; - if (current) { - observer.observe(current); - } - return () => { - if (current) { - observer.unobserve(current); - } - }; - }); - const onScroll = () => { // No need to do an infinite scroll for enums if (!containerRef.current || options) { @@ -117,7 +90,8 @@ export const Selector = ({ const offset = height - scrollY; if ( - offset === 0 && + offset >= -100 && + offset <= 0 && !isPending && optionsLeft.length < totalSearchedItems.current ) { @@ -167,13 +141,11 @@ export const Selector = ({ {option.label}
))} - - {optionsLeft && optionsLeft.length === 0 && !isPending ? ( + {isPending && } + {optionsLeft && optionsLeft.length === 0 && !isPending && (
No results found
- ) : ( - !isLastRowReached && )} diff --git a/packages/next-admin/src/hooks/useCloseOnOutsideClick.ts b/packages/next-admin/src/hooks/useCloseOnOutsideClick.ts index c8037e7f..76b45cb9 100644 --- a/packages/next-admin/src/hooks/useCloseOnOutsideClick.ts +++ b/packages/next-admin/src/hooks/useCloseOnOutsideClick.ts @@ -1,24 +1,23 @@ -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; -function useCloseOnOutsideClick( - ref: React.RefObject, +const useCloseOnOutsideClick = ( close: () => void -) { - function onWindowClick(event: MouseEvent) { +) => { + const ref: React.RefObject = useRef(null); + const onWindowClick = (event: MouseEvent) => { if (ref.current && !ref.current.contains(event.target as Node)) { close(); } - } - useEffect( - () => { - window.addEventListener("click", onWindowClick); - return () => { - window.removeEventListener("click", onWindowClick); - }; - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); -} + }; + + useEffect(() => { + window.addEventListener("click", onWindowClick); + return () => { + window.removeEventListener("click", onWindowClick); + }; + }, []); + + return ref; +}; export default useCloseOnOutsideClick;