Skip to content

Commit

Permalink
feat(core): add document action custom dialog component (#4529)
Browse files Browse the repository at this point in the history
* chore(core): add `DocumentActionCustomDialogComponentProps` to `DocumentActionDialogProps`

* feat(desk): add custom dialog component, refactor document pane portal handling

* refactor(desk): use custom dialog component in `DeleteAction`

* dev(test-studio): add document action with custom dialog component
  • Loading branch information
hermanwikner committed May 26, 2023
1 parent c2f98ba commit d6a841b
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {Button, Card, Dialog, Stack, Text} from '@sanity/ui'
import {DocumentActionComponent} from 'sanity'
import React, {useCallback, useState} from 'react'

export const TestCustomComponentAction: DocumentActionComponent = () => {
const [open, setOpen] = useState<boolean>(false)
const toggleOpen = useCallback(() => setOpen((v) => !v), [])

return {
label: 'Custom modal',
tone: 'primary',
onHandle: toggleOpen,
dialog: {
type: 'custom',
component: open && (
<Dialog
header="Custom action component"
id="custom-modal"
onClickOutside={toggleOpen}
onClose={toggleOpen}
width={1}
footer={
<Stack padding={2}>
<Button onClick={toggleOpen} text="Close" />
</Stack>
}
>
<Card padding={5}>
<Text>This dialog is rendered using a custom dialog component.</Text>
</Card>
</Dialog>
),
},
}
}
9 changes: 8 additions & 1 deletion dev/test-studio/documentActions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ import {DocumentActionsResolver} from 'sanity'
import {TestConfirmDialogAction} from './actions/TestConfirmDialogAction'
import {TestModalDialogAction} from './actions/TestModalDialogAction'
import {TestPopoverDialogAction} from './actions/TestPopoverDialogAction'
import {TestCustomComponentAction} from './actions/TestCustomComponentAction'

export const resolveDocumentActions: DocumentActionsResolver = (prev, {schemaType}) => {
if (schemaType === 'documentActionsTest') {
return [TestConfirmDialogAction, TestModalDialogAction, TestPopoverDialogAction, ...prev]
return [
TestConfirmDialogAction,
TestModalDialogAction,
TestPopoverDialogAction,
TestCustomComponentAction,
...prev,
]
}

return prev
Expand Down
7 changes: 7 additions & 0 deletions packages/sanity/src/core/config/document/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,18 @@ export interface DocumentActionPopoverDialogProps {
onClose: () => void
}

/** @beta */
export interface DocumentActionCustomDialogComponentProps {
type: 'custom'
component: React.ReactNode
}

/** @beta */
export type DocumentActionDialogProps =
| DocumentActionConfirmDialogProps
| DocumentActionPopoverDialogProps
| DocumentActionModalDialogProps
| DocumentActionCustomDialogComponentProps

/** @beta */
export interface DocumentActionDescription {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ export function ConfirmDeleteDialog({
}
onClose={onCancel}
onClickOutside={onCancel}
// Custom portal element configured in `DocumentPane` so that the dialog is scoped to the current pane
portal="documentPanelPortalElement"
>
<DialogBody>
{crossDatasetReferences && internalReferences && !isLoading ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ function ConfirmDeleteDialogContainer(props: ConfirmDeleteDialogProps) {
</Flex>
}
onClose={props.onCancel}
// Custom portal element configured in `DocumentPane` so that the dialog is scoped to the current pane
portal="documentPanelPortalElement"
>
<Box padding={4}>
<Text>An error occurred while loading referencing documents.</Text>
Expand Down
2 changes: 2 additions & 0 deletions packages/sanity/src/desk/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export const _DEBUG = false

export const EMPTY_PARAMS = {}
export const LOADING_PANE = Symbol('LOADING_PANE')

export const DOCUMENT_PANEL_PORTAL_ELEMENT = 'documentPanelPortalElement'
5 changes: 2 additions & 3 deletions packages/sanity/src/desk/documentActions/DeleteAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,8 @@ export const DeleteAction: DocumentActionComponent = ({id, type, draft, onComple
shortcut: 'Ctrl+Alt+D',
onHandle: handle,
dialog: isConfirmDialogOpen && {
type: 'dialog',
onClose: onComplete,
content: (
type: 'custom',
component: (
<ConfirmDeleteDialog
action="delete"
id={draft?._id || id}
Expand Down
5 changes: 4 additions & 1 deletion packages/sanity/src/desk/panes/document/DocumentPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {usePaneLayout} from '../../components/pane/usePaneLayout'
import {useDeskTool} from '../../useDeskTool'
import {ErrorPane} from '../error'
import {LoadingPane} from '../loading'
import {DOCUMENT_PANEL_PORTAL_ELEMENT} from '../../constants'
import {DocumentOperationResults} from './DocumentOperationResults'
import {DocumentPaneProvider} from './DocumentPaneProvider'
import {ChangesPanel} from './changesPanel'
Expand Down Expand Up @@ -241,7 +242,9 @@ function InnerDocumentPane() {
// The portal element comes from `DocumentPanel`.
const footer = useMemo(
() => (
<PortalProvider __unstable_elements={{documentPanelPortalElement}}>
<PortalProvider
__unstable_elements={{[DOCUMENT_PANEL_PORTAL_ELEMENT]: documentPanelPortalElement}}
>
<DialogProvider position={DIALOG_PROVIDER_POSITION} zOffset={zOffsets.portal}>
<PaneFooter ref={setFooterElement}>
<DocumentStatusBar actionsBoxRef={setActionsBoxElement} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Box, Dialog} from '@sanity/ui'
import {Box, Dialog, usePortal, PortalProvider} from '@sanity/ui'
import React, {useId} from 'react'
import {DOCUMENT_PANEL_PORTAL_ELEMENT} from '../../../constants'
import {ConfirmDialog} from './dialogs/ConfirmDialog'
import {ModalDialog} from './dialogs/ModalDialog'
import {PopoverDialog} from './dialogs/PopoverDialog'
Expand All @@ -10,6 +11,16 @@ export interface ActionStateDialogProps {
referenceElement?: HTMLElement | null
}

// A portal provider that uses the document panel portal element if it exists
// as the portal element so that dialogs are scoped to the document panel
function DocumentActionPortalProvider(props: {children: React.ReactNode}) {
const {children} = props
const {element, elements} = usePortal()
const portalElement = elements?.[DOCUMENT_PANEL_PORTAL_ELEMENT] || element

return <PortalProvider element={portalElement}>{children}</PortalProvider>
}

export function ActionStateDialog(props: ActionStateDialogProps) {
const {dialog, referenceElement = null} = props
const modalId = useId()
Expand All @@ -23,7 +34,15 @@ export function ActionStateDialog(props: ActionStateDialogProps) {
}

if (dialog.type === 'dialog' || !dialog.type) {
return <ModalDialog dialog={dialog} />
return (
<DocumentActionPortalProvider>
<ModalDialog dialog={dialog} />
</DocumentActionPortalProvider>
)
}

if (dialog.type === 'custom') {
return <DocumentActionPortalProvider>{dialog?.component}</DocumentActionPortalProvider>
}

// @todo: add validation?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ export function ModalDialog(props: {dialog: DocumentActionModalDialogProps}) {
// eslint-disable-next-line react/jsx-handler-names
onClickOutside={dialog.onClose}
width={dialog.width === undefined ? 1 : DIALOG_WIDTH_TO_UI_WIDTH[dialog.width]}
// Custom portal element configured in `DocumentPane` so that the dialog is scoped to the current pane
portal="documentPanelPortalElement"
>
<Box padding={4}>{dialog.content}</Box>
</Dialog>
Expand Down

2 comments on commit d6a841b

@vercel
Copy link

@vercel vercel bot commented on d6a841b May 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

performance-studio – ./

performance-studio.sanity.build
performance-studio-git-next.sanity.build

@vercel
Copy link

@vercel vercel bot commented on d6a841b May 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

test-studio – ./

test-studio-git-next.sanity.build
test-studio.sanity.build

Please sign in to comment.