Skip to content

Commit 6a50ca9

Browse files
committed
refactor: update mobile sheet and fix keyboard toolbar layout
1 parent 7dbc49c commit 6a50ca9

17 files changed

Lines changed: 351 additions & 168 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import React, { useEffect, useRef } from 'react'
2+
import { Sheet, SheetProps } from 'react-modal-sheet'
3+
4+
import { useSheetStore, useChatStore } from '@stores'
5+
import FilterModal from '@components/pages/document/components/FilterModal'
6+
import NotificationModal from './notificationPanel/mobile/NotificationModal'
7+
import ChatContainerMobile from './pages/document/components/chat/ChatContainerMobile'
8+
import SheetHeader from './SheetHeader'
9+
10+
const BottomSheet = () => {
11+
const { activeSheet, sheetData, closeSheet } = useSheetStore()
12+
const chatRoom = useChatStore((state) => state.chatRoom)
13+
const closeChatRoom = useChatStore((state) => state.closeChatRoom)
14+
const destroyChatRoom = useChatStore((state) => state.destroyChatRoom)
15+
const containerRef = useRef<HTMLDivElement>(null)
16+
17+
// Sync chat store with bottom sheet
18+
useEffect(() => {
19+
if (chatRoom.open && activeSheet !== 'chatroom') {
20+
// Chat is open in chat store but not in bottom sheet, close chat store
21+
closeChatRoom()
22+
destroyChatRoom()
23+
}
24+
}, [activeSheet, chatRoom.open, closeChatRoom])
25+
26+
const renderContent = () => {
27+
switch (activeSheet) {
28+
case 'chatroom':
29+
return <ChatContainerMobile />
30+
case 'notifications':
31+
return <NotificationModal />
32+
case 'filters':
33+
return <FilterModal />
34+
default:
35+
return null
36+
}
37+
}
38+
39+
const getSheetProps = () => {
40+
switch (activeSheet) {
41+
case 'filters':
42+
return {
43+
id: 'filter_sheet',
44+
detent: 'content-height' as SheetProps['detent']
45+
}
46+
case 'chatroom':
47+
return {
48+
id: 'chatroom_sheet',
49+
snapPoints: [1, 0.5, 0]
50+
}
51+
default:
52+
return {
53+
id: 'bottom_sheet'
54+
}
55+
}
56+
}
57+
58+
const handleClose = () => {
59+
if (activeSheet === 'chatroom') {
60+
closeChatRoom()
61+
destroyChatRoom()
62+
}
63+
closeSheet()
64+
}
65+
66+
if (!activeSheet) return null
67+
68+
return (
69+
<Sheet
70+
className="bottom-sheet"
71+
isOpen={!!activeSheet}
72+
onClose={handleClose}
73+
{...getSheetProps()}>
74+
<Sheet.Container ref={containerRef}>
75+
<Sheet.Header />
76+
<Sheet.Content>{renderContent()}</Sheet.Content>
77+
</Sheet.Container>
78+
<Sheet.Backdrop onTap={handleClose} />
79+
</Sheet>
80+
)
81+
}
82+
83+
export default BottomSheet
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React from 'react'
2+
import { IoClose } from 'react-icons/io5'
3+
import { useSheetStore } from '@stores'
4+
5+
interface SheetHeaderProps {
6+
title?: string
7+
onClose?: () => void
8+
className?: string
9+
children?: React.ReactNode
10+
}
11+
12+
interface SheetHeaderTitleProps {
13+
children: React.ReactNode
14+
className?: string
15+
}
16+
17+
interface SheetHeaderActionsProps {
18+
children: React.ReactNode
19+
className?: string
20+
}
21+
22+
const SheetHeaderTitle: React.FC<SheetHeaderTitleProps> = ({ children, className = '' }) => (
23+
<p className={`flex-1 text-lg font-semibold ${className}`}>{children}</p>
24+
)
25+
26+
const SheetHeaderActions: React.FC<SheetHeaderActionsProps> = ({ children, className = '' }) => (
27+
<div className={`flex flex-row items-center justify-end ${className}`}>{children}</div>
28+
)
29+
30+
const SheetHeader: React.FC<SheetHeaderProps> & {
31+
Title: typeof SheetHeaderTitle
32+
Actions: typeof SheetHeaderActions
33+
} = ({ title, onClose, className = '', children }) => {
34+
const { closeSheet } = useSheetStore()
35+
const handleClose = onClose || closeSheet
36+
37+
// Compound component usage
38+
if (children) {
39+
return (
40+
<div className={`mb-3 flex w-full bg-white pb-2 text-black ${className}`}>{children}</div>
41+
)
42+
}
43+
44+
return (
45+
<div className={`mb-3 flex w-full bg-white pb-2 text-black ${className}`}>
46+
<p className="flex-1 text-lg font-semibold">{title}</p>
47+
<div className="flex flex-row items-center justify-end">
48+
<button className="btn btn-square btn-ghost btn-sm ml-2 text-black" onClick={handleClose}>
49+
<IoClose size={20} />
50+
</button>
51+
</div>
52+
</div>
53+
)
54+
}
55+
56+
SheetHeader.Title = SheetHeaderTitle
57+
SheetHeader.Actions = SheetHeaderActions
58+
59+
export default SheetHeader

