Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(web): enhance validator of required multiple values #1328

Merged
merged 4 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions web/e2e/project/item/fields/markdown.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ test("Markdown field editing has succeeded", async ({ page }) => {
await page.getByRole("button", { name: "delete" }).first().click();
await expect(page.getByText("Please input field!")).toBeVisible();
await page.getByRole("button", { name: "plus New" }).click();
await page.getByRole("button", { name: "Save" }).click();
await closeNotification(page, false);
await expect(page.getByRole("button", { name: "Save" })).toBeDisabled();
await page.locator("div:nth-child(1) > .css-1ago99h").click();
await page.getByRole("textbox").fill("text");
await page.getByRole("button", { name: "plus New" }).click();
Expand Down
3 changes: 1 addition & 2 deletions web/e2e/project/item/fields/text.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ test("Text field editing has succeeded", async ({ page }) => {
await expect(page.getByText("Please input field!")).toBeVisible();
await page.getByRole("button", { name: "plus New" }).click();
await expect(page.getByText("/ 5")).toBeVisible();
await page.getByRole("button", { name: "Save" }).click();
await closeNotification(page, false);
await expect(page.getByRole("button", { name: "Save" })).toBeDisabled();
caichi-t marked this conversation as resolved.
Show resolved Hide resolved
await page.getByRole("textbox").nth(0).click();
await page.getByRole("textbox").nth(0).fill("text");
await page.getByRole("button", { name: "plus New" }).click();
Expand Down
3 changes: 1 addition & 2 deletions web/e2e/project/item/fields/textarea.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ test("Textarea field editing has succeeded", async ({ page }) => {
await page.getByRole("button", { name: "delete" }).first().click();
await expect(page.getByText("Please input field!")).toBeVisible();
await page.getByRole("button", { name: "plus New" }).click();
await page.getByRole("button", { name: "Save" }).click();
await closeNotification(page, false);
await expect(page.getByRole("button", { name: "Save" })).toBeDisabled();
await page.getByRole("textbox").nth(0).click();
await page.getByRole("textbox").nth(0).fill("text");
await page.getByRole("button", { name: "plus New" }).click();
Expand Down
3 changes: 2 additions & 1 deletion web/src/components/atoms/Form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Form, FormInstance } from "antd";
import { Rule } from "antd/lib/form";
import { Rule, RuleObject } from "antd/lib/form";
import { FormItemProps } from "antd/lib/form/FormItem";
import { FormItemLabelProps } from "antd/lib/form/FormItemLabel";
import { FieldError, ValidateErrorEntity } from "rc-field-form/lib/interface";
Expand All @@ -12,5 +12,6 @@ export type {
FieldError,
FormInstance,
Rule,
RuleObject,
ValidateErrorEntity,
};
44 changes: 25 additions & 19 deletions web/src/components/atoms/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,32 @@ type Props = {
isError?: boolean;
} & InputProps;

const Input = forwardRef<InputRef, Props>(({ value, isError, maxLength, ...props }, ref) => {
const status = useMemo(() => {
if (isError || (maxLength && value && runes(value).length > maxLength)) {
return "error";
}
}, [isError, maxLength, value]);
const Input = forwardRef<InputRef, Props>(
({ value, isError, maxLength, required, ...props }, ref) => {
const status = useMemo(() => {
if (
isError ||
(required && !value) ||
(maxLength && value && runes(value).length > maxLength)
) {
return "error";
}
}, [isError, maxLength, required, value]);
caichi-t marked this conversation as resolved.
Show resolved Hide resolved

return (
<AntDInput
count={{
max: maxLength,
strategy: txt => runes(txt).length,
}}
value={value}
ref={ref}
status={status}
{...props}
/>
);
});
return (
<AntDInput
count={{
max: maxLength,
strategy: txt => runes(txt).length,
}}
value={value}
ref={ref}
status={status}
{...props}
/>
);
},
);

export default Input;
export type { InputProps };
14 changes: 10 additions & 4 deletions web/src/components/atoms/Markdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,21 @@ import TextArea, { TextAreaProps } from "@reearth-cms/components/atoms/TextArea"
type Props = {
value?: string;
onChange?: (value: string) => void;
isError?: boolean;
} & TextAreaProps;

const MarkdownInput: React.FC<Props> = ({ value, onChange, ...props }) => {
const [showMD, setShowMD] = useState(true);
const textareaRef = useRef<HTMLInputElement>(null);
const isError = useMemo(
() => (props.maxLength && value ? runes(value).length > props.maxLength : false),
[props.maxLength, value],
);
const isError = useMemo(() => {
if (props.isError || (props.required && !value)) {
return true;
} else if (props.maxLength && value) {
return runes(value).length > props.maxLength;
} else {
return false;
}
}, [props, value]);

const handleBlur = useCallback((event: FocusEvent<HTMLTextAreaElement>) => {
event.stopPropagation();
Expand Down
45 changes: 26 additions & 19 deletions web/src/components/atoms/TextArea/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,35 @@ import { runes } from "runes2";

type Props = {
value?: string;
isError?: boolean;
} & TextAreaProps;

const TextArea = forwardRef<HTMLInputElement, Props>(({ value, maxLength, ...props }, ref) => {
const status = useMemo(() => {
if (maxLength && value && runes(value).length > maxLength) {
return "error";
}
}, [maxLength, value]);
const TextArea = forwardRef<HTMLInputElement, Props>(
({ value, isError, maxLength, required, ...props }, ref) => {
const status = useMemo(() => {
if (
isError ||
(required && !value) ||
(maxLength && value && runes(value).length > maxLength)
) {
return "error";
}
}, [required, isError, maxLength, value]);

return (
<AntTextArea
count={{
max: maxLength,
strategy: txt => runes(txt).length,
}}
value={value}
ref={ref}
status={status}
{...props}
/>
);
});
return (
<AntTextArea
count={{
max: maxLength,
strategy: txt => runes(txt).length,
}}
value={value}
ref={ref}
status={status}
{...props}
/>
);
},
);

export default TextArea;
export type { TextAreaProps };
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Button from "@reearth-cms/components/atoms/Button";
import Icon from "@reearth-cms/components/atoms/Icon";
import { InputProps } from "@reearth-cms/components/atoms/Input";
import { TextAreaProps } from "@reearth-cms/components/atoms/TextArea";
import { checkIfEmpty } from "@reearth-cms/components/molecules/Content/Form/fields/utils";
import { useT } from "@reearth-cms/i18n";

import { moveItemInArray } from "./moveItemArray";
Expand All @@ -26,6 +27,7 @@ const MultiValueField: React.FC<Props> = ({
onBlur,
FieldInput,
errorIndexes,
required,
...props
}) => {
const t = useT();
Expand Down Expand Up @@ -93,7 +95,7 @@ const MultiValueField: React.FC<Props> = ({
onChange={(e: ChangeEvent<HTMLInputElement>) => handleInput(e, key)}
onBlur={() => onBlur?.()}
value={valueItem}
isError={errorIndexes?.has(key)}
isError={(required && value.every(v => checkIfEmpty(v))) || errorIndexes?.has(key)}
/>
{!props.disabled && (
<FieldButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { ItemAsset } from "@reearth-cms/components/molecules/Content/types";
import { Field } from "@reearth-cms/components/molecules/Schema/types";
import { useT } from "@reearth-cms/i18n";

import { requiredValidator } from "../utils";

type AssetFieldProps = {
field: Field;
itemGroupId?: string;
Expand Down Expand Up @@ -74,6 +76,7 @@ const AssetField: React.FC<AssetFieldProps> = ({
rules={[
{
required: field.required,
validator: requiredValidator,
message: t("Please input field!"),
},
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Field } from "@reearth-cms/components/molecules/Schema/types";
import { useT } from "@reearth-cms/i18n";

import FieldTitle from "../../FieldTitle";
import { requiredValidator } from "../utils";

type DateFieldProps = {
field: Field;
Expand All @@ -24,6 +25,7 @@ const DateField: React.FC<DateFieldProps> = ({ field, itemGroupId, onMetaUpdate,
rules={[
{
required: field.required,
validator: requiredValidator,
message: t("Please input field!"),
},
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Field } from "@reearth-cms/components/molecules/Schema/types";
import { useT } from "@reearth-cms/i18n";

import FieldTitle from "../../FieldTitle";
import { requiredValidator } from "../utils";

type DefaultFieldProps = {
field: Field;
Expand All @@ -25,13 +26,16 @@ const DefaultField: React.FC<DefaultFieldProps> = ({
const t = useT();
const maxLength = useMemo(() => field.typeProperty?.maxLength, [field.typeProperty?.maxLength]);

const required = useMemo(() => field.required, [field.required]);

return (
<Form.Item
extra={field.description}
validateStatus="success"
rules={[
{
required: field.required,
required,
validator: requiredValidator,
caichi-t marked this conversation as resolved.
Show resolved Hide resolved
message: t("Please input field!"),
},
{
Expand Down Expand Up @@ -59,9 +63,16 @@ const DefaultField: React.FC<DefaultFieldProps> = ({
maxLength={maxLength}
FieldInput={Input}
disabled={disabled}
required={required}
/>
) : (
<Input onBlur={onMetaUpdate} showCount={true} maxLength={maxLength} disabled={disabled} />
<Input
onBlur={onMetaUpdate}
showCount={true}
maxLength={maxLength}
disabled={disabled}
required={required}
/>
)}
</Form.Item>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Field } from "@reearth-cms/components/molecules/Schema/types";
import { useT } from "@reearth-cms/i18n";

import FieldTitle from "../../FieldTitle";
import { requiredValidator } from "../utils";

type DefaultFieldProps = {
field: Field;
Expand Down Expand Up @@ -39,6 +40,7 @@ const GeometryField: React.FC<DefaultFieldProps> = ({ field, itemGroupId, disabl
rules={[
{
required: field.required,
validator: requiredValidator,
message: t("Please input field!"),
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Field } from "@reearth-cms/components/molecules/Schema/types";
import { useT } from "@reearth-cms/i18n";

import FieldTitle from "../../FieldTitle";
import { requiredValidator } from "../utils";

type DefaultFieldProps = {
field: Field;
Expand All @@ -19,13 +20,16 @@ const MarkdownField: React.FC<DefaultFieldProps> = ({ field, itemGroupId, disabl
const t = useT();
const maxLength = useMemo(() => field.typeProperty?.maxLength, [field.typeProperty?.maxLength]);

const required = useMemo(() => field.required, [field.required]);

return (
<Form.Item
extra={field.description}
validateStatus="success"
rules={[
{
required: field.required,
required,
validator: requiredValidator,
message: t("Please input field!"),
},
{
Expand All @@ -47,9 +51,14 @@ const MarkdownField: React.FC<DefaultFieldProps> = ({ field, itemGroupId, disabl
name={itemGroupId ? [field.id, itemGroupId] : field.id}
label={<FieldTitle title={field.title} isUnique={field.unique} isTitle={field.isTitle} />}>
{field.multiple ? (
<MultiValueField maxLength={maxLength} FieldInput={MarkdownInput} disabled={disabled} />
<MultiValueField
maxLength={maxLength}
FieldInput={MarkdownInput}
disabled={disabled}
required={required}
/>
) : (
<MarkdownInput maxLength={maxLength} disabled={disabled} />
<MarkdownInput maxLength={maxLength} disabled={disabled} required={required} />
)}
</Form.Item>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Field } from "@reearth-cms/components/molecules/Schema/types";
import { useT } from "@reearth-cms/i18n";

import FieldTitle from "../../FieldTitle";
import { requiredValidator } from "../utils";

type DefaultFieldProps = {
field: Field;
Expand Down Expand Up @@ -42,6 +43,7 @@ const NumberField: React.FC<DefaultFieldProps> = ({ field, itemGroupId, disabled
rules={[
{
required: field.required,
validator: requiredValidator,
message: t("Please input field!"),
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Field } from "@reearth-cms/components/molecules/Schema/types";
import { useT } from "@reearth-cms/i18n";

import FieldTitle from "../../FieldTitle";
import { requiredValidator } from "../utils";

type DefaultFieldProps = {
field: Field;
Expand All @@ -24,6 +25,7 @@ const SelectField: React.FC<DefaultFieldProps> = ({ field, itemGroupId, disabled
rules={[
{
required: field.required,
validator: requiredValidator,
message: t("Please select an option!"),
},
]}>
Expand Down
Loading
Loading