From dc0cd5d7bd8be8ee1c89c63cf581c75401ae6a71 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Fri, 23 Jun 2023 16:27:57 +0300 Subject: [PATCH] Add distraction free to site editor (#51173) * adds preference setter in UI * Fix distraction free toolbar showing The store has the variable `distractionFree`, but `isDistractionFree` was incorrectly being used. * fix distraction free flag in site editor, revert change in block toolbar hook * basic distraction free enabled * hide header items in distraction free * fix the JS test for settings * fix the other test for editor settings * remove the rerendering of the site editor header to maintain focus on the writing options menu item * animated hader in edit mode for distraction free * fix sidebar header * move static objects out of components * Refactor distractionFree site editor animations All animations are now triggered by the edit-site-layout__header-container which has an animationState determined by the view and isDistractionFree mode. It also currently sets a isDistractionFreeHovering animation when hovering that element. This will likely need to change to be able to keep the distraction free header visible when one of the header popovers is open. * Remove invalid animation values for initial and exit * Fix bug where edit-mode__actions would get stuck in isDistractionFreeState The actions would get stuck in the isDistractionFree state when: - loading the page in the editor view with distraction free on - turn off distraction free - action stuck above the frame Why? Because the initial state of that component was -50px on page load, so that's what it would transition to when isDistractionFree was turned off. To fix this, we needed to either set an initial state of x: 0 so it can transition to that property, OR assign values for the other states like view or edit so it has a place to transition to. * Override animation with !important CSS when focus is within header on distraction-free mode * Adjust animation exit delay for header so the header remains visible longer when hovering out. * remove hardcoded z-index * turn off distraction free mode when opening the styles sidebar from site view * add distraction free mode to preferences panel in the site editor * Adds the distraction free mode keyboard shortcut --------- Co-authored-by: Alex Lende Co-authored-by: Jerry Jones --- packages/base-styles/_z-index.scss | 1 + packages/block-editor/README.md | 1 + packages/block-editor/src/store/defaults.js | 1 + .../edit-post/src/components/header/index.js | 20 +-- .../components/keyboard-shortcuts/index.js | 2 - .../edit-site/src/components/editor/index.js | 1 + .../src/components/editor/style.scss | 4 + .../src/components/header-edit-mode/index.js | 150 ++++++++++------ .../header-edit-mode/more-menu/index.js | 47 ++++- .../keyboard-shortcuts/edit-mode.js | 37 +++- .../components/keyboard-shortcuts/register.js | 10 ++ .../edit-site/src/components/layout/index.js | 163 ++++++++++++------ .../src/components/layout/style.scss | 46 +++++ .../preferences-modal/enable-feature.js | 7 +- .../src/components/preferences-modal/index.js | 25 +++ .../index.js | 18 +- packages/edit-site/src/index.js | 1 + packages/edit-site/src/store/selectors.js | 5 + .../edit-site/src/store/test/selectors.js | 2 + .../provider/use-block-editor-settings.js | 1 + .../components/interface-skeleton/index.js | 18 +- 21 files changed, 429 insertions(+), 131 deletions(-) diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 96040ec29d07b..cc4a42df98f0a 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -182,6 +182,7 @@ $z-layers: ( ".customize-widgets__block-toolbar": 7, // Site editor layout + ".edit-site-layout__header-container": 4, ".edit-site-layout__hub": 3, ".edit-site-layout__header": 2, ".edit-site-page-header": 2, diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 72c3d50372e48..2c42b42afc442 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -711,6 +711,7 @@ _Properties_ - _maxWidth_ `number`: Max width to constraint resizing - _allowedBlockTypes_ `boolean|Array`: Allowed block types - _hasFixedToolbar_ `boolean`: Whether or not the editor toolbar is fixed +- _distractionFree_ `boolean`: Whether or not the editor UI is distraction free - _focusMode_ `boolean`: Whether the focus mode is enabled or not - _styles_ `Array`: Editor Styles - _keepCaretInsideBlock_ `boolean`: Whether caret should move between blocks in edit mode diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index 7c33350d12dd0..acd40244cb260 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -18,6 +18,7 @@ export const PREFERENCES_DEFAULTS = { * @property {number} maxWidth Max width to constraint resizing * @property {boolean|Array} allowedBlockTypes Allowed block types * @property {boolean} hasFixedToolbar Whether or not the editor toolbar is fixed + * @property {boolean} distractionFree Whether or not the editor UI is distraction free * @property {boolean} focusMode Whether the focus mode is enabled or not * @property {Array} styles Editor Styles * @property {boolean} keepCaretInsideBlock Whether caret should move between blocks in edit mode diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index dfdeb2d130aa3..3f42d4736f57b 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -20,6 +20,16 @@ import MainDashboardButton from './main-dashboard-button'; import { store as editPostStore } from '../../store'; import DocumentTitle from './document-title'; +const slideY = { + hidden: { y: '-50px' }, + hover: { y: 0, transition: { type: 'tween', delay: 0.2 } }, +}; + +const slideX = { + hidden: { x: '-100%' }, + hover: { x: 0, transition: { type: 'tween', delay: 0.2 } }, +}; + function Header( { setEntitiesSavedStatesCallback } ) { const isLargeViewport = useViewportMatch( 'large' ); const { @@ -39,16 +49,6 @@ function Header( { setEntitiesSavedStatesCallback } ) { [] ); - const slideY = { - hidden: { y: '-50px' }, - hover: { y: 0, transition: { type: 'tween', delay: 0.2 } }, - }; - - const slideX = { - hidden: { x: '-100%' }, - hover: { x: 0, transition: { type: 'tween', delay: 0.2 } }, - }; - return (
diff --git a/packages/edit-post/src/components/keyboard-shortcuts/index.js b/packages/edit-post/src/components/keyboard-shortcuts/index.js index 432bce0a3bff5..9a9574fd38bb7 100644 --- a/packages/edit-post/src/components/keyboard-shortcuts/index.js +++ b/packages/edit-post/src/components/keyboard-shortcuts/index.js @@ -223,8 +223,6 @@ function KeyboardShortcuts() { } ); useShortcut( 'core/edit-post/toggle-distraction-free', () => { - closeGeneralSidebar(); - setIsListViewOpened( false ); toggleDistractionFree(); toggleFeature( 'distractionFree' ); createInfoNotice( diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 24805014e3d69..238cad7387055 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -180,6 +180,7 @@ export default function Editor( { isLoading } ) { { isEditMode && } { hasDefaultEditorCanvasView && (
- + { ! isDistractionFree && ( + + ) } { isLargeViewport && ( <> - - { isZoomedOutViewExperimentEnabled && ( + { ! isDistractionFree && ( { - setPreviewDeviceType( 'desktop' ); - __unstableSetEditorMode( - isZoomedOutView - ? 'edit' - : 'zoom-out' - ); - } } + label={ __( 'List View' ) } + onClick={ toggleListView } + shortcut={ listViewShortcut } + showTooltip={ ! showIconLabels } + variant={ + showIconLabels + ? 'tertiary' + : undefined + } /> ) } + { isZoomedOutViewExperimentEnabled && + ! isDistractionFree && ( + { + setPreviewDeviceType( + 'desktop' + ); + __unstableSetEditorMode( + isZoomedOutView + ? 'edit' + : 'zoom-out' + ); + } } + /> + ) } ) }
) } -
- { ! hasDefaultEditorCanvasView ? ( - getEditorCanvasContainerTitle( editorCanvasView ) - ) : ( - - ) } -
+ { ! isDistractionFree && ( +
+ { ! hasDefaultEditorCanvasView ? ( + getEditorCanvasContainerTitle( editorCanvasView ) + ) : ( + + ) } +
+ ) }
-
+ { ! isFocusMode && hasDefaultEditorCanvasView && (
) } - + { ! isDistractionFree && ( + + ) } -
+
); diff --git a/packages/edit-site/src/components/header-edit-mode/more-menu/index.js b/packages/edit-site/src/components/header-edit-mode/more-menu/index.js index a7c9ea977db17..802e9f9439811 100644 --- a/packages/edit-site/src/components/header-edit-mode/more-menu/index.js +++ b/packages/edit-site/src/components/header-edit-mode/more-menu/index.js @@ -3,12 +3,16 @@ */ import { __, _x } from '@wordpress/i18n'; import { useReducer } from '@wordpress/element'; +import { useSelect, useDispatch, useRegistry } from '@wordpress/data'; import { useShortcut } from '@wordpress/keyboard-shortcuts'; import { displayShortcut } from '@wordpress/keycodes'; import { external } from '@wordpress/icons'; import { MenuGroup, MenuItem, VisuallyHidden } from '@wordpress/components'; import { ActionItem, MoreMenuDropdown } from '@wordpress/interface'; -import { PreferenceToggleMenuItem } from '@wordpress/preferences'; +import { + PreferenceToggleMenuItem, + store as preferencesStore, +} from '@wordpress/preferences'; /** * Internal dependencies @@ -20,6 +24,7 @@ import SiteExport from './site-export'; import WelcomeGuideMenuItem from './welcome-guide-menu-item'; import CopyContentMenuItem from './copy-content-menu-item'; import ModeSwitcher from '../mode-switcher'; +import { store as siteEditorStore } from '../../../store'; export default function MoreMenu( { showIconLabels } ) { const [ isModalActive, toggleModal ] = useReducer( @@ -32,6 +37,29 @@ export default function MoreMenu( { showIconLabels } ) { false ); + const registry = useRegistry(); + const isDistractionFree = useSelect( + ( select ) => + select( preferencesStore ).get( + 'core/edit-site', + 'distractionFree' + ), + [] + ); + + const { setIsInserterOpened, setIsListViewOpened, closeGeneralSidebar } = + useDispatch( siteEditorStore ); + const { set: setPreference } = useDispatch( preferencesStore ); + + const toggleDistractionFree = () => { + registry.batch( () => { + setPreference( 'core/edit-site', 'fixedToolbar', false ); + setIsInserterOpened( false ); + setIsListViewOpened( false ); + closeGeneralSidebar(); + } ); + }; + useShortcut( 'core/edit-site/keyboard-shortcuts', toggleModal ); return ( @@ -48,6 +76,7 @@ export default function MoreMenu( { showIconLabels } ) { + { + setPreference( 'core/edit-site', 'fixedToolbar', false ); + setIsInserterOpened( false ); + setIsListViewOpened( false ); + closeGeneralSidebar(); + }; + const handleTextLevelShortcut = ( event, level ) => { event.preventDefault(); const destinationBlockName = @@ -114,6 +133,20 @@ function KeyboardShortcutsEditMode() { ); } ); + useShortcut( 'core/edit-site/toggle-distraction-free', () => { + toggleDistractionFree(); + toggleFeature( 'distractionFree' ); + createInfoNotice( + isFeatureActive( 'distractionFree' ) + ? __( 'Distraction free mode turned on.' ) + : __( 'Distraction free mode turned off.' ), + { + id: 'core/edit-site/distraction-free-mode/notice', + type: 'snackbar', + } + ); + } ); + return null; } diff --git a/packages/edit-site/src/components/keyboard-shortcuts/register.js b/packages/edit-site/src/components/keyboard-shortcuts/register.js index 9660b03e762ed..8dfd1e3e2a45b 100644 --- a/packages/edit-site/src/components/keyboard-shortcuts/register.js +++ b/packages/edit-site/src/components/keyboard-shortcuts/register.js @@ -149,6 +149,16 @@ function KeyboardShortcutsRegister() { }, } ); } ); + + registerShortcut( { + name: 'core/edit-site/toggle-distraction-free', + category: 'global', + description: __( 'Toggle distraction free mode.' ), + keyCombination: { + modifier: 'primaryShift', + character: '\\', + }, + } ); }, [ registerShortcut ] ); return null; diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 31d51557a2fff..3398ec332c766 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -72,26 +72,36 @@ export default function Layout() { const isMobileViewport = useViewportMatch( 'medium', '<' ); const isListPage = getIsListPage( params, isMobileViewport ); const isEditorPage = ! isListPage; - const { hasFixedToolbar, canvasMode, previousShortcut, nextShortcut } = - useSelect( ( select ) => { - const { getAllShortcutKeyCombinations } = select( - keyboardShortcutsStore - ); - const { getCanvasMode } = unlock( select( editSiteStore ) ); - return { - canvasMode: getCanvasMode(), - previousShortcut: getAllShortcutKeyCombinations( - 'core/edit-site/previous-region' - ), - nextShortcut: getAllShortcutKeyCombinations( - 'core/edit-site/next-region' - ), - hasFixedToolbar: select( preferencesStore ).get( - 'core/edit-site', - 'fixedToolbar' - ), - }; - }, [] ); + + const { + isDistractionFree, + hasFixedToolbar, + canvasMode, + previousShortcut, + nextShortcut, + } = useSelect( ( select ) => { + const { getAllShortcutKeyCombinations } = select( + keyboardShortcutsStore + ); + const { getCanvasMode } = unlock( select( editSiteStore ) ); + return { + canvasMode: getCanvasMode(), + previousShortcut: getAllShortcutKeyCombinations( + 'core/edit-site/previous-region' + ), + nextShortcut: getAllShortcutKeyCombinations( + 'core/edit-site/next-region' + ), + hasFixedToolbar: select( preferencesStore ).get( + 'core/edit-site', + 'fixedToolbar' + ), + isDistractionFree: select( preferencesStore ).get( + 'core/edit-site', + 'distractionFree' + ), + }; + }, [] ); const isEditing = canvasMode === 'edit'; const navigateRegionsProps = useNavigateRegions( { previous: previousShortcut, @@ -112,6 +122,27 @@ export default function Layout() { const [ isResizing ] = useState( false ); const isEditorLoading = useIsSiteEditorLoading(); + // This determines which animation variant should apply to the header. + // There is also a `isDistractionFreeHovering` state that gets priority + // when hovering the `edit-site-layout__header-container` in distraction + // free mode. It's set via framer and trickles down to all the children + // so they can use this variant state too. + // + // TODO: The issue with this is we want to have the hover state stick when hovering + // a popover opened via the header. We'll probably need to lift this state to + // handle it ourselves. Also, focusWithin the header needs to be handled. + let headerAnimationState; + + if ( canvasMode === 'view' ) { + // We need 'view' to always take priority so 'isDistractionFree' + // doesn't bleed over into the view (sidebar) state + headerAnimationState = 'view'; + } else if ( isDistractionFree ) { + headerAnimationState = 'isDistractionFree'; + } else { + headerAnimationState = canvasMode; // edit, view, init + } + // Sets the right context for the command center const commandContext = canvasMode === 'edit' && isEditorPage @@ -142,41 +173,77 @@ export default function Layout() { 'edit-site-layout', navigateRegionsProps.className, { + 'is-distraction-free': isDistractionFree && isEditing, 'is-full-canvas': isFullCanvas, 'is-edit-mode': isEditing, 'has-fixed-toolbar': hasFixedToolbar, } ) } > - - - - { isEditorPage && isEditing && ( - - { isEditing &&
} - - ) } - + delay: 0.8, + delayChildren: 0.8, + }, // How long to wait before the header exits + }, + isDistractionFreeHovering: { + opacity: 1, + transition: { + type: 'tween', + delay: 0.2, + delayChildren: 0.2, + }, // How long to wait before the header shows + }, + view: { opacity: 1 }, + edit: { opacity: 1 }, + } } + whileHover={ + isDistractionFree + ? 'isDistractionFreeHovering' + : undefined + } + animate={ headerAnimationState } + > + + + + { isEditorPage && isEditing && ( + + { isEditing &&
} + + ) } + +
diff --git a/packages/edit-site/src/components/layout/style.scss b/packages/edit-site/src/components/layout/style.scss index 19ae1e60fed2f..d42a9e5c5ae20 100644 --- a/packages/edit-site/src/components/layout/style.scss +++ b/packages/edit-site/src/components/layout/style.scss @@ -47,6 +47,10 @@ } } +.edit-site-layout__header-container { + z-index: z-index(".edit-site-layout__header-container"); +} + .edit-site-layout__header { height: $header-height; display: flex; @@ -260,6 +264,9 @@ // so the fixed toolbar can be positioned on top of it // but only on desktop @include break-medium() { + .edit-site-layout__canvas-container { + z-index: 5; + } .edit-site-site-hub { z-index: 4; } @@ -269,3 +276,42 @@ } } +.is-edit-mode.is-distraction-free { + + .edit-site-layout__header-container { + height: $header-height; + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: z-index(".edit-site-layout__header-container"); + width: 100%; + + // We need ! important because we override inline styles + // set by the motion component. + &:focus-within { + opacity: 1 !important; + div { + transform: translateX(0) translateY(0) translateZ(0) !important; + } + + .edit-site-layout__header { + opacity: 1 !important; + } + } + } + + .edit-site-site-hub, + .edit-site-layout__header { + position: absolute; + top: 0; + z-index: z-index(".edit-site-layout__header"); + } + .edit-site-site-hub { + z-index: z-index(".edit-site-layout__hub"); + } + .edit-site-layout__header { + width: 100%; + } + +} diff --git a/packages/edit-site/src/components/preferences-modal/enable-feature.js b/packages/edit-site/src/components/preferences-modal/enable-feature.js index ca4a8a1bb1a32..9cd2105ba69ff 100644 --- a/packages/edit-site/src/components/preferences-modal/enable-feature.js +++ b/packages/edit-site/src/components/preferences-modal/enable-feature.js @@ -6,14 +6,17 @@ import { ___unstablePreferencesModalBaseOption as BaseOption } from '@wordpress/ import { store as preferencesStore } from '@wordpress/preferences'; export default function EnableFeature( props ) { - const { featureName, ...remainingProps } = props; + const { featureName, onToggle = () => {}, ...remainingProps } = props; const isChecked = useSelect( ( select ) => !! select( preferencesStore ).get( 'core/edit-site', featureName ), [ featureName ] ); const { toggle } = useDispatch( preferencesStore ); - const onChange = () => toggle( 'core/edit-site', featureName ); + const onChange = () => { + onToggle(); + toggle( 'core/edit-site', featureName ); + }; return ( { + registry.batch( () => { + setPreference( 'core/edit-site', 'fixedToolbar', false ); + setIsInserterOpened( false ); + setIsListViewOpened( false ); + closeGeneralSidebar(); + } ); + }; + const sections = useMemo( () => [ { name: 'general', @@ -29,6 +46,14 @@ export default function EditSitePreferencesModal( { 'Customize options related to the block editor interface and editing flow.' ) } > + {}; export function SidebarNavigationItemGlobalStyles( props ) { - const { openGeneralSidebar } = useDispatch( editSiteStore ); + const { openGeneralSidebar, toggleFeature } = useDispatch( editSiteStore ); const { setCanvasMode } = unlock( useDispatch( editSiteStore ) ); + const { createNotice } = useDispatch( noticesStore ); const hasGlobalStyleVariations = useSelect( ( select ) => !! select( @@ -53,9 +55,19 @@ export function SidebarNavigationItemGlobalStyles( props ) { { - // switch to edit mode. + // Disable distraction free mode. + toggleFeature( 'distractionFree', false ); + createNotice( + 'info', + __( 'Distraction free mode turned off' ), + { + isDismissible: true, + type: 'snackbar', + } + ); + // Switch to edit mode. setCanvasMode( 'edit' ); - // open global styles sidebar. + // Open global styles sidebar. openGeneralSidebar( 'edit-site/global-styles' ); } } /> diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index e696a441bb10d..a4698c02aff90 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -63,6 +63,7 @@ export function initializeEditor( id, settings ) { editorMode: 'visual', fixedToolbar: false, focusMode: false, + distractionFree: false, keepCaretInsideBlock: false, welcomeGuide: true, welcomeGuideStyles: true, diff --git a/packages/edit-site/src/store/selectors.js b/packages/edit-site/src/store/selectors.js index e8dcd03da7341..654b3c321ae93 100644 --- a/packages/edit-site/src/store/selectors.js +++ b/packages/edit-site/src/store/selectors.js @@ -107,6 +107,10 @@ export const getSettings = createSelector( ...state.settings, outlineMode: true, focusMode: !! __unstableGetPreference( state, 'focusMode' ), + isDistractionFree: !! __unstableGetPreference( + state, + 'distractionFree' + ), hasFixedToolbar: !! __unstableGetPreference( state, 'fixedToolbar' @@ -143,6 +147,7 @@ export const getSettings = createSelector( getCanUserCreateMedia( state ), state.settings, __unstableGetPreference( state, 'focusMode' ), + __unstableGetPreference( state, 'distractionFree' ), __unstableGetPreference( state, 'fixedToolbar' ), __unstableGetPreference( state, 'keepCaretInsideBlock' ), __unstableGetPreference( state, 'showIconLabels' ), diff --git a/packages/edit-site/src/store/test/selectors.js b/packages/edit-site/src/store/test/selectors.js index ac4778f03c7b5..9380f8ea4d276 100644 --- a/packages/edit-site/src/store/test/selectors.js +++ b/packages/edit-site/src/store/test/selectors.js @@ -77,6 +77,7 @@ describe( 'selectors', () => { outlineMode: true, focusMode: false, hasFixedToolbar: false, + isDistractionFree: false, keepCaretInsideBlock: false, showIconLabels: false, __experimentalSetIsInserterOpened: setInserterOpened, @@ -102,6 +103,7 @@ describe( 'selectors', () => { key: 'value', focusMode: true, hasFixedToolbar: true, + isDistractionFree: false, keepCaretInsideBlock: false, showIconLabels: false, __experimentalSetIsInserterOpened: setInserterOpened, diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 8a71fb5deca65..400e6799996cc 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -47,6 +47,7 @@ const BLOCK_EDITOR_SETTINGS = [ 'enableCustomUnits', 'enableOpenverseMediaCategory', 'focusMode', + 'distractionFree', 'fontSizes', 'gradients', 'generateAnchors', diff --git a/packages/interface/src/components/interface-skeleton/index.js b/packages/interface/src/components/interface-skeleton/index.js index 43e4e532e8eab..baf98d153ed87 100644 --- a/packages/interface/src/components/interface-skeleton/index.js +++ b/packages/interface/src/components/interface-skeleton/index.js @@ -33,6 +33,15 @@ function useHTMLClass( className ) { }, [ className ] ); } +const headerVariants = { + hidden: { opacity: 0 }, + hover: { + opacity: 1, + transition: { type: 'tween', delay: 0.2, delayChildren: 0.2 }, + }, + distractionFreeInactive: { opacity: 1, transition: { delay: 0 } }, +}; + function InterfaceSkeleton( { isDistractionFree, @@ -74,15 +83,6 @@ function InterfaceSkeleton( const mergedLabels = { ...defaultLabels, ...labels }; - const headerVariants = { - hidden: { opacity: 0 }, - hover: { - opacity: 1, - transition: { type: 'tween', delay: 0.2, delayChildren: 0.2 }, - }, - distractionFreeInactive: { opacity: 1, transition: { delay: 0 } }, - }; - return (