diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index c3405f2db2..63b5756c91 100644 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -355,6 +355,7 @@ export const TOUCH_CTX_MENU_TIMEOUT = 500; export const TITLE_TIMEOUT = 10000; export const VERSION_TIMEOUT = 30000; export const SCROLL_TIMEOUT = 100; +export const ZEN_MODE_TRANSITION_DURATION = 250; export const ZOOM_STEP = 0.1; export const MIN_ZOOM = 0.1; export const MAX_ZOOM = 30; diff --git a/packages/excalidraw/actions/actionCanvas.tsx b/packages/excalidraw/actions/actionCanvas.tsx index 6ec1add522..c4696fce58 100644 --- a/packages/excalidraw/actions/actionCanvas.tsx +++ b/packages/excalidraw/actions/actionCanvas.tsx @@ -155,17 +155,22 @@ export const actionZoomIn = register({ PanelComponent: ({ updateData }) => { const zoomValue = useAppStateValue((appState) => appState.zoom.value); return ( - = MAX_ZOOM} - onClick={() => { - updateData(null); - }} - /> + + = MAX_ZOOM} + onClick={() => { + updateData(null); + }} + /> + ); }, keyTest: (event) => @@ -202,17 +207,22 @@ export const actionZoomOut = register({ PanelComponent: ({ updateData }) => { const zoomValue = useAppStateValue((appState) => appState.zoom.value); return ( - { - updateData(null); - }} - /> + + { + updateData(null); + }} + /> + ); }, keyTest: (event) => @@ -254,11 +264,15 @@ export const actionResetZoom = register({ PanelComponent: ({ updateData }) => { const zoomValue = useAppStateValue((appState) => appState.zoom.value); return ( - + { updateData(null); diff --git a/packages/excalidraw/appState.ts b/packages/excalidraw/appState.ts index 7d9b0f3485..e448d2084b 100644 --- a/packages/excalidraw/appState.ts +++ b/packages/excalidraw/appState.ts @@ -77,6 +77,7 @@ export const getDefaultAppState = (): Omit< isBindingEnabled: true, bindingPreference: "enabled", isMidpointSnappingEnabled: true, + showHints: true, inputDevice: "auto", defaultSidebarDockedPreference: false, isLoading: false, @@ -225,6 +226,7 @@ const APP_STATE_STORAGE_CONF = (< boxSelectionMode: { browser: true, export: false, server: false }, bindingPreference: { browser: true, export: false, server: false }, isMidpointSnappingEnabled: { browser: true, export: false, server: false }, + showHints: { browser: true, export: false, server: false }, inputDevice: { browser: true, export: false, server: false }, defaultSidebarDockedPreference: { browser: true, diff --git a/packages/excalidraw/components/Actions.scss b/packages/excalidraw/components/Actions.scss index 26930a533f..954d0b5112 100644 --- a/packages/excalidraw/components/Actions.scss +++ b/packages/excalidraw/components/Actions.scss @@ -1,6 +1,6 @@ .zoom-actions, .undo-redo-buttons { - background-color: var(--island-bg-color); + background-color: var(--color-surface-low); border-radius: var(--border-radius-lg); box-shadow: 0 0 0 1px var(--color-surface-lowest); } @@ -8,7 +8,6 @@ .zoom-button, .undo-redo-buttons button { border-radius: 0 !important; - background-color: var(--color-surface-low) !important; font-size: 0.875rem !important; width: var(--lg-button-size); height: var(--lg-button-size); @@ -31,6 +30,24 @@ width: 3.75rem !important; justify-content: center; color: var(--text-primary-color); + + // unlike the other zoom buttons, has no .ToolIcon__icon to show the hover + .zoom-actions &:hover:not(:active) { + background-color: var(--button-hover-bg); + } +} + +// shifted left in zen mode (the wrapper rather than the button, so that the +// tooltip, positioned off the wrapper, moves along) +.reset-zoom-button-wrapper { + transition: transform var(--zen-mode-transition-duration) ease-in-out; +} + +// faded out in zen mode +.zoom-out-button-wrapper, +.zoom-in-button-wrapper { + transition: opacity var(--zen-mode-transition-duration) ease-in-out, + visibility var(--zen-mode-transition-duration); } .zoom-out-button { diff --git a/packages/excalidraw/components/Actions.tsx b/packages/excalidraw/components/Actions.tsx index 6e09452237..b10e855c89 100644 --- a/packages/excalidraw/components/Actions.tsx +++ b/packages/excalidraw/components/Actions.tsx @@ -897,10 +897,14 @@ export const UndoRedoActions = ({ }) => (
- {renderAction("undo")} + + {renderAction("undo")} +
- {renderAction("redo")} + + {renderAction("redo")} +
); diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 1d310179d3..a1cf8242f2 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -57,6 +57,7 @@ import { ARROW_TYPE, DEFAULT_REDUCED_GLOBAL_ALPHA, DEFAULT_STICKY_NOTE_SIZE, + ZEN_MODE_TRANSITION_DURATION, isLocalLink, normalizeLink, toValidURL, @@ -647,6 +648,7 @@ class App extends React.Component { ); private excalidrawContainerRef = React.createRef(); + private zenModeTransitionTimer = 0; public get ownerDocument(): Document { return ( @@ -2447,6 +2449,7 @@ class App extends React.Component { ? POINTER_EVENTS.disabled : POINTER_EVENTS.enabled, ["--right-sidebar-width" as any]: `${RIGHT_SIDEBAR_WIDTH}px`, + ["--zen-mode-transition-duration" as any]: `${ZEN_MODE_TRANSITION_DURATION}ms`, }} ref={this.excalidrawContainerRef} onDrop={this.isInteractionEnabled() ? this.handleAppOnDrop : undefined} @@ -3364,7 +3367,9 @@ class App extends React.Component { this.setState({ contextMenu: null, - openMenu: null, + // keep the main menu open when entering view mode, since that's where + // the user usually toggles it from (Preferences submenu) + openMenu: this.isInteractionEnabled() ? this.state.openMenu : null, openPopup: null, cursorButton: "up", bindMode: "orbit", @@ -3923,6 +3928,7 @@ class App extends React.Component { } } + clearTimeout(this.zenModeTransitionTimer); this.editorLifecycleEvents.emit("editor:unmount"); this.props.onUnmount?.(); this.props.onExcalidrawAPI?.(null); @@ -4249,6 +4255,34 @@ class App extends React.Component { } } + getSnapshotBeforeUpdate(prevProps: AppProps, prevState: AppState) { + // before the DOM update, so the transition styles are in place by the time + // the zen-mode class changes (layout reads during commit would otherwise + // resolve the new styles without them) + if (prevState.zenModeEnabled !== this.state.zenModeEnabled) { + this.markZenModeTransition(); + } + return null; + } + + /** + * Flags the container while toggling zen mode so that UI chrome + * (backgrounds etc.) animates only then, and hover transitions stay + * instant. A data attribute so that React doesn't reset it on re-render. + */ + private markZenModeTransition() { + const container = this.excalidrawContainerRef.current; + if (!container) { + return; + } + container.setAttribute("data-zen-mode-transition", ""); + clearTimeout(this.zenModeTransitionTimer); + this.zenModeTransitionTimer = window.setTimeout(() => { + container.removeAttribute("data-zen-mode-transition"); + // slightly past the CSS transition so it isn't cut short + }, ZEN_MODE_TRANSITION_DURATION + 100); + } + componentDidUpdate(prevProps: AppProps, prevState: AppState) { const renderOverridesUpdatePending = this.renderOverridesUpdatePending; this.renderOverridesUpdatePending = false; diff --git a/packages/excalidraw/components/HelpButton.tsx b/packages/excalidraw/components/HelpButton.tsx index 3dfe7da742..5dc6c9e79f 100644 --- a/packages/excalidraw/components/HelpButton.tsx +++ b/packages/excalidraw/components/HelpButton.tsx @@ -10,7 +10,7 @@ type HelpButtonProps = { }; export const HelpButton = (props: HelpButtonProps) => ( - +