chore: reconfigures useDocumentDrawer hook

This commit is contained in:
Jacob Fletcher
2022-12-06 12:13:30 -05:00
parent d9eb1ea801
commit 445c6fc775
10 changed files with 168 additions and 137 deletions

View File

@@ -2,6 +2,10 @@
.doc-drawer {
&__header {
margin-bottom: base(1);
}
&__header-content {
display: flex;
justify-content: space-between;
align-items: flex-start;

View File

@@ -1,8 +1,8 @@
import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
import { useModal } from '@faceless-ui/modal';
import { useTranslation } from 'react-i18next';
import { toast } from 'react-toastify';
import { Props, DocumentTogglerProps, IDocumentDrawerContext } from './types';
import { DocumentDrawerProps, DocumentTogglerProps, UseDocumentDrawer } from './types';
import DefaultEdit from '../../views/collections/Edit/Default';
import X from '../../icons/X';
import { Fields } from '../../forms/Form/types';
@@ -17,33 +17,47 @@ import { DocumentInfoProvider } from '../../utilities/DocumentInfo';
import RenderCustomComponent from '../../utilities/RenderCustomComponent';
import usePayloadAPI from '../../../hooks/usePayloadAPI';
import formatFields from '../../views/collections/Edit/formatFields';
import { useRelatedCollections } from '../../forms/field-types/Relationship/AddNew/useRelatedCollections';
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
import IDLabel from '../IDLabel';
import './index.scss';
const baseClass = 'doc-drawer';
const formatFieldsFromSchema = ({
collectionSlug,
collectionConfig,
} : {
collectionSlug: string,
collectionConfig: SanitizedCollectionConfig,
}) => {
if (collectionSlug) {
if (collectionConfig) {
return formatFields(collectionConfig, true);
}
}
return [];
};
const formatDocumentDrawerSlug = ({
collection,
collectionSlug,
id,
depth,
uuid,
}: {
collection: string,
collectionSlug: string,
id: string,
depth: number,
uuid?: string, // supply when creating a new document and no id is available
}) => `doc-${collection}-lvl-${depth}-${id || uuid || '0'}`;
}) => `doc-${collectionSlug}-lvl-${depth}-${id || uuid || '0'}`;
export const DocumentDrawerToggler: React.FC<DocumentTogglerProps> = ({
id,
collection,
children,
className,
uuid,
drawerSlug,
...rest
}) => {
const drawerDepth = useDrawerDepth();
const drawerSlug = formatDocumentDrawerSlug({ collection, id, depth: drawerDepth, uuid });
return (
<DrawerToggler
slug={drawerSlug}
@@ -56,12 +70,12 @@ export const DocumentDrawerToggler: React.FC<DocumentTogglerProps> = ({
);
};
export const DocumentDrawer: React.FC<Props> = ({
collection,
export const DocumentDrawer: React.FC<DocumentDrawerProps> = ({
collectionSlug,
id,
drawerSlug,
onSave,
customHeader,
uuid,
}) => {
const { serverURL, routes: { api } } = useConfig();
const { toggleModal, modalState, closeModal } = useModal();
@@ -69,22 +83,18 @@ export const DocumentDrawer: React.FC<Props> = ({
const { permissions, user } = useAuth();
const [initialState, setInitialState] = useState<Fields>();
const { t, i18n } = useTranslation(['fields', 'general']);
const drawerDepth = useDrawerDepth();
const config = useConfig();
const hasInitializedState = useRef(false);
const [isOpen, setIsOpen] = useState(false);
const [modalSlug] = useState<string>(() => formatDocumentDrawerSlug({
collection,
id,
depth: drawerDepth,
uuid,
}));
const [collectionConfig] = useRelatedCollections(collectionSlug);
const collectionConfig = config.collections.find((col) => col.slug === collection);
const [fields] = useState(() => formatFields(collectionConfig, true));
const [fields, setFields] = useState(() => formatFieldsFromSchema({ collectionConfig, collectionSlug }));
useEffect(() => {
setFields(formatFieldsFromSchema({ collectionConfig, collectionSlug }));
}, [collectionSlug, collectionConfig]);
const [{ data, isLoading: isLoadingDocument, isError }] = usePayloadAPI(
(id ? `${serverURL}${api}/${collection}/${id}` : null),
(id ? `${serverURL}${api}/${collectionSlug}/${id}` : null),
{ initialParams: { 'fallback-locale': 'null', depth: 0, draft: 'true' } },
);
@@ -111,17 +121,17 @@ export const DocumentDrawer: React.FC<Props> = ({
}, [data, fields, id, user, locale, isLoadingDocument, t]);
useEffect(() => {
setIsOpen(Boolean(modalState[modalSlug]?.isOpen));
}, [modalState, modalSlug]);
setIsOpen(Boolean(modalState[drawerSlug]?.isOpen));
}, [modalState, drawerSlug]);
useEffect(() => {
if (isOpen && isError) {
closeModal(modalSlug);
toast.error(data.errors[0].message);
closeModal(drawerSlug);
toast.error(data.errors?.[0].message || t('error:unspecific'));
}
}, [isError, t, isOpen, data, modalSlug, closeModal]);
}, [isError, t, isOpen, data, drawerSlug, closeModal]);
const modalAction = `${serverURL}${api}/${collection}?locale=${locale}&depth=0&fallback-locale=null`;
const modalAction = `${serverURL}${api}/${collectionSlug}?locale=${locale}&depth=0&fallback-locale=null`;
if (isError) return null;
@@ -130,7 +140,7 @@ export const DocumentDrawer: React.FC<Props> = ({
// to do this, do not render the drawer until it is open
return (
<Drawer
slug={modalSlug}
slug={drawerSlug}
formatSlug={false}
>
<DocumentInfoProvider collection={collectionConfig}>
@@ -140,6 +150,7 @@ export const DocumentDrawer: React.FC<Props> = ({
componentProps={{
isLoading: !initialState,
data,
id,
collection: collectionConfig,
permissions: permissions.collections[collectionConfig.slug],
isEditing: false,
@@ -153,16 +164,21 @@ export const DocumentDrawer: React.FC<Props> = ({
disableLeaveWithoutSaving: true,
customHeader: (
<div className={`${baseClass}__header`}>
<h2>
{!customHeader ? t(!id ? 'fields:addNewLabel' : 'general:editLabel', { label: getTranslation(collectionConfig.labels.singular, i18n) }) : customHeader}
</h2>
<Button
buttonStyle="none"
className={`${baseClass}__header-close`}
onClick={() => toggleModal(modalSlug)}
>
<X />
</Button>
<div className={`${baseClass}__header-content`}>
<h2>
{!customHeader ? t(!id ? 'fields:addNewLabel' : 'general:editLabel', { label: getTranslation(collectionConfig.labels.singular, i18n) }) : customHeader}
</h2>
<Button
buttonStyle="none"
className={`${baseClass}__header-close`}
onClick={() => toggleModal(drawerSlug)}
>
<X />
</Button>
</div>
{id && (
<IDLabel id={id} />
)}
</div>
),
}}
@@ -174,10 +190,52 @@ export const DocumentDrawer: React.FC<Props> = ({
return null;
};
export const DocumentDrawerContext = createContext({
DocumentDrawer,
DocumentDrawerToggler,
formatDocumentDrawerSlug,
});
export const useDocumentDrawer: UseDocumentDrawer = ({ id, collectionSlug }) => {
const drawerDepth = useDrawerDepth();
const uuid = useId();
const { modalState, toggleModal } = useModal();
const [isOpen, setIsOpen] = useState(false);
const drawerSlug = formatDocumentDrawerSlug({
collectionSlug,
id,
depth: drawerDepth,
uuid,
});
export const useDocumentDrawer = (): IDocumentDrawerContext => useContext(DocumentDrawerContext);
useEffect(() => {
setIsOpen(Boolean(modalState[drawerSlug]?.isOpen));
}, [modalState, drawerSlug]);
const toggleDrawer = useCallback(() => {
toggleModal(drawerSlug);
}, [toggleModal, drawerSlug]);
return useMemo(() => {
return ([
(props) => {
return (
<DocumentDrawer
{...props}
collectionSlug={collectionSlug}
id={id}
drawerSlug={drawerSlug}
key={drawerSlug}
/>
);
},
(props) => (
<DocumentDrawerToggler
{...props}
id={id}
drawerSlug={drawerSlug}
/>
),
{
drawerSlug,
drawerDepth,
isDrawerOpen: isOpen,
toggleDrawer,
},
]);
}, [drawerDepth, id, drawerSlug, collectionSlug, isOpen, toggleDrawer]);
};

View File

@@ -1,28 +1,30 @@
import React, { HTMLAttributes } from 'react';
export type Props = {
collection: string
export type DocumentDrawerProps = {
collectionSlug: string
id?: string
onSave?: (json: Record<string, unknown>) => void
customHeader?: React.ReactNode
uuid?: string
drawerSlug?: string
}
export type DocumentTogglerProps = HTMLAttributes<HTMLButtonElement> & {
collection: string
id?: string
children?: React.ReactNode
className?: string
uuid?: string
drawerSlug?: string
key?: string
}
export type IDocumentDrawerContext = {
DocumentDrawer: React.FC<Props>,
DocumentDrawerToggler: React.FC<DocumentTogglerProps>
formatDocumentDrawerSlug: (props: {
collection: string,
id: string,
depth: number,
uuid?: string,
}) => string,
}
export type UseDocumentDrawer = (args: {
id?: string
collectionSlug: string
}) => [
React.FC<Omit<DocumentDrawerProps, 'collectionSlug' | 'id'>>, // drawer
React.FC<Omit<DocumentTogglerProps, 'collectionSlug' | 'id'>>, // toggler
{
drawerSlug: string,
drawerDepth: number
isDrawerOpen: boolean
toggleDrawer: () => void
}
]

View File

@@ -19,7 +19,7 @@
}
&:hover {
background-color: var(--theme-error-150);
background-color: var(--theme-elevation-100);
}
}

View File

@@ -1,4 +1,4 @@
import React, { Fragment, useId } from 'react';
import React, { Fragment } from 'react';
import { components, MultiValueProps } from 'react-select';
import { useDocumentDrawer } from '../../DocumentDrawer';
import Edit from '../../../icons/Edit';
@@ -17,8 +17,10 @@ export const MultiValueLabel: React.FC<MultiValueProps<Option>> = (props) => {
selectProps,
} = props;
const { DocumentDrawer, DocumentDrawerToggler } = useDocumentDrawer();
const uuid = useId();
const [DocumentDrawer, DocumentDrawerToggler] = useDocumentDrawer({
id: value?.toString(),
collectionSlug: relationTo,
});
return (
<div className={baseClass}>
@@ -33,19 +35,12 @@ export const MultiValueLabel: React.FC<MultiValueProps<Option>> = (props) => {
{relationTo && (
<Fragment>
<DocumentDrawerToggler
collection={relationTo}
id={value.toString()}
uuid={uuid}
className={`${baseClass}__drawer-toggler`}
aria-label={`Edit ${label}`}
>
<Edit />
</DocumentDrawerToggler>
<DocumentDrawer
collection={relationTo}
id={value.toString()}
uuid={uuid}
/>
<DocumentDrawer />
</Fragment>
)}
</div>

View File

@@ -31,7 +31,7 @@
}
&:hover {
background-color: var(--theme-error-150);
background-color: var(--theme-elevation-100);
}
}

View File

@@ -1,10 +1,9 @@
import React, { Fragment, useId } from 'react';
import React, { Fragment } from 'react';
import { components, SingleValueProps } from 'react-select';
import { useDocumentDrawer } from '../../DocumentDrawer';
import Edit from '../../../icons/Edit';
import { Option } from '../../../forms/field-types/Relationship/types';
import './index.scss';
import { useDrawerDepth } from '../../Drawer';
const baseClass = 'single-value';
@@ -18,9 +17,10 @@ export const SingleValue: React.FC<SingleValueProps<Option>> = (props) => {
children,
} = props;
const drawerDepth = useDrawerDepth();
const { DocumentDrawer, DocumentDrawerToggler, formatDocumentDrawerSlug } = useDocumentDrawer();
const uuid = useId();
const [DocumentDrawer, DocumentDrawerToggler] = useDocumentDrawer({
id: value.toString(),
collectionSlug: relationTo,
});
return (
<div className={baseClass}>
@@ -30,9 +30,6 @@ export const SingleValue: React.FC<SingleValueProps<Option>> = (props) => {
{relationTo && (
<Fragment>
<DocumentDrawerToggler
collection={relationTo}
id={value.toString()}
uuid={uuid}
className={`${baseClass}__drawer-toggler`}
aria-label={`Edit ${label}`}
onMouseDown={(e) => e.stopPropagation()} // prevents react-select dropdown from opening
@@ -44,18 +41,7 @@ export const SingleValue: React.FC<SingleValueProps<Option>> = (props) => {
</components.SingleValue>
</div>
{relationTo && (
<DocumentDrawer
// use `key` to force the drawer to re-mount when the value changes
key={formatDocumentDrawerSlug({
collection: relationTo,
id: value.toString(),
depth: drawerDepth,
uuid,
})}
collection={relationTo}
id={value.toString()}
uuid={uuid}
/>
<DocumentDrawer />
)}
</div>
);

View File

@@ -18,7 +18,7 @@
padding-top: base(0.25);
padding-bottom: base(0.25);
> * {
.rs__multi-value {
margin: base(.125);
}
}

View File

@@ -1,5 +1,4 @@
import React, { Fragment, useCallback, useEffect, useId, useState } from 'react';
import { useModal } from '@faceless-ui/modal';
import React, { Fragment, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import Button from '../../../../elements/Button';
import { Props } from './types';
@@ -7,11 +6,10 @@ import { SanitizedCollectionConfig } from '../../../../../../collections/config/
import Popup from '../../../../elements/Popup';
import { useRelatedCollections } from './useRelatedCollections';
import { useAuth } from '../../../../utilities/Auth';
import { useEditDepth } from '../../../../utilities/EditDepth';
import Plus from '../../../../icons/Plus';
import { getTranslation } from '../../../../../../utilities/getTranslation';
import { DocumentDrawer, DocumentDrawerToggler } from '../../../../elements/DocumentDrawer';
import Tooltip from '../../../../elements/Tooltip';
import { useDocumentDrawer } from '../../../../elements/DocumentDrawer';
import './index.scss';
@@ -19,17 +17,19 @@ const baseClass = 'relationship-add-new';
export const AddNewRelation: React.FC<Props> = ({ path, hasMany, relationTo, value, setValue, dispatchOptions }) => {
const relatedCollections = useRelatedCollections(relationTo);
const { isModalOpen } = useModal();
const { permissions } = useAuth();
const [hasPermission, setHasPermission] = useState(false);
const [selectedCollection, setSelectedCollection] = useState<SanitizedCollectionConfig>();
const [popupOpen, setPopupOpen] = useState(false);
const editDepth = useEditDepth();
const { t, i18n } = useTranslation('fields');
const [showTooltip, setShowTooltip] = useState(false);
const uuid = useId();
const modalSlug = `${path}-add-modal-depth-${editDepth}`;
const [
DocumentDrawer,
DocumentDrawerToggler,
{ toggleDrawer },
] = useDocumentDrawer({
collectionSlug: relatedCollections.length === 1 ? relatedCollections[0].slug : selectedCollection?.slug,
});
const onSave = useCallback((json) => {
const newValue = Array.isArray(relationTo) ? {
@@ -54,7 +54,6 @@ export const AddNewRelation: React.FC<Props> = ({ path, hasMany, relationTo, val
}
setSelectedCollection(undefined);
// toggleModal(modalSlug);
}, [relationTo, selectedCollection, dispatchOptions, i18n, hasMany, setValue, value]);
const onPopopToggle = useCallback((state) => {
@@ -72,10 +71,11 @@ export const AddNewRelation: React.FC<Props> = ({ path, hasMany, relationTo, val
}, [permissions, relatedCollections]);
useEffect(() => {
if (!isModalOpen(modalSlug)) {
setSelectedCollection(undefined);
if (relatedCollections.length > 1 && selectedCollection) {
// the drawer must be rendered on the page before before opening it
toggleDrawer();
}
}, [isModalOpen, modalSlug]);
}, [selectedCollection, toggleDrawer, relatedCollections]);
return hasPermission ? (
<div
@@ -86,8 +86,6 @@ export const AddNewRelation: React.FC<Props> = ({ path, hasMany, relationTo, val
<Fragment>
<DocumentDrawerToggler
className={`${baseClass}__add-button`}
collection={relatedCollections[0].slug}
uuid={uuid}
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
>
@@ -98,11 +96,7 @@ export const AddNewRelation: React.FC<Props> = ({ path, hasMany, relationTo, val
)}
<Plus />
</DocumentDrawerToggler>
<DocumentDrawer
collection={relatedCollections[0].slug}
uuid={uuid}
onSave={onSave}
/>
<DocumentDrawer onSave={onSave} />
</Fragment>
)}
{relatedCollections.length > 1 && (
@@ -126,9 +120,8 @@ export const AddNewRelation: React.FC<Props> = ({ path, hasMany, relationTo, val
if (permissions.collections[relatedCollection.slug].create.permission) {
return (
<li key={relatedCollection.slug}>
<DocumentDrawerToggler
collection={relatedCollection.slug}
uuid={uuid}
<button
type="button"
className={`${baseClass}__relation-button ${baseClass}__relation-button--${relatedCollection.slug}`}
onClick={() => {
closePopup();
@@ -136,7 +129,7 @@ export const AddNewRelation: React.FC<Props> = ({ path, hasMany, relationTo, val
}}
>
{getTranslation(relatedCollection.labels.singular, i18n)}
</DocumentDrawerToggler>
</button>
</li>
);
}
@@ -146,19 +139,9 @@ export const AddNewRelation: React.FC<Props> = ({ path, hasMany, relationTo, val
</ul>
)}
/>
{relatedCollections.map((relatedCollection, index) => {
if (permissions.collections[relatedCollection.slug].create.permission) {
return (
<DocumentDrawer
key={index}
collection={relatedCollection.slug}
uuid={uuid}
onSave={onSave}
/>
);
}
return null;
})}
{selectedCollection && permissions.collections[selectedCollection.slug].create.permission && (
<DocumentDrawer onSave={onSave} />
)}
</Fragment>
)}
</div>

View File

@@ -5,8 +5,11 @@ import { useConfig } from '../../../../utilities/Config';
export const useRelatedCollections = (relationTo: string | string[]): SanitizedCollectionConfig[] => {
const config = useConfig();
const [relatedCollections] = useState(() => {
const relations = typeof relationTo === 'string' ? [relationTo] : relationTo;
return relations.map((relation) => config.collections.find((collection) => collection.slug === relation));
if (relationTo) {
const relations = typeof relationTo === 'string' ? [relationTo] : relationTo;
return relations.map((relation) => config.collections.find((collection) => collection.slug === relation));
}
return [];
});
return relatedCollections;