Skip to content

Commit

Permalink
feat(mobile): swipe to open menu for explorer (#8953)
Browse files Browse the repository at this point in the history
close AF-1803

- bump theme
- extract `SwipeHelper` and add `speed`, `direction` detection support
- new mobile `SwipeMenu` component
- integrate `SwipeMenu` to open a menu in Explorer
- New `Haptics` module for mobile, implemented in `ios` and `mobile`(`navigator.vibrate()`)

![CleanShot 2024-11-28 at 12.25.14.gif](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/LakojjjzZNf6ogjOVwKE/cba36660-f38a-473b-b85c-33540a406835.gif)
  • Loading branch information
CatsJuice committed Dec 2, 2024
1 parent b600f2b commit 11b453f
Show file tree
Hide file tree
Showing 23 changed files with 457 additions and 45 deletions.
1 change: 1 addition & 0 deletions packages/frontend/apps/ios/App/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def capacitor_pods
pod 'CapacitorCordova', :path => '../../../../../node_modules/@capacitor/ios'
pod 'CapacitorApp', :path => '../../../../../node_modules/@capacitor/app'
pod 'CapacitorBrowser', :path => '../../../../../node_modules/@capacitor/browser'
pod 'CapacitorHaptics', :path => '../../../../../node_modules/@capacitor/haptics'
pod 'CapacitorKeyboard', :path => '../../../../../node_modules/@capacitor/keyboard'
end

Expand Down
28 changes: 17 additions & 11 deletions packages/frontend/apps/ios/App/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
PODS:
- Capacitor (6.1.2):
- Capacitor (6.2.0):
- CapacitorCordova
- CapacitorApp (6.0.1):
- CapacitorApp (6.0.2):
- Capacitor
- CapacitorBrowser (6.0.3):
- CapacitorBrowser (6.0.4):
- Capacitor
- CapacitorCordova (6.1.2)
- CapacitorKeyboard (6.0.2):
- CapacitorCordova (6.2.0)
- CapacitorHaptics (6.0.2):
- Capacitor
- CapacitorKeyboard (6.0.3):
- Capacitor
- CryptoSwift (1.8.3)

Expand All @@ -15,6 +17,7 @@ DEPENDENCIES:
- "CapacitorApp (from `../../../../../node_modules/@capacitor/app`)"
- "CapacitorBrowser (from `../../../../../node_modules/@capacitor/browser`)"
- "CapacitorCordova (from `../../../../../node_modules/@capacitor/ios`)"
- "CapacitorHaptics (from `../../../../../node_modules/@capacitor/haptics`)"
- "CapacitorKeyboard (from `../../../../../node_modules/@capacitor/keyboard`)"
- CryptoSwift (~> 1.8.3)

Expand All @@ -31,17 +34,20 @@ EXTERNAL SOURCES:
:path: "../../../../../node_modules/@capacitor/browser"
CapacitorCordova:
:path: "../../../../../node_modules/@capacitor/ios"
CapacitorHaptics:
:path: "../../../../../node_modules/@capacitor/haptics"
CapacitorKeyboard:
:path: "../../../../../node_modules/@capacitor/keyboard"

SPEC CHECKSUMS:
Capacitor: 679f9673fdf30597493a6362a5d5bf233d46abc2
CapacitorApp: 0bc633b4eae40a1f32cd2834788fad3bc42da6a1
CapacitorBrowser: aab1ed943b01c0365c4810538a8b3477e2d9f72e
CapacitorCordova: f48c89f96c319101cd2f0ce8a2b7449b5fb8b3dd
CapacitorKeyboard: 2700f9b18687be021e28b5a09b59eb151a46d5e0
Capacitor: 1f3c7b9802d958cd8c4eb63895fff85dff2e1eea
CapacitorApp: 2a8c3a0b0814322e5e6e15fe595f02c3808f0f8b
CapacitorBrowser: ef0529d16cd8839281050c350e7bbee4f5c6d65f
CapacitorCordova: b33e7f4aa4ed105dd43283acdd940964374a87d9
CapacitorHaptics: b53409aaca1203f79c6d0eb3ed5de40556339518
CapacitorKeyboard: 460c6f9ec5e52c84f2742d5ce2e67bbc7ab0ebb0
CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483

PODFILE CHECKSUM: 1b0d3fe81862c0e9ce712ddd0c5a0accd0097698
PODFILE CHECKSUM: e0c0ccf027ea6d51e476f0baf9d44d97b9a90a4b

COCOAPODS: 1.16.2
1 change: 1 addition & 0 deletions packages/frontend/apps/ios/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@capacitor/app": "^6.0.1",
"@capacitor/browser": "^6.0.3",
"@capacitor/core": "^6.1.2",
"@capacitor/haptics": "^6.0.2",
"@capacitor/ios": "^6.1.2",
"@capacitor/keyboard": "^6.0.2",
"@sentry/react": "^8.0.0",
Expand Down
10 changes: 10 additions & 0 deletions packages/frontend/apps/ios/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AffineContext } from '@affine/core/components/context';
import { AppFallback } from '@affine/core/mobile/components/app-fallback';
import { configureMobileModules } from '@affine/core/mobile/modules';
import { HapticProvider } from '@affine/core/mobile/modules/haptics';
import { NavigationGestureProvider } from '@affine/core/mobile/modules/navigation-gesture';
import { VirtualKeyboardProvider } from '@affine/core/mobile/modules/virtual-keyboard';
import { router } from '@affine/core/mobile/router';
Expand All @@ -22,6 +23,7 @@ import {
} from '@affine/core/modules/workspace-engine';
import { App as CapacitorApp } from '@capacitor/app';
import { Browser } from '@capacitor/browser';
import { Haptics } from '@capacitor/haptics';
import { Keyboard } from '@capacitor/keyboard';
import {
Framework,
Expand Down Expand Up @@ -94,6 +96,14 @@ framework.impl(NavigationGestureProvider, {
enable: () => NavigationGesture.enable(),
disable: () => NavigationGesture.disable(),
});
framework.impl(HapticProvider, {
impact: options => Haptics.impact(options as any),
vibrate: options => Haptics.vibrate(options as any),
notification: options => Haptics.notification(options as any),
selectionStart: () => Haptics.selectionStart(),
selectionChanged: () => Haptics.selectionChanged(),
selectionEnd: () => Haptics.selectionEnd(),
});
const frameworkProvider = framework.provider();

// setup application lifecycle events, and emit application start event
Expand Down
23 changes: 23 additions & 0 deletions packages/frontend/apps/mobile/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AffineContext } from '@affine/core/components/context';
import { AppFallback } from '@affine/core/mobile/components/app-fallback';
import { configureMobileModules } from '@affine/core/mobile/modules';
import { HapticProvider } from '@affine/core/mobile/modules/haptics';
import { router } from '@affine/core/mobile/router';
import { configureCommonModules } from '@affine/core/modules';
import { I18nProvider } from '@affine/core/modules/i18n';
Expand Down Expand Up @@ -52,6 +53,28 @@ framework.impl(PopupWindowProvider, {
window.open(url, '_blank', 'noreferrer noopener');
},
});
framework.impl(HapticProvider, {
impact: options => {
return new Promise(resolve => {
const style = options?.style ?? 'LIGHT';
const pattern = {
LIGHT: [10],
MEDIUM: [20],
HEAVY: [30],
}[style];
const result = navigator.vibrate?.(pattern);
if (!result) {
console.warn('vibrate not supported, or user not interacted');
}
resolve();
});
},
notification: () => Promise.reject('Not supported'),
vibrate: () => Promise.reject('Not supported'),
selectionStart: () => Promise.reject('Not supported'),
selectionChanged: () => Promise.reject('Not supported'),
selectionEnd: () => Promise.reject('Not supported'),
});
const frameworkProvider = framework.provider();

