-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Block Editor: Introduce block locking UI (#39183)
* Block Locking: Introduce UI * Add lock button to the block toolbar * Specify rootClientId in modal * Fix typo * Update text and fix typos * Remove top separator and update icon position * Display toolbar icon if any of the restrictions is active * Add icons to menu item * Update labels * Move block settings menu item * Fix menu item label condition * Add basic e2e tests * Update text * Update toolbar button spacing * Remove block title suffix * Use rootClientId in menu item * Don't export new components * Remove extra div
- Loading branch information
Showing
11 changed files
with
492 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { default as BlockLockMenuItem } from './menu-item'; | ||
export { default as BlockLockModal } from './modal'; | ||
export { default as BlockLockToolbar } from './toolbar'; |
52 changes: 52 additions & 0 deletions
52
packages/block-editor/src/components/block-lock/menu-item.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { __ } from '@wordpress/i18n'; | ||
import { useReducer } from '@wordpress/element'; | ||
import { MenuItem } from '@wordpress/components'; | ||
import { useSelect } from '@wordpress/data'; | ||
import { lock, unlock } from '@wordpress/icons'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import BlockLockModal from './modal'; | ||
import { store as blockEditorStore } from '../../store'; | ||
|
||
export default function BlockLockMenuItem( { clientId } ) { | ||
const { isLocked } = useSelect( | ||
( select ) => { | ||
const { | ||
canMoveBlock, | ||
canRemoveBlock, | ||
getBlockRootClientId, | ||
} = select( blockEditorStore ); | ||
const rootClientId = getBlockRootClientId( clientId ); | ||
|
||
return { | ||
isLocked: | ||
! canMoveBlock( clientId, rootClientId ) || | ||
! canRemoveBlock( clientId, rootClientId ), | ||
}; | ||
}, | ||
[ clientId ] | ||
); | ||
|
||
const [ isModalOpen, toggleModal ] = useReducer( | ||
( isActive ) => ! isActive, | ||
false | ||
); | ||
|
||
const label = isLocked ? __( 'Unlock' ) : __( 'Lock' ); | ||
|
||
return ( | ||
<> | ||
<MenuItem icon={ isLocked ? unlock : lock } onClick={ toggleModal }> | ||
{ label } | ||
</MenuItem> | ||
{ isModalOpen && ( | ||
<BlockLockModal clientId={ clientId } onClose={ toggleModal } /> | ||
) } | ||
</> | ||
); | ||
} |
165 changes: 165 additions & 0 deletions
165
packages/block-editor/src/components/block-lock/modal.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { __, sprintf } from '@wordpress/i18n'; | ||
import { useEffect, useState } from '@wordpress/element'; | ||
import { | ||
Button, | ||
CheckboxControl, | ||
Flex, | ||
FlexItem, | ||
Icon, | ||
Modal, | ||
} from '@wordpress/components'; | ||
import { dragHandle, trash } from '@wordpress/icons'; | ||
import { useInstanceId } from '@wordpress/compose'; | ||
import { useDispatch, useSelect } from '@wordpress/data'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import useBlockDisplayInformation from '../use-block-display-information'; | ||
import { store as blockEditorStore } from '../../store'; | ||
|
||
export default function BlockLockModal( { clientId, onClose } ) { | ||
const [ lock, setLock ] = useState( { move: false, remove: false } ); | ||
const { canMove, canRemove } = useSelect( | ||
( select ) => { | ||
const { | ||
canMoveBlock, | ||
canRemoveBlock, | ||
getBlockRootClientId, | ||
} = select( blockEditorStore ); | ||
const rootClientId = getBlockRootClientId( clientId ); | ||
|
||
return { | ||
canMove: canMoveBlock( clientId, rootClientId ), | ||
canRemove: canRemoveBlock( clientId, rootClientId ), | ||
}; | ||
}, | ||
[ clientId ] | ||
); | ||
const { updateBlockAttributes } = useDispatch( blockEditorStore ); | ||
const blockInformation = useBlockDisplayInformation( clientId ); | ||
const instanceId = useInstanceId( | ||
BlockLockModal, | ||
'block-editor-block-lock-modal__options-title' | ||
); | ||
|
||
useEffect( () => { | ||
setLock( { | ||
move: ! canMove, | ||
remove: ! canRemove, | ||
} ); | ||
}, [ canMove, canRemove ] ); | ||
|
||
const isAllChecked = Object.values( lock ).every( Boolean ); | ||
|
||
let ariaChecked; | ||
if ( isAllChecked ) { | ||
ariaChecked = 'true'; | ||
} else if ( Object.values( lock ).some( Boolean ) ) { | ||
ariaChecked = 'mixed'; | ||
} else { | ||
ariaChecked = 'false'; | ||
} | ||
|
||
return ( | ||
<Modal | ||
title={ sprintf( | ||
/* translators: %s: Name of the block. */ | ||
__( 'Lock %s' ), | ||
blockInformation.title | ||
) } | ||
overlayClassName="block-editor-block-lock-modal" | ||
closeLabel={ __( 'Close' ) } | ||
onRequestClose={ onClose } | ||
> | ||
<form | ||
onSubmit={ ( event ) => { | ||
event.preventDefault(); | ||
updateBlockAttributes( [ clientId ], { lock } ); | ||
onClose(); | ||
} } | ||
> | ||
<p> | ||
{ __( | ||
'Choose specific attributes to restrict or lock all available options.' | ||
) } | ||
</p> | ||
<div | ||
role="group" | ||
aria-labelledby={ instanceId } | ||
className="block-editor-block-lock-modal__options" | ||
> | ||
<CheckboxControl | ||
className="block-editor-block-lock-modal__options-title" | ||
label={ | ||
<span id={ instanceId }>{ __( 'Lock all' ) }</span> | ||
} | ||
checked={ isAllChecked } | ||
aria-checked={ ariaChecked } | ||
onChange={ ( newValue ) => | ||
setLock( { | ||
move: newValue, | ||
remove: newValue, | ||
} ) | ||
} | ||
/> | ||
<ul className="block-editor-block-lock-modal__checklist"> | ||
<li className="block-editor-block-lock-modal__checklist-item"> | ||
<CheckboxControl | ||
label={ | ||
<> | ||
{ __( 'Disable movement' ) } | ||
<Icon icon={ dragHandle } /> | ||
</> | ||
} | ||
checked={ lock.move } | ||
onChange={ ( move ) => | ||
setLock( ( prevLock ) => ( { | ||
...prevLock, | ||
move, | ||
} ) ) | ||
} | ||
/> | ||
</li> | ||
<li className="block-editor-block-lock-modal__checklist-item"> | ||
<CheckboxControl | ||
label={ | ||
<> | ||
{ __( 'Prevent removal' ) } | ||
<Icon icon={ trash } /> | ||
</> | ||
} | ||
checked={ lock.remove } | ||
onChange={ ( remove ) => | ||
setLock( ( prevLock ) => ( { | ||
...prevLock, | ||
remove, | ||
} ) ) | ||
} | ||
/> | ||
</li> | ||
</ul> | ||
</div> | ||
<Flex | ||
className="block-editor-block-lock-modal__actions" | ||
justify="flex-end" | ||
expanded={ false } | ||
> | ||
<FlexItem> | ||
<Button variant="tertiary" onClick={ onClose }> | ||
{ __( 'Cancel' ) } | ||
</Button> | ||
</FlexItem> | ||
<FlexItem> | ||
<Button variant="primary" type="submit"> | ||
{ __( 'Apply' ) } | ||
</Button> | ||
</FlexItem> | ||
</Flex> | ||
</form> | ||
</Modal> | ||
); | ||
} |
67 changes: 67 additions & 0 deletions
67
packages/block-editor/src/components/block-lock/style.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
.block-editor-block-lock-modal { | ||
z-index: z-index(".block-editor-block-lock-modal"); | ||
|
||
.components-modal__frame { | ||
@include break-small() { | ||
max-width: $break-mobile; | ||
} | ||
} | ||
} | ||
|
||
.block-editor-block-lock-modal__checklist { | ||
margin: 0; | ||
} | ||
|
||
.block-editor-block-lock-modal__options-title { | ||
border-bottom: 1px solid $gray-300; | ||
padding: $grid-unit-15 0; | ||
|
||
.components-checkbox-control__label { | ||
font-weight: 600; | ||
} | ||
|
||
.components-base-control__field { | ||
align-items: center; | ||
display: flex; | ||
margin: 0; | ||
} | ||
} | ||
.block-editor-block-lock-modal__checklist-item { | ||
border-bottom: 1px solid $gray-300; | ||
margin-bottom: 0; | ||
padding: $grid-unit-15 0 $grid-unit-15 $grid-unit-15; | ||
|
||
.components-base-control__field { | ||
align-items: center; | ||
display: flex; | ||
margin: 0; | ||
} | ||
|
||
.components-checkbox-control__label { | ||
display: flex; | ||
align-items: center; | ||
justify-content: space-between; | ||
flex-grow: 1; | ||
|
||
svg { | ||
margin-right: $grid-unit-15; | ||
fill: $gray-900; | ||
} | ||
} | ||
} | ||
|
||
.block-editor-block-lock-modal__actions { | ||
margin-top: $grid-unit-30; | ||
} | ||
|
||
.block-editor-block-lock-toolbar { | ||
.components-button.has-icon { | ||
min-width: $button-size-small + $grid-unit-15 !important; | ||
padding-left: 0 !important; | ||
|
||
&:focus::before { | ||
left: 0 !important; | ||
right: $grid-unit-15 !important; | ||
} | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
packages/block-editor/src/components/block-lock/toolbar.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { __, sprintf } from '@wordpress/i18n'; | ||
import { ToolbarButton, ToolbarGroup } from '@wordpress/components'; | ||
import { useReducer } from '@wordpress/element'; | ||
import { lock } from '@wordpress/icons'; | ||
import { useSelect } from '@wordpress/data'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import BlockLockModal from './modal'; | ||
import useBlockDisplayInformation from '../use-block-display-information'; | ||
import { store as blockEditorStore } from '../../store'; | ||
|
||
export default function BlockLockToolbar( { clientId } ) { | ||
const blockInformation = useBlockDisplayInformation( clientId ); | ||
const { canMove, canRemove } = useSelect( | ||
( select ) => { | ||
const { canMoveBlock, canRemoveBlock } = select( blockEditorStore ); | ||
|
||
return { | ||
canMove: canMoveBlock( clientId ), | ||
canRemove: canRemoveBlock( clientId ), | ||
}; | ||
}, | ||
[ clientId ] | ||
); | ||
|
||
const [ isModalOpen, toggleModal ] = useReducer( | ||
( isActive ) => ! isActive, | ||
false | ||
); | ||
|
||
if ( canMove && canRemove ) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<> | ||
<ToolbarGroup className="block-editor-block-lock-toolbar"> | ||
<ToolbarButton | ||
icon={ lock } | ||
label={ sprintf( | ||
/* translators: %s: block name */ | ||
__( 'Unlock %s' ), | ||
blockInformation.title | ||
) } | ||
onClick={ toggleModal } | ||
/> | ||
</ToolbarGroup> | ||
{ isModalOpen && ( | ||
<BlockLockModal clientId={ clientId } onClose={ toggleModal } /> | ||
) } | ||
</> | ||
); | ||
} |
Oops, something went wrong.