Skip to content

Commit

Permalink
Allow custom inputs in array fields (#517)
Browse files Browse the repository at this point in the history
* #510 allow custom input in array fields

* Add Changeset

* Upgrade upload-artifact for CI
  • Loading branch information
cregourd authored Jan 16, 2025
1 parent d41d2de commit 97ac9bf
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 34 deletions.
5 changes: 5 additions & 0 deletions .changeset/sharp-eggs-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@premieroctet/next-admin": patch
---

Allow custom inputs in array field (#510)
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
pnpm turbo test:e2e
cd apps/example && pnpm prisma db seed && cd -
BASE_URL=http://localhost:3000/pagerouter/admin pnpm test:e2e
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
Expand Down
30 changes: 19 additions & 11 deletions packages/next-admin/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import RjsfForm from "@rjsf/core";
import {
BaseInputTemplateProps,
ErrorSchema,
FieldProps,
FieldTemplateProps,
getSubmitButtonOptions,
ObjectFieldTemplateProps,
SubmitButtonProps,
SubmitButtonProps
} from "@rjsf/utils";
import validator from "@rjsf/validator-ajv8";
import clsx from "clsx";
Expand All @@ -25,7 +26,7 @@ import React, {
useEffect,
useMemo,
useRef,
useState,
useState
} from "react";
import { twMerge } from "tailwind-merge";
import ClientActionDialogProvider from "../context/ClientActionDialogContext";
Expand Down Expand Up @@ -71,11 +72,6 @@ const RichTextField = dynamic(() => import("./inputs/RichText/RichTextField"), {
ssr: false,
});

const fields: RjsfForm["props"]["fields"] = {
ArrayField,
NullField,
};

const widgets: RjsfForm["props"]["widgets"] = {
DateWidget: DateWidget,
DateTimeWidget: DateTimeWidget,
Expand Down Expand Up @@ -309,6 +305,18 @@ const Form = ({
[apiBasePath, id]

Check warning on line 305 in packages/next-admin/src/components/Form.tsx

View workflow job for this annotation

GitHub Actions / start

React Hook useCallback has missing dependencies: 'basePath', 'cleanAll', 'resource', 'router', 'setFormData', 'showMessage', and 't'. Either include them or remove the dependency array

Check warning on line 305 in packages/next-admin/src/components/Form.tsx

View workflow job for this annotation

GitHub Actions / Release

React Hook useCallback has missing dependencies: 'basePath', 'cleanAll', 'resource', 'router', 'setFormData', 'showMessage', and 't'. Either include them or remove the dependency array
);

const fields: RjsfForm["props"]["fields"] = {
ArrayField: (props: FieldProps) => {
const customInput = customInputs?.[props.name as Field<ModelName>]
const improvedCustomInput = customInput ? cloneElement(customInput, {
...customInput.props,
mode: edit ? "edit" : "create",
}) : undefined;
return ArrayField({ ...props, customInput: improvedCustomInput })
},
NullField,
};

const templates: RjsfForm["props"]["templates"] = {
FieldTemplate: (props: FieldTemplateProps) => {
const {
Expand Down Expand Up @@ -501,6 +509,9 @@ const Form = ({
</span>
) : null;
},
ButtonTemplates: {
SubmitButton: submitButton,
},
};

const CustomForm = forwardRef<HTMLFormElement, HTMLProps<HTMLFormElement>>(
Expand Down Expand Up @@ -548,10 +559,7 @@ const Form = ({
fields={fields}
disabled={allDisabled}
formContext={{ isPending, schema }}
templates={{
...templates,
ButtonTemplates: { SubmitButton: submitButton },
}}
templates={templates}
widgets={widgets}
ref={ref}
className="relative"
Expand Down
10 changes: 6 additions & 4 deletions packages/next-admin/src/components/inputs/ArrayField.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { FieldProps } from "@rjsf/utils";
import type { CustomInputProps, Enumeration, FormProps } from "../../types";
import MultiSelectWidget from "./MultiSelect/MultiSelectWidget";
import ScalarArrayField from "./ScalarArray/ScalarArrayField";
import type { Enumeration, FormProps, ModelName } from "../../types";

const ArrayField = (props: FieldProps) => {
const { formData, onChange, name, disabled, schema, required, formContext } =

const ArrayField = (props: FieldProps & { customInput?: React.ReactElement<CustomInputProps> }) => {
const { formData, onChange, name, disabled, schema, required, formContext, customInput } =
props;

const resourceDefinition: FormProps["schema"] = formContext.schema;

const field =
resourceDefinition.properties[
name as keyof typeof resourceDefinition.properties
name as keyof typeof resourceDefinition.properties
];

if (field?.__nextadmin?.kind === "scalar" && field?.__nextadmin?.isList) {
Expand All @@ -22,6 +23,7 @@ const ArrayField = (props: FieldProps) => {
onChange={onChange}
disabled={disabled ?? false}
schema={schema}
customInput={customInput}
/>
);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/next-admin/src/components/inputs/JsonField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useMemo } from "react";
import { useColorScheme } from "../../context/ColorSchemeContext";
import { CustomInputProps } from "../../types";

type Props = CustomInputProps;
type Props = Omit<CustomInputProps, "onFocus" | "onBlur">;

const JsonField = ({ value, onChange, name, disabled, required }: Props) => {
const { colorScheme } = useColorScheme();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { RJSFSchema } from "@rjsf/utils";
import Button from "../../radix/Button";
import { useI18n } from "../../../context/I18nContext";
import { DndContext, DragEndEvent } from "@dnd-kit/core";
import { SortableContext } from "@dnd-kit/sortable";
import { RJSFSchema } from "@rjsf/utils";
import { useEffect, useState } from "react";
import { useFormState } from "../../../context/FormStateContext";
import { useI18n } from "../../../context/I18nContext";
import { CustomInputProps } from "../../../types";
import Button from "../../radix/Button";
import ScalarArrayFieldItem from "./ScalarArrayFieldItem";
import clsx from "clsx";

type Scalar = number | string;

Expand All @@ -16,6 +16,7 @@ type Props = {
name: string;
disabled: boolean;
schema: RJSFSchema;
customInput?: React.ReactElement<CustomInputProps>;
};

const ScalarArrayField = ({
Expand All @@ -24,6 +25,7 @@ const ScalarArrayField = ({
name,
disabled,
schema,
customInput,
}: Props) => {
const { t } = useI18n();
const { setFieldDirty } = useFormState();
Expand Down Expand Up @@ -77,6 +79,7 @@ const ScalarArrayField = ({
id={value.id}
// @ts-expect-error
scalarType={schema.items.type}
customInput={customInput}
/>
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { ChangeEvent, cloneElement } from "react";
import { twMerge } from "tailwind-merge";
import { CustomInputProps } from "../../../types";
import BaseInput from "../BaseInput";
import DndItem from "../DndItem";

Expand All @@ -9,6 +12,7 @@ type Props = {
inputValue: string;
id: string;
scalarType: string;
customInput?: React.ReactElement<CustomInputProps>;
};

const ScalarArrayFieldItem = ({
Expand All @@ -18,18 +22,16 @@ const ScalarArrayFieldItem = ({
onRemoveClick,
id,
scalarType,
customInput = <BaseInput />,
}: Props) => {
const renderInput = () => {
return (
<BaseInput
value={value}
onChange={(evt) => onChange(evt.target.value)}
disabled={disabled}
className="w-full"
type={scalarType === "number" ? "number" : "text"}
/>
);
};
const renderInput = () => cloneElement(customInput, {
...customInput.props,
value,
onChange: (evt: ChangeEvent<HTMLInputElement>) => onChange(evt.target.value),
disabled,
className: twMerge("w-full", customInput.props.className),
type: scalarType === "number" ? "number" : "text",
});

return (
<DndItem
Expand Down
4 changes: 2 additions & 2 deletions packages/next-admin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as OutlineIcons from "@heroicons/react/24/outline";
import { Prisma, PrismaClient } from "@prisma/client";
import { NextApiRequest } from "next";
import { NextRequest, NextResponse } from "next/server";
import type { ChangeEvent, ReactNode } from "react";
import type { ChangeEvent, ComponentProps, ReactNode } from "react";
import type { PropertyValidationError } from "./exceptions/ValidationError";
import type { NextAdminJSONSchema } from "@premieroctet/next-admin-json-schema";
import type React from "react";
Expand Down Expand Up @@ -937,7 +937,7 @@ export type CustomInputProps = Partial<{
disabled: boolean;
required?: boolean;
mode: "create" | "edit";
}>;
}> & ComponentProps<"input">;

export type TranslationKeys =
| "actions.delete.label"
Expand Down

0 comments on commit 97ac9bf

Please sign in to comment.