// setup application lifecycle events, and emit application start event
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/component/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-tooltip": "^1.0.7",
"@radix-ui/react-visually-hidden": "^1.1.0",
"@toeverything/theme": "^1.0.18",
"@toeverything/theme": "^1.0.21",
"@vanilla-extract/dynamic": "^2.1.0",
"check-password-strength": "^2.0.10",
"clsx": "^2.1.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"@radix-ui/react-toolbar": "^1.0.4",
"@sentry/react": "^8.0.0",
"@toeverything/pdf-viewer": "^0.1.1",
"@toeverything/theme": "^1.0.18",
"@toeverything/theme": "^1.0.21",
"@vanilla-extract/dynamic": "^2.1.0",
"animejs": "^3.2.2",
"bytes": "^3.1.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ export const useExplorerDocNodeOperationsMenu = (
),
},
{
index: 99,
index: 97,
view: (
<MenuItem
prefixIcon={<LinkedPageIcon />}
Expand All @@ -244,7 +244,7 @@ export const useExplorerDocNodeOperationsMenu = (
),
},
{
index: 99,
index: 98,
view: (
<MenuItem prefixIcon={<DuplicateIcon />} onClick={handleDuplicate}>
{t['com.affine.header.option.duplicate']()}
Expand Down
39 changes: 29 additions & 10 deletions packages/frontend/core/src/mobile/components/explorer/tree/node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { BaseExplorerTreeNodeProps } from '@affine/core/modules/explorer';
import { ExplorerTreeContext } from '@affine/core/modules/explorer';
import { WorkbenchLink } from '@affine/core/modules/workbench';
import { extractEmojiIcon } from '@affine/core/utils';
import { ArrowDownSmallIcon } from '@blocksuite/icons/rc';
import { ArrowDownSmallIcon, MoreHorizontalIcon } from '@blocksuite/icons/rc';
import * as Collapsible from '@radix-ui/react-collapsible';
import { assignInlineVars } from '@vanilla-extract/dynamic';
import {
Expand All @@ -15,6 +15,7 @@ import {
useState,
} from 'react';

import { SwipeMenu } from '../../swipe-menu';
import * as styles from './node.css';

interface ExplorerTreeNodeProps extends BaseExplorerTreeNodeProps {}
Expand Down Expand Up @@ -43,6 +44,7 @@ export const ExplorerTreeNode = ({
const clickForCollapse = !onClick && !to && !disabled;
const [childCount, setChildCount] = useState(0);
const rootRef = useRef<HTMLDivElement>(null);
const [menuOpen, setMenuOpen] = useState(false);

const { emoji, name } = useMemo(() => {
if (!extractEmojiAsIcon || !rawName) {
Expand Down Expand Up @@ -160,15 +162,32 @@ export const ExplorerTreeNode = ({
ref={rootRef}
{...otherProps}
>
<div className={styles.contentContainer} data-open={!collapsed}>
{to ? (
<LinkComponent to={to} className={styles.linkItemRoot}>
{content}
</LinkComponent>
) : (
<div>{content}</div>
)}
</div>
<SwipeMenu
onExecute={useCallback(() => setMenuOpen(true), [])}
menu={
<MobileMenu
rootOptions={useMemo(
() => ({ open: menuOpen, onOpenChange: setMenuOpen }),
[menuOpen]
)}
items={menuOperations.map(({ view, index }) => (
<Fragment key={index}>{view}</Fragment>
))}
>
<MoreHorizontalIcon fontSize={24} />
</MobileMenu>
}
>
<div className={styles.contentContainer} data-open={!collapsed}>
{to ? (
<LinkComponent to={to} className={styles.linkItemRoot}>
{content}
</LinkComponent>
) : (
<div>{content}</div>
)}
</div>
</SwipeMenu>
<Collapsible.Content>
{/* For lastInGroup check, the placeholder must be placed above all children in the dom */}
<div className={styles.collapseContentPlaceholder}>
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/core/src/mobile/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export * from './page-header';
export * from './rename';
export * from './search-input';
export * from './search-result';
export * from './swipe-menu';
export * from './user-plan-tag';
export * from './workspace-selector';
Loading

0 comments on commit 11b453f

Please sign in to comment.