Skip to content

Commit

Permalink
Add loader to select
Browse files Browse the repository at this point in the history
  • Loading branch information
cregourd committed Jun 28, 2024
1 parent 5b5e505 commit 06ed22c
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 133 deletions.
5 changes: 5 additions & 0 deletions .changeset/strange-waves-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@premieroctet/next-admin": patch
---

Add loader on select
2 changes: 1 addition & 1 deletion apps/example/app/[locale]/admin/[[...nextadmin]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const viewport: Viewport = {
};

export const metadata: Metadata = {
icons: "/favicon.ico",
icons: "/assets/logo.svg",
};

export default async function AdminPage({
Expand Down
92 changes: 92 additions & 0 deletions apps/example/public/assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/next-admin/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,7 @@ const Form = ({
onSubmit={(e) => console.log("onSubmit", e)}
onError={(e) => console.log("onError", e)}
ref={formRef}
className="relative"
/>
)}
</FormContext.Consumer>
Expand Down
7 changes: 4 additions & 3 deletions packages/next-admin/src/components/LoaderRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ const LoadingRow = forwardRef<
return (
<div
className={clsx(
"flex items-center justify-start px-3 py-2 text-sm text-gray-400",
"flex items-center justify-start px-3 py-2",
"dark:bg-dark-nextadmin-background-subtle ",
props.className
)}
{...props}
ref={ref}
>
<Loader className="h-5 w-5 animate-spin stroke-gray-400" />
<span className="ml-3">{t("selector.loading")}</span>
<Loader className="h-5 w-5 animate-spin stroke-nextadmin-content-default dark:stroke-dark-nextadmin-content-default dark:stroke-gray-400" />
<span className="ml-3 dark:text-dark-nextadmin-content-default text-sm text-gray-400">{t("selector.loading")}</span>
</div>
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -26,9 +24,10 @@ type Props = {
const MultiSelectWidget = (props: Props) => {
const formContext = useForm();
const { formData, onChange, options, name, schema } = props;
const containerRef = useRef<HTMLDivElement>(null);
const containerRef = useCloseOnOutsideClick<HTMLDivElement>(() =>
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<ModelName>
Expand All @@ -53,65 +52,58 @@ const MultiSelectWidget = (props: Props) => {
// @ts-expect-error
const fieldSortable = displayMode === "list" && !!fieldOptions?.orderField;

const Select = ({
children,
className,
}: PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) => (
<div className={twMerge("relative", className)} ref={containerRef}>
<select
name={name}
className={clsx(
"absolute inset-0 h-full w-full opacity-0",
props.disabled ? "cursor-not-allowed" : "cursor-pointer"
)}
disabled={props.disabled}
required={props.required}
onMouseDown={(e) => {
e.preventDefault();
if (!props.disabled) {
formContext.toggleOpen(name);
}
}}
>
{!(props.required && selectedValues.length === 0) && (
<option value={JSON.stringify(selectedValues)} />
)}
</select>
{children}
</div>
const select = (
<select
name={name}
className={clsx(
"absolute inset-0 h-full w-full opacity-0",
props.disabled ? "cursor-not-allowed" : "cursor-pointer"
)}
disabled={props.disabled}
required={props.required}
onMouseDown={(e) => {
e.preventDefault();
if (!props.disabled) {
formContext.toggleOpen(name);
}
}}
>
{!(props.required && selectedValues.length === 0) && (
<option value={JSON.stringify(selectedValues)} />
)}
</select>
);

return (
<div className="relative">
<div ref={containerRef}>
{displayMode === "select" && (
<Select>
<div
className={clsx(
"dark:bg-dark-nextadmin-background-subtle dark:ring-dark-nextadmin-border-strong text-nextadmin-content-inverted dark:text-dark-nextadmin-content-inverted dark:border-dark-nextadmin-border-default ring-nextadmin-border-default flex min-h-[38px] w-[100%] w-full appearance-none flex-wrap gap-x-1 gap-y-1 rounded-md border-0 border-gray-300 px-2 py-1.5 pr-10 text-sm placeholder-gray-500 shadow-sm ring-1 ring-inset transition-all duration-300 placeholder:text-gray-400 sm:leading-6",
{
"opacity-50": props.disabled,
}
)}
aria-disabled={props.disabled}
>
{formData?.map(
(value: any, index: number) =>
value && (
<MultiSelectItem
key={index}
label={value.label}
onRemoveClick={() => onRemoveClick(value.value)}
deletable={!props.disabled}
/>
)
)}
{!props.disabled && (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-3">
<DoubleArrow />
</div>
)}
</div>
</Select>
<div
className={clsx(
"dark:bg-dark-nextadmin-background-subtle dark:ring-dark-nextadmin-border-strong text-nextadmin-content-inverted dark:text-dark-nextadmin-content-inverted dark:border-dark-nextadmin-border-default ring-nextadmin-border-default flex min-h-[38px] w-[100%] w-full appearance-none flex-wrap gap-x-1 gap-y-1 rounded-md border-0 border-gray-300 px-2 py-1.5 pr-10 text-sm placeholder-gray-500 shadow-sm ring-1 ring-inset transition-all duration-300 placeholder:text-gray-400 sm:leading-6",
{
"cursor-not-allowed opacity-50": props.disabled,
}
)}
aria-disabled={props.disabled}
>
{select}
{formData?.map(
(value: any, index: number) =>
value && (
<MultiSelectItem
key={index}
label={value.label}
onRemoveClick={() => onRemoveClick(value.value)}
deletable={!props.disabled}
/>
)
)}
{!props.disabled && (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-3">
<DoubleArrow />
</div>
)}
</div>
)}
{displayMode === "list" && (
<div className="space-y-2">
Expand All @@ -123,15 +115,16 @@ const MultiSelectWidget = (props: Props) => {
sortable={fieldSortable}
onUpdateFormData={onChange}
/>
<Select className="max-w-fit">
<Button
aria-disabled={props.disabled}
type="button"
disabled={props.disabled}
>
{t("form.widgets.multiselect.select")}
</Button>
</Select>

<Button
aria-disabled={props.disabled}
type="button"
disabled={props.disabled}
className="relative"
>
{select}
{t("form.widgets.multiselect.select")}
</Button>
</div>
)}
{displayMode === "table" && (
Expand All @@ -142,15 +135,16 @@ const MultiSelectWidget = (props: Props) => {
onRemoveClick={onRemoveClick}
deletable={!props.disabled}
/>
<Select className="max-w-fit">
<Button
aria-disabled={props.disabled}
type="button"
disabled={props.disabled}
>
{t("form.widgets.multiselect.select")}
</Button>
</Select>

<Button
aria-disabled={props.disabled}
type="button"
disabled={props.disabled}
className="relative"
>
{select}
{t("form.widgets.multiselect.select")}
</Button>
</div>
)}

Expand Down
7 changes: 4 additions & 3 deletions packages/next-admin/src/components/inputs/SelectWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -28,8 +28,9 @@ const SelectWidget = ({
const enumOptions = options.enumOptions?.map(
(option: any) => option.value as Enumeration
);
const containerRef = useRef<HTMLDivElement>(null);
useCloseOnOutsideClick(containerRef, () => formContext.setOpen(false, name));
const containerRef = useCloseOnOutsideClick<HTMLDivElement>(() => {
formContext.setOpen(false, name);
});

const { basePath } = useConfig();

Expand Down
Loading

0 comments on commit 06ed22c

Please sign in to comment.