From 468c8161eb14a099b4cc3f0238c746f79919c9d6 Mon Sep 17 00:00:00 2001 From: Nathan Vasse Date: Tue, 29 Aug 2023 16:18:30 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D(react)=20add=20RHF=20examples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework a bit the recent work made on RHF example to make some component more generic, such as RhfSelect and RhfDatepicker, which is based on a design using RHF context hooks to provide a seamless integration. Fixes #144 --- .changeset/chilly-ways-dance.md | 5 + packages/react/.eslintrc.cjs | 2 + .../Forms/Checkbox/index.stories.tsx | 6 +- .../Forms/DatePicker/index.stories.tsx | 37 +++ .../Forms/DatePicker/stories-utils.tsx | 27 ++ .../Examples/ReactHookForm/Login.stories.tsx | 4 +- .../Examples/ReactHookForm/Sports.stories.tsx | 295 +++++++----------- .../ReactHookForm}/reactHookFormUtils.tsx | 0 .../components/Forms/Input/index.stories.tsx | 88 +++--- .../components/Forms/Radio/index.stories.tsx | 6 +- .../components/Forms/Select/mono.stories.tsx | 156 ++++----- .../components/Forms/Select/multi.stories.tsx | 184 +++++------ .../components/Forms/Select/stories-utils.tsx | 26 ++ .../components/Forms/Switch/index.stories.tsx | 92 +++--- 14 files changed, 460 insertions(+), 468 deletions(-) create mode 100644 .changeset/chilly-ways-dance.md create mode 100644 packages/react/src/components/Forms/DatePicker/stories-utils.tsx rename packages/react/src/{tests => components/Forms/Examples/ReactHookForm}/reactHookFormUtils.tsx (100%) create mode 100644 packages/react/src/components/Forms/Select/stories-utils.tsx diff --git a/.changeset/chilly-ways-dance.md b/.changeset/chilly-ways-dance.md new file mode 100644 index 0000000..ec83d44 --- /dev/null +++ b/.changeset/chilly-ways-dance.md @@ -0,0 +1,5 @@ +--- +"@openfun/cunningham-react": minor +--- + +add RHF examples diff --git a/packages/react/.eslintrc.cjs b/packages/react/.eslintrc.cjs index 2cbf790..7dcbfa9 100644 --- a/packages/react/.eslintrc.cjs +++ b/packages/react/.eslintrc.cjs @@ -17,6 +17,8 @@ module.exports = { "**/*.stories.tsx", "**/*.spec.tsx", "src/tests/*", + "**/stories-utils.tsx", + "**/reactHookFormUtils.tsx" ], }, ], diff --git a/packages/react/src/components/Forms/Checkbox/index.stories.tsx b/packages/react/src/components/Forms/Checkbox/index.stories.tsx index 9170e1c..41ffd1f 100644 --- a/packages/react/src/components/Forms/Checkbox/index.stories.tsx +++ b/packages/react/src/components/Forms/Checkbox/index.stories.tsx @@ -3,13 +3,13 @@ import { yupResolver } from "@hookform/resolvers/yup"; import { Meta, StoryFn } from "@storybook/react"; import { useForm } from "react-hook-form"; import * as Yup from "yup"; -import { Checkbox, CheckboxGroup } from ":/components/Forms/Checkbox/index"; -import { Button } from ":/components/Button"; import { getFieldState, getFieldErrorMessage, onSubmit, -} from ":/tests/reactHookFormUtils"; +} from ":/components/Forms/Examples/ReactHookForm/reactHookFormUtils"; +import { Checkbox, CheckboxGroup } from ":/components/Forms/Checkbox/index"; +import { Button } from ":/components/Button"; export default { title: "Components/Forms/Checkbox", diff --git a/packages/react/src/components/Forms/DatePicker/index.stories.tsx b/packages/react/src/components/Forms/DatePicker/index.stories.tsx index d06accf..5520852 100644 --- a/packages/react/src/components/Forms/DatePicker/index.stories.tsx +++ b/packages/react/src/components/Forms/DatePicker/index.stories.tsx @@ -1,9 +1,14 @@ import { Meta, StoryFn } from "@storybook/react"; import React, { useState } from "react"; +import * as Yup from "yup"; +import { FormProvider, useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; import { CunninghamProvider } from ":/components/Provider"; import { Button } from ":/components/Button"; import { DateRangePicker } from ":/components/Forms/DatePicker/DateRangePicker"; import { DatePicker } from ":/components/Forms/DatePicker/DatePicker"; +import { onSubmit } from ":/components/Forms/Examples/ReactHookForm/reactHookFormUtils"; +import { RhfDatePicker } from ":/components/Forms/DatePicker/stories-utils"; export default { title: "Components/Forms/DatePicker", @@ -135,6 +140,38 @@ export const Controlled = () => { ); }; +export const ReactHookForm = () => { + const methods = useForm({ + defaultValues: { + date: "", + }, + resolver: yupResolver( + Yup.object().shape({ + date: Yup.string().required(), + }), + ), + }); + + return ( + + +
+ + + +
+
+ ); +}; + export const RangeDefault = () => { return (
diff --git a/packages/react/src/components/Forms/DatePicker/stories-utils.tsx b/packages/react/src/components/Forms/DatePicker/stories-utils.tsx new file mode 100644 index 0000000..580aab3 --- /dev/null +++ b/packages/react/src/components/Forms/DatePicker/stories-utils.tsx @@ -0,0 +1,27 @@ +import { Controller, useFormContext } from "react-hook-form"; +import React from "react"; +import { + DatePicker, + DatePickerProps, +} from ":/components/Forms/DatePicker/DatePicker"; + +export const RhfDatePicker = (props: DatePickerProps & { name: string }) => { + const { control } = useFormContext(); + return ( + { + return ( + + ); + }} + /> + ); +}; diff --git a/packages/react/src/components/Forms/Examples/ReactHookForm/Login.stories.tsx b/packages/react/src/components/Forms/Examples/ReactHookForm/Login.stories.tsx index 8aae6e2..7d40f60 100644 --- a/packages/react/src/components/Forms/Examples/ReactHookForm/Login.stories.tsx +++ b/packages/react/src/components/Forms/Examples/ReactHookForm/Login.stories.tsx @@ -10,10 +10,10 @@ import { getFieldState, getFieldErrorMessage, onSubmit, -} from ":/tests/reactHookFormUtils"; +} from "./reactHookFormUtils"; export default { - title: "Components/Forms/Reac-Hook-Form", + title: "Components/Forms/Examples/React-Hook-Form", } as Meta; interface LoginStoryFormValues { diff --git a/packages/react/src/components/Forms/Examples/ReactHookForm/Sports.stories.tsx b/packages/react/src/components/Forms/Examples/ReactHookForm/Sports.stories.tsx index 383a49a..f978833 100644 --- a/packages/react/src/components/Forms/Examples/ReactHookForm/Sports.stories.tsx +++ b/packages/react/src/components/Forms/Examples/ReactHookForm/Sports.stories.tsx @@ -1,218 +1,153 @@ import { yupResolver } from "@hookform/resolvers/yup"; import { Meta } from "@storybook/react"; import React from "react"; -import { useForm, Controller, ControllerRenderProps } from "react-hook-form"; +import { useForm, FormProvider } from "react-hook-form"; import * as Yup from "yup"; import { Input } from ":/components/Forms/Input"; import { Button } from ":/components/Button"; -import { Select } from ":/components/Forms/Select"; import { CunninghamProvider } from ":/components/Provider"; import { Radio, RadioGroup } from ":/components/Forms/Radio"; +import { RhfSelect } from ":/components/Forms/Select/stories-utils"; import { getFieldState, getFieldErrorMessage, onSubmit, -} from ":/tests/reactHookFormUtils"; +} from "./reactHookFormUtils"; export default { - title: "Components/Forms/Reac-Hook-Form", + title: "Components/Forms/Examples/React-Hook-Form", } as Meta; -enum GenderEnum { - MALE = "male", - FEMALE = "female", - OTHER = "other", -} -enum CompetitionEnum { - ATHLETICS = "Athletics", - SWIMMING = "Swimming", - MARATHON = "Marathon", -} -enum RewardEnum { - BRONZE = "bronze", - SILVER = "silver", - GOLD = "gold", - FLOCON = "flocon", - OURSON = "ourson", - CHAMOIS = "chamois", -} - interface SportsStoryFormValues { firstName: string; lastName: string; - gender: GenderEnum; - competition: CompetitionEnum; - rewards: RewardEnum[]; + gender: string; + competition: string; + rewards: string[]; } const sportsSchema = Yup.object().shape({ firstName: Yup.string().required(), lastName: Yup.string().required(), - gender: Yup.string().required(), - competition: Yup.string() - .defined() - .required() - .oneOf(Object.values(CompetitionEnum)), - rewards: Yup.array().of(Yup.string().defined()).defined(), + gender: Yup.string().required(), + competition: Yup.string().defined().required(), + rewards: Yup.array().of(Yup.string().defined()).defined(), }); export const Sports = () => { - const { register, handleSubmit, formState, control } = - useForm({ - defaultValues: { - firstName: "", - lastName: "", - rewards: [], - }, - mode: "onChange", - reValidateMode: "onChange", - resolver: yupResolver(sportsSchema), - }); - - const renderCompetitionSelect = ({ - field, - }: { - field: ControllerRenderProps; - }) => { - return ( - - ); - }; + const methods = useForm({ + defaultValues: { + firstName: "", + lastName: "", + rewards: [], + }, + mode: "onChange", + reValidateMode: "onChange", + resolver: yupResolver(sportsSchema), + }); return ( -
-

+ - Register -

-
- - -
+

+ Register +

+
+ + +
- - + + + + + - - - - - - - - + + + +
); }; diff --git a/packages/react/src/tests/reactHookFormUtils.tsx b/packages/react/src/components/Forms/Examples/ReactHookForm/reactHookFormUtils.tsx similarity index 100% rename from packages/react/src/tests/reactHookFormUtils.tsx rename to packages/react/src/components/Forms/Examples/ReactHookForm/reactHookFormUtils.tsx diff --git a/packages/react/src/components/Forms/Input/index.stories.tsx b/packages/react/src/components/Forms/Input/index.stories.tsx index 78a07bc..96d2fdc 100644 --- a/packages/react/src/components/Forms/Input/index.stories.tsx +++ b/packages/react/src/components/Forms/Input/index.stories.tsx @@ -3,13 +3,13 @@ import { Meta } from "@storybook/react"; import { useForm } from "react-hook-form"; import * as Yup from "yup"; import React, { useRef } from "react"; -import { Input } from ":/components/Forms/Input/index"; -import { Button } from ":/components/Button"; import { getFieldState, getFieldErrorMessage, onSubmit, -} from ":/tests/reactHookFormUtils"; +} from ":/components/Forms/Examples/ReactHookForm/reactHookFormUtils"; +import { Input } from ":/components/Forms/Input/index"; +import { Button } from ":/components/Button"; export default { title: "Components/Forms/Input", @@ -175,47 +175,6 @@ export const WithRef = () => { ); }; -export const ReactHookForm = () => { - interface InputExampleFormValues { - email: string; - } - - const inputExampleSchema = Yup.object().shape({ - email: Yup.string().email().required(), - }); - const { register, handleSubmit, formState } = useForm( - { - defaultValues: { - email: "", - }, - mode: "onChange", - reValidateMode: "onChange", - resolver: yupResolver(inputExampleSchema), - }, - ); - - return ( -
- - -
- ); -}; - export const FormExample = () => { return (
@@ -268,3 +227,44 @@ export const FormExample = () => {
); }; + +export const ReactHookForm = () => { + interface InputExampleFormValues { + email: string; + } + + const inputExampleSchema = Yup.object().shape({ + email: Yup.string().email().required(), + }); + const { register, handleSubmit, formState } = useForm( + { + defaultValues: { + email: "", + }, + mode: "onChange", + reValidateMode: "onChange", + resolver: yupResolver(inputExampleSchema), + }, + ); + + return ( +
+ + +
+ ); +}; diff --git a/packages/react/src/components/Forms/Radio/index.stories.tsx b/packages/react/src/components/Forms/Radio/index.stories.tsx index 83b8818..1f04b48 100644 --- a/packages/react/src/components/Forms/Radio/index.stories.tsx +++ b/packages/react/src/components/Forms/Radio/index.stories.tsx @@ -3,13 +3,13 @@ import { useForm } from "react-hook-form"; import * as Yup from "yup"; import { yupResolver } from "@hookform/resolvers/yup"; import { Meta, StoryFn } from "@storybook/react"; -import { Radio, RadioGroup } from ":/components/Forms/Radio/index"; -import { Button } from ":/components/Button"; import { getFieldState, getFieldErrorMessage, onSubmit, -} from ":/tests/reactHookFormUtils"; +} from ":/components/Forms/Examples/ReactHookForm/reactHookFormUtils"; +import { Radio, RadioGroup } from ":/components/Forms/Radio/index"; +import { Button } from ":/components/Button"; export default { title: "Components/Forms/Radio", diff --git a/packages/react/src/components/Forms/Select/mono.stories.tsx b/packages/react/src/components/Forms/Select/mono.stories.tsx index 40dd5ec..8a3ba96 100644 --- a/packages/react/src/components/Forms/Select/mono.stories.tsx +++ b/packages/react/src/components/Forms/Select/mono.stories.tsx @@ -1,17 +1,14 @@ import { Meta, StoryFn } from "@storybook/react"; import React, { useState } from "react"; -import { useForm, Controller, ControllerRenderProps } from "react-hook-form"; +import { useForm, FormProvider } from "react-hook-form"; import * as Yup from "yup"; import { faker } from "@faker-js/faker"; import { yupResolver } from "@hookform/resolvers/yup"; +import { onSubmit } from ":/components/Forms/Examples/ReactHookForm/reactHookFormUtils"; import { Select } from ":/components/Forms/Select"; import { Button } from ":/components/Button"; import { CunninghamProvider } from ":/components/Provider"; -import { - getFieldState, - getFieldErrorMessage, - onSubmit, -} from ":/tests/reactHookFormUtils"; +import { RhfSelect } from ":/components/Forms/Select/stories-utils"; export default { title: "Components/Forms/Select/Mono", @@ -164,87 +161,6 @@ export const SearchableControlled = () => { ); }; -export const ReactHookForm = () => { - enum CitiesOptionEnum { - NONE = "", - DIJON = "dijon", - PARIS = "paris", - TOKYO = "tokyo", - } - - interface SelectExampleFormValues { - joTown: CitiesOptionEnum; - } - - const selectExampleSchema = Yup.object().shape({ - joTown: Yup.string() - .required() - .oneOf([CitiesOptionEnum.PARIS], "That's not the right town!"), - }); - - const { handleSubmit, formState, control } = useForm( - { - defaultValues: { - joTown: CitiesOptionEnum.NONE, - }, - mode: "onChange", - reValidateMode: "onChange", - resolver: yupResolver(selectExampleSchema), - }, - ); - - const renderSelect = ({ - field, - }: { - field: ControllerRenderProps; - }) => { - return ( - <> -
Where will the 2024 Olympics take place?
- - - ); - }; - - return ( - -
- - - -
- ); -}; - export const FormExample = () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -409,3 +309,81 @@ export const FormExample = () => { ); }; + +export const ReactHookForm = () => { + enum CitiesOptionEnum { + NONE = "", + DIJON = "dijon", + PARIS = "paris", + TOKYO = "tokyo", + VANNES = "vannes", + } + + interface SelectExampleFormValues { + capitalCity: CitiesOptionEnum[]; + } + + const selectExampleSchema = Yup.object().shape({ + capitalCity: Yup.array() + .required() + .test({ + test: (cityList) => + cityList.every((city) => + [CitiesOptionEnum.PARIS, CitiesOptionEnum.TOKYO].includes(city), + ), + message: "Wrong answer!", + }), + }); + + const methods = useForm({ + defaultValues: { + capitalCity: [], + }, + mode: "onChange", + reValidateMode: "onChange", + resolver: yupResolver(selectExampleSchema), + }); + + return ( + + +
+
Which cities are capital of countries ?
+ + + +
+
+ ); +}; diff --git a/packages/react/src/components/Forms/Select/stories-utils.tsx b/packages/react/src/components/Forms/Select/stories-utils.tsx new file mode 100644 index 0000000..b8928de --- /dev/null +++ b/packages/react/src/components/Forms/Select/stories-utils.tsx @@ -0,0 +1,26 @@ +import { Controller, useFormContext } from "react-hook-form"; +import React from "react"; +import { Select } from ":/components/Forms/Select/index"; +import { SelectProps } from ":/components/Forms/Select/mono"; + +export const RhfSelect = (props: SelectProps & { name: string }) => { + const { control } = useFormContext(); + return ( + { + return ( +