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/.github/workflows/reset-database.yml b/.github/workflows/reset-database.yml
index 6c086fba..e4b4074f 100644
--- a/.github/workflows/reset-database.yml
+++ b/.github/workflows/reset-database.yml
@@ -13,4 +13,7 @@ jobs:
- name: Install dependencies
run: yarn install
- name: Reset database
+ env:
+ POSTGRES_URL_NON_POOLING: ${{ env.POSTGRES_URL_NON_POOLING }}
+ POSTGRES_URL: ${{ env.POSTGRES_URL }}
run: yarn reset-database
\ No newline at end of file
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/options.tsx b/apps/example/options.tsx
index 1b459cfa..7797fa3a 100644
--- a/apps/example/options.tsx
+++ b/apps/example/options.tsx
@@ -228,6 +228,10 @@ export const options: NextAdminOptions = {
label: "Documentation",
url: "https://next-admin.js.org",
},
+ {
+ label: "Page Router",
+ url: "/pagerouter/admin",
+ },
],
defaultColorScheme: "dark",
};
diff --git a/apps/example/pageRouterOptions.tsx b/apps/example/pageRouterOptions.tsx
index 54699343..cf988246 100644
--- a/apps/example/pageRouterOptions.tsx
+++ b/apps/example/pageRouterOptions.tsx
@@ -3,7 +3,7 @@ import DatePicker from "./components/DatePicker";
export const options: NextAdminOptions = {
basePath: "/pagerouter/admin",
- title: "⚡️ My Admin",
+ title: "⚡️ My Admin Page Router",
model: {
User: {
toString: (user) => `${user.name} (${user.email})`,
@@ -22,7 +22,6 @@ export const options: NextAdminOptions = {
},
},
},
-
],
fields: {
role: {
@@ -157,6 +156,12 @@ export const options: NextAdminOptions = {
icon: "AdjustmentsHorizontalIcon",
},
},
+ externalLinks: [
+ {
+ label: "App Router",
+ url: "/ ",
+ },
+ ],
sidebar: {
groups: [
{
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..7991a4f6 100644
--- a/packages/next-admin/src/components/inputs/MultiSelect/MultiSelectWidget.tsx
+++ b/packages/next-admin/src/components/inputs/MultiSelect/MultiSelectWidget.tsx
@@ -1,11 +1,8 @@
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";
-import useCloseOnOutsideClick from "../../../hooks/useCloseOnOutsideClick";
import { Enumeration, Field, ModelName } from "../../../types";
import Button from "../../radix/Button";
import { Selector } from "../Selector";
@@ -26,9 +23,7 @@ type Props = {
const MultiSelectWidget = (props: Props) => {
const formContext = useForm();
const { formData, onChange, options, name, schema } = props;
- const containerRef = useRef(null);
const { t } = useI18n();
- useCloseOnOutsideClick(containerRef, () => formContext.setOpen(false, name));
const fieldOptions =
formContext.options?.model?.[formContext.resource!]?.edit?.fields?.[
name as Field
@@ -53,65 +48,61 @@ 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 +114,16 @@ const MultiSelectWidget = (props: Props) => {
sortable={fieldSortable}
onUpdateFormData={onChange}
/>
-
+
+
)}
{displayMode === "table" && (
@@ -142,15 +134,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..1933122a 100644
--- a/packages/next-admin/src/components/inputs/SelectWidget.tsx
+++ b/packages/next-admin/src/components/inputs/SelectWidget.tsx
@@ -5,11 +5,10 @@ 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";
-import useCloseOnOutsideClick from "../../hooks/useCloseOnOutsideClick";
import { Enumeration } from "../../types";
import { slugify } from "../../utils/tools";
import { Selector } from "./Selector";
@@ -28,8 +27,6 @@ const SelectWidget = ({
const enumOptions = options.enumOptions?.map(
(option: any) => option.value as Enumeration
);
- const containerRef = useRef(null);
- useCloseOnOutsideClick(containerRef, () => formContext.setOpen(false, name));
const { basePath } = useConfig();
@@ -43,7 +40,7 @@ const SelectWidget = ({
}, [value]);
return (
-
+
{
+ e.stopPropagation();
+ }}
>
@@ -96,7 +96,16 @@ const SelectWidget = ({
)}
{!disabled && (
-
+
{
+ e.stopPropagation();
+ e.preventDefault();
+ if (!disabled) {
+ formContext.toggleOpen(name);
+ }
+ }}
+ >
)}
diff --git a/packages/next-admin/src/components/inputs/Selector.tsx b/packages/next-admin/src/components/inputs/Selector.tsx
index 5c6c012b..cd2db8de 100644
--- a/packages/next-admin/src/components/inputs/Selector.tsx
+++ b/packages/next-admin/src/components/inputs/Selector.tsx
@@ -1,7 +1,9 @@
import { Transition } from "@headlessui/react";
import debounce from "lodash/debounce";
import { ChangeEvent, createRef, useEffect, useRef, useState } from "react";
+import { useForm } from "../../context/FormContext";
import { useI18n } from "../../context/I18nContext";
+import useCloseOnOutsideClick from "../../hooks/useCloseOnOutsideClick";
import useSearchPaginatedResource from "../../hooks/useSearchPaginatedResource";
import { Enumeration } from "../../types";
import LoaderRow from "../LoaderRow";
@@ -21,11 +23,14 @@ export const Selector = ({
options,
selectedOptions,
}: SelectorProps) => {
+ const formContext = useForm();
+ const containerRef = useCloseOnOutsideClick
(() =>
+ formContext.setOpen(false, name)
+ );
+
const currentQuery = useRef("");
const searchInput = createRef();
- const loaderRowRef = useRef(null);
const { t } = useI18n();
- const [isLastRowReached, setIsLastRowReached] = useState(false);
const {
allOptions,
isPending,
@@ -76,34 +81,8 @@ 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
@@ -117,7 +96,8 @@ export const Selector = ({
const offset = height - scrollY;
if (
- offset === 0 &&
+ offset >= -100 &&
+ offset <= 0 &&
!isPending &&
optionsLeft.length < totalSearchedItems.current
) {
@@ -167,13 +147,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;