packages/webapp/components/TipTap/pad-title-section/MobilePadTitle.tsx

Lines changed: 9 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import ReadOnlyIndicator from './ReadOnlyIndicator'
33
import FilterBar from './FilterBar'
44
import { useStore } from '@stores'
55
import Modal from '@components/ui/Modal'
6-
import React, { useRef, useState } from 'react'
6+
import React, { useState } from 'react'
77
import { Avatar } from '@components/ui/Avatar'
88
import Button from '@components/ui/Button'
99
import { useAuthStore } from '@stores'
@@ -14,8 +14,7 @@ import SignInPanel from '@components/pages/panels/SignInPanel'
1414
import ToolbarButton from '@components/TipTap/toolbar/ToolbarButton'
1515
import { BiCheck, BiUndo, BiRedo } from 'react-icons/bi'
1616
import { useNotificationCount } from '@hooks/useNotificationCount'
17-
import NotificationModal from '@components/notificationPanel/mobile/NotificationModal'
18-
import { Sheet } from 'react-modal-sheet'
17+
import { useBottomSheet } from '@hooks/useBottomSheet'
1918
interface UserProfileButtonProps {
2019
user: any
2120
onProfileClick: () => void
@@ -26,11 +25,6 @@ interface UndoRedoButtonsProps {
2625
className?: string
2726
}
2827

29-
interface NotificationButtonProps {
30-
unreadCount: number
31-
setIsNotificationSheetOpen: (isOpen: boolean) => void
32-
}
33-
3428
const EditableToggle = ({ isEditable }: { isEditable: boolean }) => {
3529
if (isEditable) {
3630
return (
@@ -77,15 +71,16 @@ const UserProfileButton = ({ user, onProfileClick }: UserProfileButtonProps) =>
7771
)
7872
}
7973

80-
const NotificationButton = ({
81-
unreadCount,
82-
setIsNotificationSheetOpen
83-
}: NotificationButtonProps) => {
74+
const NotificationButton = () => {
75+
const { openNotifications } = useBottomSheet()
76+
const { workspaceId } = useStore((state) => state.settings)
77+
const unreadCount = useNotificationCount({ workspaceId })
78+
8479
return (
8580
<Button
8681
className="btn-ghost tooltip tooltip-bottom relative p-2"
8782
data-tip="Notifications"
88-
onClick={() => setIsNotificationSheetOpen(true)}>
83+
onClick={openNotifications}>
8984
<MdInsertComment size={26} className="text-docsy" />
9085
{unreadCount > 0 && (
9186
<div className="absolute top-[2px] right-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-xs text-white">
@@ -113,16 +108,11 @@ const UndoRedoButtons = ({ editor, className }: UndoRedoButtonsProps) => {
113108
}
114109

115110
const MobilePadTitle = () => {
116-
const { workspaceId } = useStore((state) => state.settings)
117-
const unreadCount = useNotificationCount({ workspaceId })
118111
const user = useAuthStore((state) => state.profile)
119112
const {
120113
editor: { isEditable, instance: editor }
121114
} = useStore((state) => state.settings)
122115
const [isProfileModalOpen, setProfileModalOpen] = useState(false)
123-
const notificationModalRef = useRef<any>(null!)
124-
const [isNotificationSheetOpen, setIsNotificationSheetOpen] = useState(false)
125-
const [isFilterModalOpen, setIsFilterModalOpen] = useState(false)
126116

127117
return (
128118
<>
@@ -143,10 +133,7 @@ const MobilePadTitle = () => {
143133

144134
<div className="flex w-[20%] items-center justify-end gap-2">
145135
<ReadOnlyIndicator />
146-
<NotificationButton
147-
unreadCount={unreadCount}
148-
setIsNotificationSheetOpen={setIsNotificationSheetOpen}
149-
/>
136+
<NotificationButton />
150137
<UserProfileButton user={user} onProfileClick={() => setProfileModalOpen(true)} />
151138
</div>
152139
</div>
@@ -156,20 +143,6 @@ const MobilePadTitle = () => {
156143
</div>
157144
</div>
158145

159-
<Sheet
160-
id="notification_sheet"
161-
modalEffectRootId="notification_sheet"
162-
isOpen={isNotificationSheetOpen}
163-
onClose={() => setIsNotificationSheetOpen(false)}>
164-
<Sheet.Container>
165-
<Sheet.Header />
166-
<Sheet.Content>
167-
<NotificationModal />
168-
</Sheet.Content>
169-
</Sheet.Container>
170-
<Sheet.Backdrop />
171-
</Sheet>
172-
173146
<Modal
174147
asAChild={false}
175148
id="modal_profile"

packages/webapp/components/chat/components/ToolbarMobile.tsx

Lines changed: 0 additions & 38 deletions
This file was deleted.

packages/webapp/components/pages/document/components/FilterModal.tsx

Lines changed: 9 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,15 @@
11
import React, { useState, useEffect, useRef, RefObject } from 'react'
22
import { useRouter } from 'next/router'
3-
import { IoClose } from 'react-icons/io5'
4-
const FilterModalHeader = ({
5-
setIsFilterModalOpen
6-
}: {
7-
setIsFilterModalOpen: (isOpen: boolean) => void
8-
}) => {
9-
return (
10-
<>
11-
<div className="flex w-full items-center justify-center">
12-
<span className="drag-indicator h-1 w-14 rounded-full bg-gray-300"></span>
13-
</div>
14-
<div className="mb-3 flex w-full bg-white py-2 text-black">
15-
<p className="w-2/3 text-lg font-semibold">Filters</p>
16-
<div className="flex w-1/3 flex-row items-center justify-end justify-items-end">
17-
<label
18-
htmlFor="filterModalBottom"
19-
className="btn btn-square btn-ghost btn-sm ml-2 text-black"
20-
onClick={() => setIsFilterModalOpen(false)}>
21-
<IoClose size={20} />
22-
</label>
23-
</div>
24-
</div>
25-
</>
26-
)
27-
}
3+
import { useSheetStore } from '@stores'
4+
import SheetHeader from '@components/SheetHeader'
285

29-
const FilterModal = ({
30-
setIsFilterModalOpen
31-
}: {
32-
setIsFilterModalOpen: (isOpen: boolean) => void
33-
}) => {
6+
const FilterModal = () => {
347
const filterSearchRef = useRef<HTMLInputElement>(null)
358
const [totalHeading, setTotalHeading] = useState(0)
369
const [totalSearch, setTotalSearch] = useState(0)
3710
const [search, setSearch] = useState('')
3811
const router = useRouter()
12+
const { isSheetOpen } = useSheetStore((state) => state)
3913

4014
const countHeadings = () => {
4115
const headings = document.querySelectorAll('.title')
@@ -73,14 +47,13 @@ const FilterModal = ({
7347
if (search) window.location.href = `/${mainDoc}/${encodeURIComponent(search)}`
7448
}
7549

76-
const handleModalStateChange = (isOpen: boolean) => {
77-
if (!isOpen) setSearch('')
78-
else countHeadings()
79-
}
50+
useEffect(() => {
51+
if (isSheetOpen('filters')) countHeadings()
52+
}, [isSheetOpen])
8053

8154
return (
82-
<div className="h-full max-h-96 w-full rounded-t-lg bg-white p-4">
83-
<FilterModalHeader setIsFilterModalOpen={setIsFilterModalOpen} />
55+
<div className="h-full max-h-96 w-full rounded-t-lg bg-white p-4 pt-0">
56+
<SheetHeader title="Filters" />
8457
<div className="join w-full">
8558
<input
8659
id="filterModalBottom"

packages/webapp/components/pages/document/components/MobileEditor.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { useRef } from 'react'
2-
import useDetectKeyboardOpen from 'use-detect-keyboard-open'
1+
import { useEffect, useRef, useState } from 'react'
32
import EditorContent from './EditorContent'
43
import ToolbarMobile from './toolbarMobile/ToolbarMobile'
54
import { useChatStore } from '@stores'
@@ -8,13 +7,15 @@ import useEditableDocControl from '@components/pages/document/hooks/useEditableD
87
import usePageHeightAdjust from '@components/pages/document/hooks/usePageHeightAdjust'
98
import useUpdateDocPageUnreadMsg from '@components/pages/document/hooks/useUpdateDocPageUnreadMsg'
109
import { MobileBubbleMenu } from './MobileBubbleMenu'
10+
import { animate, useMotionValue } from 'motion/react'
11+
import useKeyboardHeight from '@hooks/useKeyboardHeight'
1112

1213
const Editor = () => {
1314
const editorWrapperRef = useRef<HTMLDivElement>(null)
1415

1516
const chatRoom = useChatStore((state) => state.chatRoom)
1617

17-
const isKeyboardOpen = useDetectKeyboardOpen() || false
18+
const { isOpen: isKeyboardOpen, height: keyboardHeight, viewportHeight } = useKeyboardHeight()
1819

1920
// @ts-ignore
2021
useAdjustEditorSizeForChatRoom(editorWrapperRef)
@@ -27,17 +28,17 @@ const Editor = () => {
2728

2829
return (
2930
<>
30-
<div className="editor relative flex size-full w-full max-w-full flex-col justify-around align-top">
31+
<div className="editor editorHeighttt relative flex size-full w-full max-w-full flex-col justify-around align-top">
3132
<div
3233
ref={editorWrapperRef}
33-
className="editorWrapper flex h-full grow items-start justify-center overflow-hidden overflow-y-auto p-0">
34+
className="editorWrapper flex h-full w-full justify-center overflow-hidden overflow-y-auto p-0">
3435
<MobileBubbleMenu />
3536
<EditorContent />
3637
</div>
3738
</div>
3839

3940
<div
40-
className={`toolbars bg-base-100 sticky inset-x-0 bottom-0 z-10 w-full ${
41+
className={`toolbars bg-base-100 z-10 w-full ${
4142
isKeyboardOpen && !chatRoom?.headingId ? 'block' : 'hidden'
4243
}`}>
4344
<ToolbarMobile />

0 commit comments

Comments
 (0)