From 85bca4970d2c9efc6f89f03f28c915d7f8ff0599 Mon Sep 17 00:00:00 2001 From: Christina Yang Date: Sat, 20 Dec 2025 16:32:42 -0800 Subject: [PATCH 01/20] migrate to react-modal-sheet --- package.json | 2 + src/components/CommandMenu/CommandMenu.tsx | 284 ++++++++++----------- yarn.lock | 86 +++++++ 3 files changed, 219 insertions(+), 153 deletions(-) diff --git a/package.json b/package.json index c5eb852eb48..b6d66948808 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "lottie-react": "^2.4.1", "marked": "^17.0.1", "moize": "^6.1.6", + "motion": "^12.23.26", "murmurhash3js": "^3.0.1", "nanoid": "^5.1.6", "openai": "^5.20.3", @@ -127,6 +128,7 @@ "react-dom": "^19.1.1", "react-error-boundary": "^6.0.0", "react-gravatar": "^2.6.3", + "react-modal-sheet": "^5.2.1", "react-native-web": "^0.21.2", "react-redux": "^9.2.0", "react-signature-pad-wrapper": "^4.3.2", diff --git a/src/components/CommandMenu/CommandMenu.tsx b/src/components/CommandMenu/CommandMenu.tsx index 5f2436f8848..4d8fb8eaf86 100644 --- a/src/components/CommandMenu/CommandMenu.tsx +++ b/src/components/CommandMenu/CommandMenu.tsx @@ -1,10 +1,10 @@ -import SwipeableDrawer from '@mui/material/SwipeableDrawer' import _ from 'lodash' +import { useTransform } from 'motion/react' import pluralize from 'pluralize' import { FC, useCallback, useRef } from 'react' +import { Sheet, SheetRef } from 'react-modal-sheet' import { useDispatch, useSelector } from 'react-redux' import { css } from '../../../styled-system/css' -import { token } from '../../../styled-system/tokens' import { clearMulticursorsActionCreator as clearMulticursors } from '../../actions/clearMulticursors' import { toggleDropdownActionCreator as toggleDropdown } from '../../actions/toggleDropdown' import { isTouch } from '../../browser' @@ -18,9 +18,7 @@ import outdent from '../../commands/outdent' import swapParent from '../../commands/swapParent' import uncategorize from '../../commands/uncategorize' import isTutorial from '../../selectors/isTutorial' -import durations from '../../util/durations' import fastClick from '../../util/fastClick' -import FadeTransition from '../FadeTransition' import PanelCommand from './PanelCommand' import PanelCommandGroup from './PanelCommandGroup' @@ -61,31 +59,6 @@ const MultiselectMessage: FC = () => { ) } -/** Command menu gradient overlay. Fades in when the Command Menu opens. */ -const Overlay = () => { - const showCommandMenu = useSelector(state => state.showCommandMenu) - const ref = useRef(null) - return ( - -
- - ) -} - /** * A panel that displays the command menu. */ @@ -93,33 +66,26 @@ const CommandMenu = () => { const dispatch = useDispatch() const showCommandMenu = useSelector(state => state.showCommandMenu) const isTutorialOn = useSelector(isTutorial) + const ref = useRef(null) - const onOpen = useCallback(() => { - dispatch(toggleDropdown({ dropDownType: 'commandMenu', value: true })) - }, [dispatch]) + const opacity = useTransform(() => { + const y = ref.current?.yInverted.get() ?? 0 + const height = ref.current?.height ?? 0 + console.log('ref.current?.yInverted.get()', ref.current?.yInverted.get(), y / height) + return y / height + }) const onClose = useCallback(() => { dispatch([toggleDropdown({ dropDownType: 'commandMenu', value: false }), clearMulticursors()]) }, [dispatch]) if (isTouch && !isTutorialOn) { + console.log('showCommandMenu', showCommandMenu) return ( - + { maxHeight: '70%', pointerEvents: 'auto', boxShadow: 'none', - }, - }} - ModalProps={{ - disableAutoFocus: true, - disableEnforceFocus: true, - disableRestoreFocus: true, - style: { - pointerEvents: 'none', - zIndex: token('zIndex.modal'), - backgroundColor: 'transparent', - }, - }} - > -
-
-
- - -
+
- +
+ {/* ISSUE: CSS TRANSFORM CUTS BACKDROP OFF, when not transform: none */} +
- +
+
+
- Done - + + + + + + + + + + + +
-
- - - - - - - - - - - -
-
-
- + + + ) } } diff --git a/yarn.lock b/yarn.lock index b4ea27045d2..e0c20424c89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9817,6 +9817,7 @@ __metadata: lottie-react: "npm:^2.4.1" marked: "npm:^17.0.1" moize: "npm:^6.1.6" + motion: "npm:^12.23.26" murmurhash3js: "npm:^3.0.1" nanoid: "npm:^5.1.6" npm-run-all2: "npm:^8.0.4" @@ -9838,6 +9839,7 @@ __metadata: react-dom: "npm:^19.1.1" react-error-boundary: "npm:^6.0.0" react-gravatar: "npm:^2.6.3" + react-modal-sheet: "npm:^5.2.1" react-native-web: "npm:^0.21.2" react-redux: "npm:^9.2.0" react-signature-pad-wrapper: "npm:^4.3.2" @@ -11685,6 +11687,28 @@ __metadata: languageName: node linkType: hard +"framer-motion@npm:^12.23.26": + version: 12.23.26 + resolution: "framer-motion@npm:12.23.26" + dependencies: + motion-dom: "npm:^12.23.23" + motion-utils: "npm:^12.23.6" + tslib: "npm:^2.4.0" + peerDependencies: + "@emotion/is-prop-valid": "*" + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/is-prop-valid": + optional: true + react: + optional: true + react-dom: + optional: true + checksum: 10c0/7dbbc4a392b969804e04a056e3d89a55bf31572dc7f6cd79050b90616fbb84a1762e4ac4e2537c735d347bbf4ceebea126f5c090234149b12cffa3ea6c518a34 + languageName: node + linkType: hard + "fresh@npm:0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" @@ -15871,6 +15895,43 @@ __metadata: languageName: node linkType: hard +"motion-dom@npm:^12.23.23": + version: 12.23.23 + resolution: "motion-dom@npm:12.23.23" + dependencies: + motion-utils: "npm:^12.23.6" + checksum: 10c0/139705731085063519b88f23fcc5b1c13e15707a4ff3365da02ef9a4bf2a2d8ebed9a151c57e7f215ccd9e822365d93c16e28e619fbf25611f61dcff5ee81d75 + languageName: node + linkType: hard + +"motion-utils@npm:^12.23.6": + version: 12.23.6 + resolution: "motion-utils@npm:12.23.6" + checksum: 10c0/c058e8ba6423b3baa63e985bcc669877ee7d9579d938f5348b4e60c5ea1b4b33dd7f4877434436a4a5807f3cf00370d3fd4079a6fdd6309c5c87aa62b311a897 + languageName: node + linkType: hard + +"motion@npm:^12.23.26": + version: 12.23.26 + resolution: "motion@npm:12.23.26" + dependencies: + framer-motion: "npm:^12.23.26" + tslib: "npm:^2.4.0" + peerDependencies: + "@emotion/is-prop-valid": "*" + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/is-prop-valid": + optional: true + react: + optional: true + react-dom: + optional: true + checksum: 10c0/80ac9c2caaa149fb6feaad4146294b304dda52a643360063eb7a672bf7e7c8e134ec201ddcfafc0a918113004aed939ddbacf546b0e2297ea23955c78235c497 + languageName: node + linkType: hard + "mrmime@npm:^2.0.0": version: 2.0.1 resolution: "mrmime@npm:2.0.1" @@ -18136,6 +18197,18 @@ __metadata: languageName: node linkType: hard +"react-modal-sheet@npm:^5.2.1": + version: 5.2.1 + resolution: "react-modal-sheet@npm:5.2.1" + dependencies: + react-use-measure: "npm:2.1.7" + peerDependencies: + motion: ">=11" + react: ">=16" + checksum: 10c0/b622d670e83d3035b3b798564143a7f7cf34987e66c800837d6dc784a1d3afb606c7e67d89ea92bca65c73fa286a0e6bc076ce6d0eb1dc64b91d95f2e85ffe97 + languageName: node + linkType: hard + "react-native-web@npm:^0.21.2": version: 0.21.2 resolution: "react-native-web@npm:0.21.2" @@ -18271,6 +18344,19 @@ __metadata: languageName: node linkType: hard +"react-use-measure@npm:2.1.7": + version: 2.1.7 + resolution: "react-use-measure@npm:2.1.7" + peerDependencies: + react: ">=16.13" + react-dom: ">=16.13" + peerDependenciesMeta: + react-dom: + optional: true + checksum: 10c0/ff24130e6f95e853feb6892fb74af08dbc5aae3574b701169e3bc3adb392c3162f51a58ddfe39bb7337db13ae609bbec0bb51a9de8b5fae5420f9d17e1f8b542 + languageName: node + linkType: hard + "react@npm:^19.1.1": version: 19.2.0 resolution: "react@npm:19.2.0" From d5fa301a3444e0fd178a11796759770fcd664583 Mon Sep 17 00:00:00 2001 From: Christina Yang Date: Sun, 21 Dec 2025 00:47:08 -0600 Subject: [PATCH 02/20] feat: enhance CommandMenu with motion effects and improved backdrop styling --- src/components/CommandMenu/CommandMenu.tsx | 208 ++++++++++----------- 1 file changed, 103 insertions(+), 105 deletions(-) diff --git a/src/components/CommandMenu/CommandMenu.tsx b/src/components/CommandMenu/CommandMenu.tsx index 4d8fb8eaf86..502ff2f2f46 100644 --- a/src/components/CommandMenu/CommandMenu.tsx +++ b/src/components/CommandMenu/CommandMenu.tsx @@ -1,5 +1,6 @@ import _ from 'lodash' import { useTransform } from 'motion/react' +import { motion } from 'motion/react' import pluralize from 'pluralize' import { FC, useCallback, useRef } from 'react' import { Sheet, SheetRef } from 'react-modal-sheet' @@ -68,10 +69,13 @@ const CommandMenu = () => { const isTutorialOn = useSelector(isTutorial) const ref = useRef(null) + const height = useTransform(() => { + return ref.current?.yInverted.get() ?? 0 + }) + const opacity = useTransform(() => { const y = ref.current?.yInverted.get() ?? 0 const height = ref.current?.height ?? 0 - console.log('ref.current?.yInverted.get()', ref.current?.yInverted.get(), y / height) return y / height }) @@ -83,6 +87,50 @@ const CommandMenu = () => { console.log('showCommandMenu', showCommandMenu) return ( + + + { overflow: 'visible', }} > -
-
- {/* ISSUE: CSS TRANSFORM CUTS BACKDROP OFF, when not transform: none */} -
+
-
+ -
-
-
- - - - - - - - - - - + Done +
+
+ + + + + + + + + + + +
From 8ca611555d6beefea56c344637f0ed63a61bfb17 Mon Sep 17 00:00:00 2001 From: Christina Yang Date: Sun, 21 Dec 2025 08:07:57 -0600 Subject: [PATCH 03/20] move falloff behind backdrop, and remove z-index on backdrop --- src/components/CommandMenu/CommandMenu.tsx | 28 ++++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/components/CommandMenu/CommandMenu.tsx b/src/components/CommandMenu/CommandMenu.tsx index 502ff2f2f46..1aa4b9a8e66 100644 --- a/src/components/CommandMenu/CommandMenu.tsx +++ b/src/components/CommandMenu/CommandMenu.tsx @@ -102,11 +102,25 @@ const CommandMenu = () => { height: height, }} /> + { bottom: 0, })} /> - { maxHeight: '70%', pointerEvents: 'auto', boxShadow: 'none', + zIndex: 'auto', }} > Date: Sun, 21 Dec 2025 08:10:26 -0600 Subject: [PATCH 04/20] chore: remove debug log for showCommandMenu in CommandMenu component --- src/components/CommandMenu/CommandMenu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/CommandMenu/CommandMenu.tsx b/src/components/CommandMenu/CommandMenu.tsx index 1aa4b9a8e66..0d844b305b9 100644 --- a/src/components/CommandMenu/CommandMenu.tsx +++ b/src/components/CommandMenu/CommandMenu.tsx @@ -84,7 +84,6 @@ const CommandMenu = () => { }, [dispatch]) if (isTouch && !isTutorialOn) { - console.log('showCommandMenu', showCommandMenu) return ( Date: Sun, 21 Dec 2025 08:13:04 -0600 Subject: [PATCH 05/20] fix: simplify height assignment and ensure backdrop z-index is overridden --- src/components/CommandMenu/CommandMenu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/CommandMenu/CommandMenu.tsx b/src/components/CommandMenu/CommandMenu.tsx index 0d844b305b9..181bd7c04c1 100644 --- a/src/components/CommandMenu/CommandMenu.tsx +++ b/src/components/CommandMenu/CommandMenu.tsx @@ -98,7 +98,7 @@ const CommandMenu = () => { height: 'calc(100% + 110px)', })} style={{ - height: height, + height, }} /> { /> Date: Wed, 24 Dec 2025 12:46:43 -0600 Subject: [PATCH 06/20] feat: enhance CommandMenu with improved opacity handling and backdrop styling --- src/components/CommandMenu/CommandMenu.tsx | 54 ++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/src/components/CommandMenu/CommandMenu.tsx b/src/components/CommandMenu/CommandMenu.tsx index 181bd7c04c1..4de55488b6b 100644 --- a/src/components/CommandMenu/CommandMenu.tsx +++ b/src/components/CommandMenu/CommandMenu.tsx @@ -1,5 +1,5 @@ import _ from 'lodash' -import { useTransform } from 'motion/react' +import { useMotionValueEvent, useTransform } from 'motion/react' import { motion } from 'motion/react' import pluralize from 'pluralize' import { FC, useCallback, useRef } from 'react' @@ -73,19 +73,49 @@ const CommandMenu = () => { return ref.current?.yInverted.get() ?? 0 }) + const blurHeight = useTransform(height, height => height + 110) + const opacity = useTransform(() => { const y = ref.current?.yInverted.get() ?? 0 const height = ref.current?.height ?? 0 + if (height === 0) return 0 return y / height }) + const bottom = useTransform(() => { + const y = ref.current?.y.get() ?? 0 + return -y + }) + + const backdropRef = useRef(null) + + useMotionValueEvent(opacity, 'change', latest => { + if (!backdropRef.current) return + /* + * The opacity is controlled via a CSS variable to ensure it takes precedence + * over any inline styles set by react-modal-sheet. + * This is because MotionValues cannot be set with '!important', so we use this + * workaround to ensure the opacity takes precedence over library styles. + */ + backdropRef.current.style.setProperty('--opacity', latest.toString(), 'important') + }) + const onClose = useCallback(() => { dispatch([toggleDropdown({ dropDownType: 'commandMenu', value: false }), clearMulticursors()]) }, [dispatch]) if (isTouch && !isTutorialOn) { return ( - + { height: 'calc(100% + 110px)', })} style={{ - height, + height: blurHeight, }} /> { style={{ height }} /> { height: '100vh', width: '100%', bottom: 0, + '--opacity': '0', // set initial value to 0 + /** + * The opacity is controlled via a CSS variable to ensure it takes precedence + * over any inline styles set by react-modal-sheet. + */ + opacity: 'var(--opacity) !important', })} /> { pointerEvents: 'auto', boxShadow: 'none', zIndex: 'auto', + bottom, }} + className={css({ + /** + * Override inline transform styles, to rely only on bottom. + * This way no new stacking context is created. + * This needs to be set in className, as react-modal-sheet + * will override the transform value passed in inside style. + */ + transform: 'none !important', + })} > Date: Wed, 24 Dec 2025 16:41:36 -0600 Subject: [PATCH 07/20] fix: replace Sheet.Backdrop with motion.div for improved backdrop styling and opacity control --- src/components/CommandMenu/CommandMenu.tsx | 26 ++-------------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/src/components/CommandMenu/CommandMenu.tsx b/src/components/CommandMenu/CommandMenu.tsx index 4de55488b6b..cae7c91996b 100644 --- a/src/components/CommandMenu/CommandMenu.tsx +++ b/src/components/CommandMenu/CommandMenu.tsx @@ -87,19 +87,6 @@ const CommandMenu = () => { return -y }) - const backdropRef = useRef(null) - - useMotionValueEvent(opacity, 'change', latest => { - if (!backdropRef.current) return - /* - * The opacity is controlled via a CSS variable to ensure it takes precedence - * over any inline styles set by react-modal-sheet. - * This is because MotionValues cannot be set with '!important', so we use this - * workaround to ensure the opacity takes precedence over library styles. - */ - backdropRef.current.style.setProperty('--opacity', latest.toString(), 'important') - }) - const onClose = useCallback(() => { dispatch([toggleDropdown({ dropDownType: 'commandMenu', value: false }), clearMulticursors()]) }, [dispatch]) @@ -144,11 +131,7 @@ const CommandMenu = () => { })} style={{ height }} /> - { height: '100vh', width: '100%', bottom: 0, - '--opacity': '0', // set initial value to 0 - /** - * The opacity is controlled via a CSS variable to ensure it takes precedence - * over any inline styles set by react-modal-sheet. - */ - opacity: 'var(--opacity) !important', })} + style={{ opacity }} /> Date: Wed, 24 Dec 2025 16:41:54 -0600 Subject: [PATCH 08/20] fix: remove unused import of useMotionValueEvent in CommandMenu component --- src/components/CommandMenu/CommandMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CommandMenu/CommandMenu.tsx b/src/components/CommandMenu/CommandMenu.tsx index cae7c91996b..03ff0222ca7 100644 --- a/src/components/CommandMenu/CommandMenu.tsx +++ b/src/components/CommandMenu/CommandMenu.tsx @@ -1,5 +1,5 @@ import _ from 'lodash' -import { useMotionValueEvent, useTransform } from 'motion/react' +import { useTransform } from 'motion/react' import { motion } from 'motion/react' import pluralize from 'pluralize' import { FC, useCallback, useRef } from 'react' From c8e515a71ae93ae248ff9f421d7c28bff795544f Mon Sep 17 00:00:00 2001 From: Christina Yang Date: Fri, 26 Dec 2025 23:27:28 -0600 Subject: [PATCH 09/20] feat: add glow effect readiness to PanelCommand, to fix glow blending with wrong backdrop --- src/components/CommandMenu/CommandMenu.tsx | 35 +++++++++++++-------- src/components/CommandMenu/PanelCommand.tsx | 9 ++++-- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/components/CommandMenu/CommandMenu.tsx b/src/components/CommandMenu/CommandMenu.tsx index 03ff0222ca7..b656f4af8e0 100644 --- a/src/components/CommandMenu/CommandMenu.tsx +++ b/src/components/CommandMenu/CommandMenu.tsx @@ -2,7 +2,7 @@ import _ from 'lodash' import { useTransform } from 'motion/react' import { motion } from 'motion/react' import pluralize from 'pluralize' -import { FC, useCallback, useRef } from 'react' +import { FC, useCallback, useRef, useState } from 'react' import { Sheet, SheetRef } from 'react-modal-sheet' import { useDispatch, useSelector } from 'react-redux' import { css } from '../../../styled-system/css' @@ -87,6 +87,8 @@ const CommandMenu = () => { return -y }) + const [isFullyOpen, setIsFullyOpen] = useState(false) + const onClose = useCallback(() => { dispatch([toggleDropdown({ dropDownType: 'commandMenu', value: false }), clearMulticursors()]) }, [dispatch]) @@ -94,14 +96,17 @@ const CommandMenu = () => { if (isTouch && !isTutorialOn) { return ( { + setIsFullyOpen(true) + }} + onCloseStart={() => { + setIsFullyOpen(false) + }} ref={ref} isOpen={showCommandMenu} onClose={onClose} detent='content' unstyled - style={{ - position: 'static', - }} > { gridRowGap: '1rem', })} > - - - - + + + + - - + + - - - + + +
diff --git a/src/components/CommandMenu/PanelCommand.tsx b/src/components/CommandMenu/PanelCommand.tsx index cee220055a8..202bbf5a4be 100644 --- a/src/components/CommandMenu/PanelCommand.tsx +++ b/src/components/CommandMenu/PanelCommand.tsx @@ -15,6 +15,8 @@ interface PanelCommandProps { command: Command /** The size of the button. */ size?: 'small' | 'medium' + /** Whether the glow effect is ready to be displayed. */ + isGlowReady: boolean } interface ActiveButtonGlowImageProps { @@ -54,7 +56,7 @@ const ActiveButtonGlowImage: FC = ({ isActive, type } /** A single button in the Panel Command Grid. */ -const PanelCommand: FC = ({ command, size }) => { +const PanelCommand: FC = ({ command, size, isGlowReady }) => { const [isAnimated, setIsAnimated] = useState(false) const { svg, isActive, canExecute } = command @@ -90,8 +92,9 @@ const PanelCommand: FC = ({ command, size }) => { : { gridColumn: 'span 1', gridTemplateColumns: 'auto', gridTemplateAreas: `"command"` }), })} > - - + {/* For the first fade in to work properly, ActiveButtonGlowImage must be already mounted with opacity 0. */} + +
Date: Fri, 26 Dec 2025 23:44:35 -0600 Subject: [PATCH 10/20] fix: add mountPoint prop to CommandMenu to fix progressive blur --- src/components/AppComponent.tsx | 6 ++++-- src/components/CommandMenu/CommandMenu.tsx | 9 +++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/AppComponent.tsx b/src/components/AppComponent.tsx index 3deb447ed2a..88458b1c3ee 100644 --- a/src/components/AppComponent.tsx +++ b/src/components/AppComponent.tsx @@ -1,7 +1,7 @@ import { Capacitor } from '@capacitor/core' import { StatusBar, Style } from '@capacitor/status-bar' import _ from 'lodash' -import React, { FC, PropsWithChildren, useEffect, useLayoutEffect } from 'react' +import React, { FC, PropsWithChildren, useEffect, useLayoutEffect, useRef } from 'react' import { useSelector } from 'react-redux' import { WebviewBackground } from 'webview-background' import { css } from '../../styled-system/css' @@ -114,6 +114,7 @@ const AppComponent: FC = () => { const fontSize = useSelector(state => state.fontSize) const showModal = useSelector(state => state.showModal) const tutorial = useSelector(isTutorial) + const rootRef = useRef(null) useEffect(() => { WebviewBackground.changeBackgroundColor({ color: colors.bg }) @@ -162,6 +163,7 @@ const AppComponent: FC = () => { /* safeAreaTop applies for rounded screens */ paddingTop: 'safeAreaTop', })} + ref={rootRef} > @@ -205,7 +207,7 @@ const AppComponent: FC = () => { {/* NavBar must be outside MultiGestureIfTouch in order to have a higher stacking order than the Sidebar. Otherwise the user can accidentally activate the Sidebar edge swipe when trying to tap the Home icon. */} - +
diff --git a/src/components/CommandMenu/CommandMenu.tsx b/src/components/CommandMenu/CommandMenu.tsx index b656f4af8e0..4565ec6b14e 100644 --- a/src/components/CommandMenu/CommandMenu.tsx +++ b/src/components/CommandMenu/CommandMenu.tsx @@ -3,7 +3,7 @@ import { useTransform } from 'motion/react' import { motion } from 'motion/react' import pluralize from 'pluralize' import { FC, useCallback, useRef, useState } from 'react' -import { Sheet, SheetRef } from 'react-modal-sheet' +import { Sheet, SheetProps, SheetRef } from 'react-modal-sheet' import { useDispatch, useSelector } from 'react-redux' import { css } from '../../../styled-system/css' import { clearMulticursorsActionCreator as clearMulticursors } from '../../actions/clearMulticursors' @@ -63,7 +63,7 @@ const MultiselectMessage: FC = () => { /** * A panel that displays the command menu. */ -const CommandMenu = () => { +const CommandMenu = ({ mountPoint }: Pick) => { const dispatch = useDispatch() const showCommandMenu = useSelector(state => state.showCommandMenu) const isTutorialOn = useSelector(isTutorial) @@ -107,6 +107,11 @@ const CommandMenu = () => { onClose={onClose} detent='content' unstyled + mountPoint={mountPoint} + style={{ + /** Required for progressive blur to properly blend with background. */ + position: 'static', + }} > Date: Sat, 27 Dec 2025 00:04:42 -0600 Subject: [PATCH 11/20] refactor: remove unused commandCenterDrawer duration from durationsConfig and fadeTransition --- src/durations.config.ts | 2 -- src/recipes/fadeTransition.ts | 5 ----- 2 files changed, 7 deletions(-) diff --git a/src/durations.config.ts b/src/durations.config.ts index e32d8fed05c..1f4229f1bf3 100644 --- a/src/durations.config.ts +++ b/src/durations.config.ts @@ -37,8 +37,6 @@ const durationsConfig = { /* Duration for context view disappearing text animations */ disappearingUpperRight: 500, disappearingLowerLeft: 500, - /* Duration for command center swipable drawer animation. */ - commandCenterDrawer: 400, activeButtonGlowLuminosity: 400, activeButtonGlowSaturation: 400, } as const diff --git a/src/recipes/fadeTransition.ts b/src/recipes/fadeTransition.ts index 6608b786d5f..4c3750ea540 100644 --- a/src/recipes/fadeTransition.ts +++ b/src/recipes/fadeTransition.ts @@ -29,11 +29,6 @@ const fadeTransitionRecipe = defineSlotRecipe({ enterActive: { transition: `opacity {durations.medium} ease 0ms` }, exitActive: { transition: `opacity {durations.medium} ease 0ms` }, }, - commandCenterDrawer: { - // Easing follows that of Material UI SwipeableDrawer. - enterActive: { transition: `opacity {durations.commandCenterDrawer} cubic-bezier(0, 0, 0.2, 1) 0ms` }, - exitActive: { transition: `opacity {durations.commandCenterDrawer} cubic-bezier(0.4, 0, 0.2, 1) 0ms` }, - }, activeButtonGlowLuminosity: { enter: { opacity: 0 }, enterActive: { opacity: 0.75, transition: `opacity {durations.activeButtonGlowLuminosity} ease 0ms` }, From 380a0f4db0a90dcc52fb2423aa0bf828287a716c Mon Sep 17 00:00:00 2001 From: Christina Yang Date: Sat, 27 Dec 2025 09:32:17 -0600 Subject: [PATCH 12/20] refactor: restructure CommandCenter component for improved layout and clarity --- .../CommandCenter/CommandCenter.tsx | 273 +++++++++--------- 1 file changed, 137 insertions(+), 136 deletions(-) diff --git a/src/components/CommandCenter/CommandCenter.tsx b/src/components/CommandCenter/CommandCenter.tsx index acd679c2ef1..435cea4f155 100644 --- a/src/components/CommandCenter/CommandCenter.tsx +++ b/src/components/CommandCenter/CommandCenter.tsx @@ -95,27 +95,12 @@ const CommandCenter = ({ mountPoint }: Pick) => { if (isTouch && !isTutorialOn) { return ( - { - setIsFullyOpen(true) - }} - onCloseStart={() => { - setIsFullyOpen(false) - }} - ref={ref} - isOpen={showCommandCenter} - onClose={onClose} - detent='content' - unstyled - mountPoint={mountPoint} - style={{ - /** Required for progressive blur to properly blend with background. */ - position: 'static', - }} - > + <> ) => { height: blurHeight, }} /> - - - { + setIsFullyOpen(true) }} - className={css({ - /** - * Override inline transform styles, to rely only on bottom. - * This way no new stacking context is created. - * This needs to be set in className, as react-modal-sheet - * will override the transform value passed in inside style. - */ - transform: 'none !important', - })} + onCloseStart={() => { + setIsFullyOpen(false) + }} + ref={ref} + isOpen={showCommandCenter} + onClose={onClose} + detent='content' + unstyled + mountPoint={mountPoint} > - + + -
-
+
- +
+ +
+
+
+ + + + + + + + + + +
-
- - - - - - - - - - - -
-
- - - + + + + ) } } From 1ab94bf7a7a2bea4944a2621654c7a0d9e5bbcab Mon Sep 17 00:00:00 2001 From: Christina Yang Date: Sat, 27 Dec 2025 09:34:25 -0600 Subject: [PATCH 13/20] fix: change CommandCenter position from absolute to fixed for improved layout --- src/components/CommandCenter/CommandCenter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CommandCenter/CommandCenter.tsx b/src/components/CommandCenter/CommandCenter.tsx index 435cea4f155..4bb505b12f1 100644 --- a/src/components/CommandCenter/CommandCenter.tsx +++ b/src/components/CommandCenter/CommandCenter.tsx @@ -102,8 +102,8 @@ const CommandCenter = ({ mountPoint }: Pick) => { * from the background content due to the fixed position of the parent. */ className={css({ + position: 'fixed', pointerEvents: 'none', - position: 'absolute', backdropFilter: 'blur(2px)', mask: 'linear-gradient(180deg, {colors.bgTransparent} 0%, black 110px, black 100%)', bottom: 0, From 92655c7121839fdacc4aa6eeb263f5d28a291250 Mon Sep 17 00:00:00 2001 From: Christina Yang Date: Fri, 2 Jan 2026 12:02:19 -0500 Subject: [PATCH 14/20] Fix Safari blend mode rendering bug by adding will-change property --- src/components/CommandCenter/CommandCenter.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/CommandCenter/CommandCenter.tsx b/src/components/CommandCenter/CommandCenter.tsx index 734019fb7bd..c1afd1dbc84 100644 --- a/src/components/CommandCenter/CommandCenter.tsx +++ b/src/components/CommandCenter/CommandCenter.tsx @@ -237,6 +237,8 @@ const CommandCenter = ({ mountPoint }: Pick) => { background: 'fgOverlay20', borderRadius: 46, mixBlendMode: 'soft-light', + // Safari fix: will-change forces GPU layer creation which fixes blend mode rendering bug. + willChange: 'transform', })} />
diff --git a/src/components/CommandCenter/PanelCommand.tsx b/src/components/CommandCenter/PanelCommand.tsx index f4c0cc55676..b184de85672 100644 --- a/src/components/CommandCenter/PanelCommand.tsx +++ b/src/components/CommandCenter/PanelCommand.tsx @@ -15,8 +15,6 @@ interface PanelCommandProps { command: Command /** The size of the button. */ size?: 'small' | 'medium' - /** Whether the glow effect is ready to be displayed. */ - isGlowReady: boolean } interface ActiveButtonGlowImageProps { @@ -34,6 +32,8 @@ const ActiveButtonGlowImage: FC = ({ isActive, type in={isActive} unmountOnExit nodeRef={nodeRef} + /** When `in={true}` on mount, ensures the initial opacity of the element is correct. */ + appear >
= ({ isActive, type } /** A single button in the Panel Command Grid. */ -const PanelCommand: FC = ({ command, size, isGlowReady }) => { +const PanelCommand: FC = ({ command, size }) => { const [isAnimated, setIsAnimated] = useState(false) const { svg, isActive, canExecute } = command @@ -93,8 +93,8 @@ const PanelCommand: FC = ({ command, size, isGlowReady }) => })} > {/* For the first fade in to work properly, ActiveButtonGlowImage must be already mounted with opacity 0. */} - - + +
Date: Sat, 3 Jan 2026 00:31:12 -0500 Subject: [PATCH 17/20] Remove unnecessary mixBlendMode and will-change properties from CommandCenter button styles --- src/components/CommandCenter/CommandCenter.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/CommandCenter/CommandCenter.tsx b/src/components/CommandCenter/CommandCenter.tsx index 2089b163b7f..980385975a0 100644 --- a/src/components/CommandCenter/CommandCenter.tsx +++ b/src/components/CommandCenter/CommandCenter.tsx @@ -240,13 +240,10 @@ const CommandCenter = ({ mountPoint }: Pick) => { className={css({ all: 'unset', gridArea: 'button', - mixBlendMode: 'lighten', opacity: 0.5, fontWeight: 500, cursor: 'pointer', padding: '8px 16px', - // Safari fix: will-change forces GPU layer creation which fixes blend mode rendering bug. - willChange: 'transform', })} > Done From 7555d7a80ecb3894ba17ffc02e5976e9588ecf95 Mon Sep 17 00:00:00 2001 From: Christina Yang Date: Sat, 3 Jan 2026 00:44:54 -0500 Subject: [PATCH 18/20] Refactor CommandCenter component to improve sheet progress calculation and enhance opacity transition with cubicBezier easing --- src/components/CommandCenter/CommandCenter.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/CommandCenter/CommandCenter.tsx b/src/components/CommandCenter/CommandCenter.tsx index 980385975a0..e2a7a54acfb 100644 --- a/src/components/CommandCenter/CommandCenter.tsx +++ b/src/components/CommandCenter/CommandCenter.tsx @@ -1,5 +1,5 @@ import _ from 'lodash' -import { useTransform } from 'motion/react' +import { cubicBezier, useTransform } from 'motion/react' import { motion } from 'motion/react' import pluralize from 'pluralize' import { FC, useCallback, useRef } from 'react' @@ -76,6 +76,8 @@ const HiddenOverlay = () => { ) } +const ease = cubicBezier(0, 0, 0.2, 1) + /** * A panel that displays the Command Center. */ @@ -91,13 +93,15 @@ const CommandCenter = ({ mountPoint }: Pick) => { const blurHeight = useTransform(height, height => height + 110) - const opacity = useTransform(() => { + const sheetProgress = useTransform(() => { const y = ref.current?.yInverted.get() ?? 0 const height = ref.current?.height ?? 0 if (height === 0) return 0 - return y / height + return Math.min(Math.max(y / height, 0), 1) }) + const opacity = useTransform(sheetProgress, [0, 1], [0, 1], { ease }) + const bottom = useTransform(() => { const y = ref.current?.y.get() ?? 0 return -y From aa2b007a89e192acf5c8f2891e24b3f5325d1bec Mon Sep 17 00:00:00 2001 From: Christina Yang Date: Sat, 3 Jan 2026 00:54:13 -0500 Subject: [PATCH 19/20] Add transition configuration to match MUI SwipableDrawer enter easing --- src/components/CommandCenter/CommandCenter.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/CommandCenter/CommandCenter.tsx b/src/components/CommandCenter/CommandCenter.tsx index e2a7a54acfb..7818b408d24 100644 --- a/src/components/CommandCenter/CommandCenter.tsx +++ b/src/components/CommandCenter/CommandCenter.tsx @@ -1,5 +1,5 @@ import _ from 'lodash' -import { cubicBezier, useTransform } from 'motion/react' +import { Transition, cubicBezier, useTransform } from 'motion/react' import { motion } from 'motion/react' import pluralize from 'pluralize' import { FC, useCallback, useRef } from 'react' @@ -18,6 +18,7 @@ import note from '../../commands/note' import outdent from '../../commands/outdent' import swapParent from '../../commands/swapParent' import uncategorize from '../../commands/uncategorize' +import durationsConfig from '../../durations.config' import isTutorial from '../../selectors/isTutorial' import fastClick from '../../util/fastClick' import PanelCommand from './PanelCommand' @@ -78,6 +79,12 @@ const HiddenOverlay = () => { const ease = cubicBezier(0, 0, 0.2, 1) +const transition: Transition = { + type: 'tween', + duration: durationsConfig.medium / 100, + ease, +} + /** * A panel that displays the Command Center. */ @@ -141,6 +148,7 @@ const CommandCenter = ({ mountPoint }: Pick) => { detent='content' unstyled mountPoint={mountPoint} + transition={transition} /** Fixes sheet shifting up on ios when it opens. */ disableScrollLocking > From 70584d23b7d5bc758fd729747e900c273bfcb2d9 Mon Sep 17 00:00:00 2001 From: Christina Yang Date: Sat, 3 Jan 2026 01:01:31 -0500 Subject: [PATCH 20/20] Fix transition configuration in CommandCenter to use SheetTweenConfig and update easing definition --- src/components/CommandCenter/CommandCenter.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/CommandCenter/CommandCenter.tsx b/src/components/CommandCenter/CommandCenter.tsx index 7818b408d24..4069b2bb02b 100644 --- a/src/components/CommandCenter/CommandCenter.tsx +++ b/src/components/CommandCenter/CommandCenter.tsx @@ -1,9 +1,9 @@ import _ from 'lodash' -import { Transition, cubicBezier, useTransform } from 'motion/react' +import { cubicBezier, useTransform } from 'motion/react' import { motion } from 'motion/react' import pluralize from 'pluralize' import { FC, useCallback, useRef } from 'react' -import { Sheet, SheetProps, SheetRef } from 'react-modal-sheet' +import { Sheet, SheetProps, SheetRef, SheetTweenConfig } from 'react-modal-sheet' import { useDispatch, useSelector } from 'react-redux' import { css } from '../../../styled-system/css' import { clearMulticursorsActionCreator as clearMulticursors } from '../../actions/clearMulticursors' @@ -77,12 +77,13 @@ const HiddenOverlay = () => { ) } -const ease = cubicBezier(0, 0, 0.2, 1) +const bezierDefinition = [0, 0, 0.2, 1] as const -const transition: Transition = { - type: 'tween', - duration: durationsConfig.medium / 100, - ease, +const ease = cubicBezier(...bezierDefinition) + +const tweenConfig: SheetTweenConfig = { + duration: durationsConfig.medium / 1000, + ease: bezierDefinition, } /** @@ -148,7 +149,7 @@ const CommandCenter = ({ mountPoint }: Pick) => { detent='content' unstyled mountPoint={mountPoint} - transition={transition} + tweenConfig={tweenConfig} /** Fixes sheet shifting up on ios when it opens. */ disableScrollLocking >