diff --git a/src/components/item/form/AppForm.tsx b/src/components/item/form/AppForm.tsx index d00c9fd92..f6cc0369a 100644 --- a/src/components/item/form/AppForm.tsx +++ b/src/components/item/form/AppForm.tsx @@ -1,5 +1,5 @@ import { ChangeEventHandler, useEffect, useState } from 'react'; -import { useForm } from 'react-hook-form'; +import { FormProvider, useForm } from 'react-hook-form'; import { ArrowBack } from '@mui/icons-material'; import { @@ -22,7 +22,7 @@ import { useBuilderTranslation } from '../../../config/i18n'; import { hooks } from '../../../config/queryClient'; import { BUILDER } from '../../../langs/constants'; import addNewImage from '../../../resources/addNew.png'; -import NameForm from './NameForm'; +import { ItemNameField } from './ItemNameField'; type AppGridProps = { currentUrl: string; @@ -101,7 +101,8 @@ const AppForm = ({ onChange }: Props): JSX.Element => { const [searchQuery, setSearchQuery] = useState(''); - const { register, setValue, watch, reset } = useForm(); + const methods = useForm(); + const { register, setValue, watch, reset } = methods; const url = watch('url'); const name = watch('name'); @@ -143,66 +144,76 @@ const AppForm = ({ onChange }: Props): JSX.Element => { if (isCustomApp) { return ( - - - {translateBuilder(BUILDER.CREATE_CUSTOM_APP_HELPER_TEXT)} - - - + + + + {translateBuilder(BUILDER.CREATE_CUSTOM_APP_HELPER_TEXT)} + + + + ); } return ( - - - + + - - - - - + + + + + + + ); }; diff --git a/src/components/item/form/BaseItemForm.tsx b/src/components/item/form/BaseItemForm.tsx index b836b6ba1..290c4045e 100644 --- a/src/components/item/form/BaseItemForm.tsx +++ b/src/components/item/form/BaseItemForm.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { useForm } from 'react-hook-form'; +import { FormProvider, useForm } from 'react-hook-form'; import { Box } from '@mui/material'; @@ -7,7 +7,7 @@ import { DescriptionPlacementType, DiscriminatedItem } from '@graasp/sdk'; import { FOLDER_FORM_DESCRIPTION_ID } from '../../../config/selectors'; import DescriptionForm from './DescriptionForm'; -import NameForm from './NameForm'; +import { ItemNameField } from './ItemNameField'; type Inputs = { name: string; @@ -19,11 +19,11 @@ const BaseItemForm = ({ item, setChanges, }: { - item?: DiscriminatedItem; + item: DiscriminatedItem; setChanges: (payload: Partial) => void; }): JSX.Element => { - const { register, watch, setValue } = useForm(); - + const methods = useForm({ defaultValues: { name: item.name } }); + const { watch, setValue } = methods; const name = watch('name'); const descriptionPlacement = watch('descriptionPlacement'); const description = watch('description'); @@ -40,23 +40,25 @@ const BaseItemForm = ({ return ( - + + - - - setValue('descriptionPlacement', newValue) - } - onDescriptionChange={(newValue) => { - setValue('description', newValue); - }} - /> - + + + setValue('descriptionPlacement', newValue) + } + onDescriptionChange={(newValue) => { + setValue('description', newValue); + }} + /> + + ); }; diff --git a/src/components/item/form/FileForm.tsx b/src/components/item/form/FileForm.tsx index 937bdbefb..bbe1a113e 100644 --- a/src/components/item/form/FileForm.tsx +++ b/src/components/item/form/FileForm.tsx @@ -1,5 +1,5 @@ import { ReactNode } from 'react'; -import { useForm } from 'react-hook-form'; +import { FormProvider, useForm } from 'react-hook-form'; import { Box, @@ -32,7 +32,7 @@ import { getExtraFromPartial } from '@/utils/itemExtra'; import { BUILDER } from '../../../langs/constants'; import DescriptionForm from './DescriptionForm'; -import NameForm from './NameForm'; +import { ItemNameField } from './ItemNameField'; type Inputs = { name: string; @@ -50,14 +50,14 @@ const FileForm = ({ }): ReactNode => { const { t: translateBuilder } = useBuilderTranslation(); const { t: translateCommon } = useCommonTranslation(); + const methods = useForm({ defaultValues: { name: item.name } }); const { register, watch, setValue, handleSubmit, - reset, - formState: { errors, isValid }, - } = useForm(); + formState: { isValid }, + } = methods; const altText = watch('altText'); const description = watch('description'); const descriptionPlacement = watch('descriptionPlacement'); @@ -108,59 +108,56 @@ const FileForm = ({ } return ( - - - reset({ name: '' })} - /> - {mimetype && MimeTypes.isImage(mimetype) && ( - - )} - - setValue('descriptionPlacement', newValue) - } - onDescriptionChange={(newValue) => { - setValue('description', newValue); + + + - - - - - - + + {mimetype && MimeTypes.isImage(mimetype) && ( + + )} + + setValue('descriptionPlacement', newValue) + } + onDescriptionChange={(newValue) => { + setValue('description', newValue); + }} + description={description ?? item?.description ?? ''} + descriptionPlacement={ + descriptionPlacement ?? item?.settings?.descriptionPlacement + } + /> + + + + + + + ); }; diff --git a/src/components/item/form/FolderForm.tsx b/src/components/item/form/FolderForm.tsx index 0d19ad2eb..f70340f98 100644 --- a/src/components/item/form/FolderForm.tsx +++ b/src/components/item/form/FolderForm.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { useForm } from 'react-hook-form'; +import { FormProvider, useForm } from 'react-hook-form'; import { Stack } from '@mui/material'; @@ -8,7 +8,7 @@ import { DescriptionPlacementType, DiscriminatedItem } from '@graasp/sdk'; import { FOLDER_FORM_DESCRIPTION_ID } from '../../../config/selectors'; import ThumbnailCrop from '../../thumbnails/ThumbnailCrop'; import DescriptionForm from './DescriptionForm'; -import NameForm from './NameForm'; +import { ItemNameField } from './ItemNameField'; export type FolderFormProps = { item?: DiscriminatedItem; @@ -29,7 +29,8 @@ const FolderForm = ({ setChanges, showThumbnail = false, }: FolderFormProps): JSX.Element => { - const { register, setValue, watch } = useForm(); + const methods = useForm({ defaultValues: { name: item?.name } }); + const { setValue, watch } = methods; const description = watch('description'); const descriptionPlacement = watch('descriptionPlacement'); const name = watch('name'); @@ -46,29 +47,31 @@ const FolderForm = ({ return ( - - {showThumbnail && } - - - - setValue('descriptionPlacement', newValue) - } - onDescriptionChange={(newValue) => { - setValue('description', newValue); - }} - showPlacement={false} - descriptionPlacement={ - descriptionPlacement ?? item?.settings?.descriptionPlacement - } - /> + + + {showThumbnail && } + + + + setValue('descriptionPlacement', newValue) + } + onDescriptionChange={(newValue) => { + setValue('description', newValue); + }} + showPlacement={false} + descriptionPlacement={ + descriptionPlacement ?? item?.settings?.descriptionPlacement + } + /> + ); }; diff --git a/src/components/item/form/NameForm.tsx b/src/components/item/form/ItemNameField.tsx similarity index 66% rename from src/components/item/form/NameForm.tsx rename to src/components/item/form/ItemNameField.tsx index bf53aa681..effab6238 100644 --- a/src/components/item/form/NameForm.tsx +++ b/src/components/item/form/ItemNameField.tsx @@ -1,48 +1,32 @@ -import { FieldError, UseFormRegisterReturn } from 'react-hook-form'; +import { useFormContext } from 'react-hook-form'; import ClearIcon from '@mui/icons-material/Clear'; import { IconButton, TextField } from '@mui/material'; -import { DiscriminatedItem } from '@graasp/sdk'; - import { useBuilderTranslation } from '../../../config/i18n'; import { ITEM_FORM_NAME_INPUT_ID } from '../../../config/selectors'; import { BUILDER } from '../../../langs/constants'; -export type NameFormProps = { - /** - * @deprecated use nameForm - */ - item?: DiscriminatedItem; - /** - * @deprecated use nameForm - */ - setChanges?: (payload: Partial) => void; -} & { +export type ItemNameFieldProps = { required?: boolean; - /** - * @deprecated use nameForm - */ - name?: string; autoFocus?: boolean; - nameForm: UseFormRegisterReturn; - reset?: () => void; - error?: FieldError; showClearButton?: boolean; }; -const NameForm = ({ - nameForm, +export const ItemNameField = ({ required, autoFocus = true, - reset, - error, showClearButton, -}: NameFormProps): JSX.Element => { +}: ItemNameFieldProps): JSX.Element => { + const { + register, + reset, + formState: { errors }, + } = useFormContext<{ name: string }>(); const { t: translateBuilder } = useBuilderTranslation(); const handleClearClick = () => { - reset?.(); + reset({ name: '' }); }; return ( ); }; - -export default NameForm; diff --git a/src/components/item/form/document/DocumentForm.tsx b/src/components/item/form/document/DocumentForm.tsx index bee943ccd..83b39f6aa 100644 --- a/src/components/item/form/document/DocumentForm.tsx +++ b/src/components/item/form/document/DocumentForm.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { useForm } from 'react-hook-form'; +import { FormProvider, useForm } from 'react-hook-form'; import { Box, Stack } from '@mui/material'; @@ -15,7 +15,7 @@ import { ITEM_FORM_DOCUMENT_TEXT_ID } from '@/config/selectors'; import { BUILDER } from '@/langs/constants'; import type { EditModalContentPropType } from '../../edit/EditModal'; -import NameForm from '../NameForm'; +import { ItemNameField } from '../ItemNameField'; import { DocumentContentForm, DocumentExtraFormInputs, @@ -40,7 +40,10 @@ const DocumentForm = ({ item?.extra?.[ItemType.DOCUMENT]?.isRaw ?? false, ); - const { register, watch, control, setValue, reset } = useForm(); + const methods = useForm({ + defaultValues: { name: item?.name }, + }); + const { register, watch, control, setValue } = methods; const name = watch('name'); const content = watch('content'); const flavor = watch('flavor'); @@ -55,33 +58,31 @@ const DocumentForm = ({ return ( - - reset({ name: '' })} + + + + + - - - setValue('content', v)} - setIsRaw={setIsRaw} - isRaw={isRaw} - content={content} - contentForm={register('content', { - value: item?.extra?.[ItemType.DOCUMENT]?.content || '', - })} - placeholder={translateBuilder(BUILDER.TEXT_EDITOR_PLACEHOLDER)} - /> + setValue('content', v)} + setIsRaw={setIsRaw} + isRaw={isRaw} + content={content} + contentForm={register('content', { + value: item?.extra?.[ItemType.DOCUMENT]?.content || '', + })} + placeholder={translateBuilder(BUILDER.TEXT_EDITOR_PLACEHOLDER)} + /> + ); }; diff --git a/src/components/item/form/link/LinkForm.tsx b/src/components/item/form/link/LinkForm.tsx index cdfb3e582..30a2fb712 100644 --- a/src/components/item/form/link/LinkForm.tsx +++ b/src/components/item/form/link/LinkForm.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo } from 'react'; -import { Controller, useForm } from 'react-hook-form'; +import { Controller, FormProvider, useForm } from 'react-hook-form'; import { FormControl, @@ -16,7 +16,6 @@ import { import { DiscriminatedItem, ItemType, - LinkItemType, UnionOfConst, buildLinkExtra, getLinkThumbnailUrl, @@ -28,14 +27,13 @@ import { hooks } from '@/config/queryClient'; import { useBuilderTranslation } from '../../../../config/i18n'; import { BUILDER } from '../../../../langs/constants'; import { isUrlValid } from '../../../../utils/item'; -import NameForm from '../NameForm'; +import { ItemNameField } from '../ItemNameField'; import LinkDescriptionField from './LinkDescriptionField'; import LinkUrlField from './LinkUrlField'; import { LinkType, getSettingsFromLinkType, normalizeURL } from './linkUtils'; type Props = { onChange: (item: Partial) => void; - item?: LinkItemType; }; const StyledFormControlLabel = styled(FormControlLabel)(({ theme }) => ({ @@ -64,9 +62,10 @@ type Inputs = { url: string; }; -const LinkForm = ({ onChange, item }: Props): JSX.Element => { +const LinkForm = ({ onChange }: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); - const { register, watch, control, setValue, reset } = useForm(); + const methods = useForm(); + const { register, watch, control, setValue, reset } = methods; const url = watch('url'); const name = watch('name'); @@ -131,85 +130,66 @@ const LinkForm = ({ onChange, item }: Props): JSX.Element => { return ( - reset({ url: '' })} - showClearButton={Boolean(url)} - isValid={isUrlValid(normalizeURL(url))} - /> - reset({ name: '' })} - /> - setValue('description', linkData?.description ?? '')} - form={register('description')} - onClear={() => reset({ description: '' })} - showRestoreButton={ - Boolean(description) && description !== linkData?.description - } - showClearButton={Boolean(description)} - /> - - - - {translateBuilder(BUILDER.CREATE_ITEM_LINK_TYPE_TITLE)} - - {url ? ( - ( - - {url}} - control={} - /> - - } - control={} - slotProps={{ - typography: { width: '100%', minWidth: '0px' }, - }} - sx={{ minWidth: '0px', width: '100%' }} - /> - {linkData?.html && linkData.html !== '' && ( + + reset({ url: '' })} + showClearButton={Boolean(url)} + isValid={isUrlValid(normalizeURL(url))} + /> + + setValue('description', linkData?.description ?? '')} + form={register('description')} + onClear={() => reset({ description: '' })} + showRestoreButton={ + Boolean(description) && description !== linkData?.description + } + showClearButton={Boolean(description)} + /> + + + + {translateBuilder(BUILDER.CREATE_ITEM_LINK_TYPE_TITLE)} + + {url ? ( + ( + {url}} + control={} + /> + } control={} slotProps={{ - typography: { - width: '100%', - minWidth: '0px', - }, + typography: { width: '100%', minWidth: '0px' }, }} + sx={{ minWidth: '0px', width: '100%' }} /> - )} - { - // only show this options when embedding is allowed and there is no html code - // as the html will take precedence over showing the site as an iframe - // and some sites like daily motion actually allow both, we want to allow show the html setting - linkData?.isEmbeddingAllowed && !linkData?.html && ( + {linkData?.html && linkData.html !== '' && ( + } control={} slotProps={{ typography: { @@ -217,25 +197,43 @@ const LinkForm = ({ onChange, item }: Props): JSX.Element => { minWidth: '0px', }, }} - sx={{ - // this ensure the iframe takes up all horizontal space - '& iframe': { - width: '100%', - }, - }} /> - ) - } - - )} - /> - ) : ( - - {translateBuilder(BUILDER.CREATE_ITEM_LINK_TYPE_HELPER_TEXT)} - - )} - - + )} + { + // only show this options when embedding is allowed and there is no html code + // as the html will take precedence over showing the site as an iframe + // and some sites like daily motion actually allow both, we want to allow show the html setting + linkData?.isEmbeddingAllowed && !linkData?.html && ( + } + slotProps={{ + typography: { + width: '100%', + minWidth: '0px', + }, + }} + sx={{ + // this ensure the iframe takes up all horizontal space + '& iframe': { + width: '100%', + }, + }} + /> + ) + } + + )} + /> + ) : ( + + {translateBuilder(BUILDER.CREATE_ITEM_LINK_TYPE_HELPER_TEXT)} + + )} + + + ); }; diff --git a/src/components/item/shortcut/EditShortcutForm.tsx b/src/components/item/shortcut/EditShortcutForm.tsx index 022f1f8cd..3d08ef259 100644 --- a/src/components/item/shortcut/EditShortcutForm.tsx +++ b/src/components/item/shortcut/EditShortcutForm.tsx @@ -1,9 +1,9 @@ import { ReactNode, useEffect } from 'react'; -import { useForm } from 'react-hook-form'; +import { FormProvider, useForm } from 'react-hook-form'; import { DiscriminatedItem } from '@graasp/sdk'; -import NameForm from '../form/NameForm'; +import { ItemNameField } from '../form/ItemNameField'; type Inputs = { name: string; @@ -16,7 +16,10 @@ function EditShortcutForm({ item: DiscriminatedItem; setChanges: (args: { name: string }) => void; }): ReactNode { - const { register, reset, watch } = useForm(); + const methods = useForm({ + defaultValues: { name: item.name }, + }); + const { watch } = methods; const name = watch('name'); @@ -29,10 +32,9 @@ function EditShortcutForm({ }, [name]); return ( - reset({ name: '' })} - nameForm={register('name', { value: item.name })} - /> + + + ); }