From 06cbce74b2f5845868dbe31cf7eb3cbd6d0803d4 Mon Sep 17 00:00:00 2001 From: Pedro Bonamin Date: Fri, 8 Mar 2024 10:17:19 +0100 Subject: [PATCH 1/2] feat(core): add groups to document actions, introduce paneActions group --- .eslintrc.cjs | 12 +- .../hookCollection/GetHookCollectionState.tsx | 24 ++-- .../src/core/config/document/actions.ts | 9 ++ .../RenderActionCollectionState.tsx | 11 +- .../components/pane/PaneContextMenuButton.tsx | 14 ++- .../header/DocumentPanelHeader.tsx | 37 +++++- .../document/statusBar/ActionMenuButton.tsx | 107 +++++++++++------- .../statusBar/DocumentStatusBarActions.tsx | 7 +- 8 files changed, 159 insertions(+), 62 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 393f657e313..96f99010b42 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -73,7 +73,17 @@ const config = { { ignores: { componentPatterns: ['motion$'], - attributes: ['animate', 'closed', 'exit', 'fill', 'full', 'initial', 'size', 'sortOrder'], + attributes: [ + 'animate', + 'closed', + 'exit', + 'fill', + 'full', + 'initial', + 'size', + 'sortOrder', + 'group', + ], }, }, ], diff --git a/packages/sanity/src/core/components/hookCollection/GetHookCollectionState.tsx b/packages/sanity/src/core/components/hookCollection/GetHookCollectionState.tsx index 9e4ae4c8ef5..d41709d3893 100644 --- a/packages/sanity/src/core/components/hookCollection/GetHookCollectionState.tsx +++ b/packages/sanity/src/core/components/hookCollection/GetHookCollectionState.tsx @@ -13,13 +13,14 @@ export interface GetHookCollectionStateProps { children: (props: {states: K[]}) => ReactNode hooks: ActionHook[] onReset?: () => void + group?: string } const throttleOptions: ThrottleSettings = {trailing: true} /** @internal */ export function GetHookCollectionState(props: GetHookCollectionStateProps) { - const {hooks, args, children, onReset} = props + const {hooks, args, children, group, onReset} = props const statesRef = useRef>({}) const [tickId, setTick] = useState(0) @@ -46,14 +47,18 @@ export function GetHookCollectionState(props: GetHookCollectionStateProps< throttleOptions, ) - const handleNext = useCallback((id: any, hookState: any) => { - if (hookState === null) { - delete statesRef.current[id] - } else { - const current = statesRef.current[id] - statesRef.current[id] = {...current, value: hookState} - } - }, []) + const handleNext = useCallback( + (id: any, hookState: any) => { + const hookGroup = hookState?.group || ['default'] + if (hookState === null || (group && !hookGroup.includes(group))) { + delete statesRef.current[id] + } else { + const current = statesRef.current[id] + statesRef.current[id] = {...current, value: hookState} + } + }, + [group], + ) const handleReset = useCallback( (id: any) => { @@ -67,7 +72,6 @@ export function GetHookCollectionState(props: GetHookCollectionStateProps< ) const hookIds = useMemo(() => hooks.map((hook) => getHookId(hook)), [hooks]) - const states = useMemo( () => hookIds.map((id) => statesRef.current[id]?.value).filter(isNonNullable), // eslint-disable-next-line react-hooks/exhaustive-deps -- tickId is used to refresh the memo, before it can be removed it needs to be investigated what impact it has diff --git a/packages/sanity/src/core/config/document/actions.ts b/packages/sanity/src/core/config/document/actions.ts index 99d22a57af0..865020663d3 100644 --- a/packages/sanity/src/core/config/document/actions.ts +++ b/packages/sanity/src/core/config/document/actions.ts @@ -115,6 +115,11 @@ export type DocumentActionDialogProps = | DocumentActionModalDialogProps | DocumentActionCustomDialogComponentProps +/** + * @hidden + * @beta */ +export type DocumentActionGroup = 'default' | 'paneActions' + /** * @hidden * @beta */ @@ -127,4 +132,8 @@ export interface DocumentActionDescription { onHandle?: () => void shortcut?: string | null title?: ReactNode + /** + * @beta + */ + group?: DocumentActionGroup[] } diff --git a/packages/sanity/src/structure/components/RenderActionCollectionState.tsx b/packages/sanity/src/structure/components/RenderActionCollectionState.tsx index a5bb89fbcad..89262337dc0 100644 --- a/packages/sanity/src/structure/components/RenderActionCollectionState.tsx +++ b/packages/sanity/src/structure/components/RenderActionCollectionState.tsx @@ -1,6 +1,7 @@ import type * as React from 'react' import { type DocumentActionDescription, + type DocumentActionGroup, type DocumentActionProps, GetHookCollectionState, } from 'sanity' @@ -16,14 +17,20 @@ export interface RenderActionCollectionProps { actionProps: DocumentActionProps children: (props: {states: DocumentActionDescription[]}) => React.ReactNode onActionComplete?: () => void + group?: DocumentActionGroup } /** @internal */ export const RenderActionCollectionState = (props: RenderActionCollectionProps) => { - const {actions, children, actionProps, onActionComplete} = props + const {actions, children, actionProps, onActionComplete, group} = props return ( - + {children} ) diff --git a/packages/sanity/src/structure/components/pane/PaneContextMenuButton.tsx b/packages/sanity/src/structure/components/pane/PaneContextMenuButton.tsx index f0d16e7e908..2d0e966abd4 100644 --- a/packages/sanity/src/structure/components/pane/PaneContextMenuButton.tsx +++ b/packages/sanity/src/structure/components/pane/PaneContextMenuButton.tsx @@ -1,5 +1,5 @@ -import {Menu} from '@sanity/ui' -import {useId} from 'react' +import {Menu, MenuDivider} from '@sanity/ui' +import {type ReactNode, useId} from 'react' import {ContextMenuButton} from 'sanity' import {MenuButton, type PopoverProps} from '../../../ui-components' @@ -8,6 +8,7 @@ import {type _PaneMenuItem, type _PaneMenuNode} from './types' interface PaneContextMenuButtonProps { nodes: _PaneMenuNode[] + actionsNodes?: ReactNode } const CONTEXT_MENU_POPOVER_PROPS: PopoverProps = { @@ -31,7 +32,7 @@ function nodesHasTone(nodes: _PaneMenuNode[], tone: NonNullable<_PaneMenuItem['t * @beta This API will change. DO NOT USE IN PRODUCTION. */ export function PaneContextMenuButton(props: PaneContextMenuButtonProps) { - const {nodes} = props + const {nodes, actionsNodes} = props const id = useId() const hasCritical = nodesHasTone(nodes, 'critical') @@ -49,9 +50,14 @@ export function PaneContextMenuButton(props: PaneContextMenuButtonProps) { id={id} menu={ + {actionsNodes && ( + <> + {actionsNodes} + + + )} {nodes.map((node, nodeIndex) => { const isAfterGroup = nodes[nodeIndex - 1]?.type === 'group' - return })} diff --git a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentPanelHeader.tsx b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentPanelHeader.tsx index 8a0e3995ba8..efced88d7bc 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentPanelHeader.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/header/DocumentPanelHeader.tsx @@ -1,7 +1,7 @@ import {ArrowLeftIcon, CloseIcon, SplitVerticalIcon} from '@sanity/icons' import {Flex} from '@sanity/ui' import type * as React from 'react' -import {createElement, forwardRef, memo, useMemo} from 'react' +import {createElement, forwardRef, memo, useMemo, useState} from 'react' import {useFieldActions, useTimelineSelector, useTranslation} from 'sanity' import {Button, TooltipDelayGroupProvider} from '../../../../../ui-components' @@ -9,6 +9,7 @@ import { PaneContextMenuButton, PaneHeader, PaneHeaderActionButton, + RenderActionCollectionState, usePane, usePaneRouter, } from '../../../../components' @@ -16,6 +17,7 @@ import {structureLocaleNamespace} from '../../../../i18n' import {isMenuNodeButton, isNotMenuNodeButton, resolveMenuNodes} from '../../../../menuNodes' import {type PaneMenuItem} from '../../../../types' import {useStructureTool} from '../../../../useStructureTool' +import {ActionDialogWrapper, ActionMenuListItem} from '../../statusBar/ActionMenuButton' import {TimelineMenu} from '../../timeline' import {useDocumentPane} from '../../useDocumentPane' import {DocumentHeaderTabs} from './DocumentHeaderTabs' @@ -33,6 +35,8 @@ export const DocumentPanelHeader = memo( ) { const {menuItems} = _props const { + actions, + editState, onMenuAction, onPaneClose, onPaneSplit, @@ -46,6 +50,7 @@ export const DocumentPanelHeader = memo( const {features} = useStructureTool() const {index, BackLink, hasGroupSiblings} = usePaneRouter() const {actions: fieldActions} = useFieldActions() + const [referenceElement, setReferenceElement] = useState(null) const menuNodes = useMemo( () => @@ -130,8 +135,34 @@ export const DocumentPanelHeader = memo( ))} - - + + {({states}) => ( + + {({handleAction}) => ( +
+ ( + + ))} + /> +
+ )} +
+ )} +
{showSplitPaneButton && (