Skip to content

Commit cd7701f

Browse files
committed
fix: replace mobile tabs with native sidebar drawer navigation and enhance user experience
1 parent 6fead79 commit cd7701f

File tree

1 file changed

+154
-66
lines changed

1 file changed

+154
-66
lines changed

src/components/layout/MobileLayout.tsx

Lines changed: 154 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import { useState } from 'react';
22
import { UserButton, useUser } from '@clerk/clerk-react';
3-
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
3+
import { Menu, X } from 'lucide-react';
44
import { ChangeMasterPasswordDialog } from '@/components/password/ChangeMasterPasswordDialog';
5+
import { Button } from '@/components/ui/button';
6+
import { ThemeToggle } from '@/components/ui/theme-toggle';
57
import { hasMasterPassword } from '@/lib/encryption';
8+
import { APP_VERSION } from '@/constants/version';
69
import FolderPanel from '@/components/folders';
710
import NotesPanel from '@/components/notes/NotesPanel';
811
import Index from '@/components/editor';
912
import type { Note } from '@/types/note';
1013
import type { FolderPanelProps, FilesPanelProps, EditorProps } from '@/types/layout';
1114

12-
type MobileTab = 'folders' | 'notes' | 'editor';
15+
type MobileView = 'folders' | 'notes' | 'editor';
1316

1417
interface MobileLayoutProps {
1518
selectedNote: Note | null;
@@ -25,96 +28,169 @@ export function MobileLayout({
2528
editorProps,
2629
}: MobileLayoutProps) {
2730
const { user } = useUser();
28-
const [activeTab, setActiveTab] = useState<MobileTab>(
31+
const [currentView, setCurrentView] = useState<MobileView>(
2932
selectedNote ? 'editor' : 'folders'
3033
);
34+
const [showSidebar, setShowSidebar] = useState(false);
3135
const [showChangePassword, setShowChangePassword] = useState(false);
3236

3337
const hasPassword = user?.id ? hasMasterPassword(user.id) : false;
3438

3539
// Auto-switch to editor when note is selected
3640
const handleNoteSelect = (note: Note) => {
3741
filesPanelProps.onSelectNote(note);
38-
setActiveTab('editor');
42+
setCurrentView('editor');
43+
setShowSidebar(false);
44+
};
45+
46+
const handleViewChange = (view: MobileView) => {
47+
setCurrentView(view);
48+
setShowSidebar(false);
3949
};
4050

4151
const modifiedFilesPanelProps = {
4252
...filesPanelProps,
4353
onSelectNote: handleNoteSelect,
4454
};
55+
const getViewTitle = () => {
56+
switch (currentView) {
57+
case 'folders': return 'Folders';
58+
case 'notes': return 'Notes';
59+
case 'editor': return selectedNote?.title || 'Editor';
60+
default: return 'Typelets';
61+
}
62+
};
63+
4564
return (
4665
<>
47-
<Tabs
48-
value={activeTab}
49-
onValueChange={(value) => {
50-
if (value === 'editor' && !selectedNote) return;
51-
setActiveTab(value as MobileTab);
52-
}}
53-
className="flex h-screen flex-col"
54-
>
55-
{/* Header - only show on editor tab */}
56-
{activeTab === 'editor' && selectedNote && (
57-
<div className="border-border bg-background flex shrink-0 items-center justify-center border-b p-3">
58-
<div className="text-foreground truncate text-sm font-medium">
59-
{selectedNote.title || 'Untitled Note'}
66+
<div className="flex h-screen flex-col bg-background">
67+
{/* Header */}
68+
<div className="border-b bg-background p-3">
69+
<div className="flex items-center">
70+
<Button
71+
variant="ghost"
72+
size="sm"
73+
onClick={() => setShowSidebar(true)}
74+
className="h-9 w-9 p-0 mr-3"
75+
>
76+
<Menu className="h-5 w-5" />
77+
</Button>
78+
79+
<h1 className="text-foreground text-lg font-semibold flex-1 truncate">
80+
{getViewTitle()}
81+
</h1>
6082
</div>
6183
</div>
62-
)}
6384

64-
{/* Tab Content */}
65-
<div className="flex-1 overflow-hidden">
66-
<TabsContent value="folders" className="h-full m-0 p-0">
67-
<FolderPanel isOpen={true} {...folderPanelProps} />
68-
</TabsContent>
69-
70-
<TabsContent value="notes" className="h-full m-0 p-0">
71-
<NotesPanel isOpen={true} {...modifiedFilesPanelProps} />
72-
</TabsContent>
73-
74-
<TabsContent value="editor" className="h-full m-0 p-0">
75-
{selectedNote ? (
76-
<Index {...editorProps} />
77-
) : (
78-
<div className="flex h-full items-center justify-center text-center p-4">
79-
<div>
80-
<p className="text-muted-foreground">Select a note to start editing</p>
85+
{/* Content */}
86+
<div className="flex-1 overflow-hidden">
87+
{currentView === 'folders' && (
88+
<FolderPanel isOpen={true} {...folderPanelProps} />
89+
)}
90+
91+
{currentView === 'notes' && (
92+
<NotesPanel isOpen={true} {...modifiedFilesPanelProps} />
93+
)}
94+
95+
{currentView === 'editor' && (
96+
selectedNote ? (
97+
<Index {...editorProps} />
98+
) : (
99+
<div className="flex h-full items-center justify-center text-center p-4">
100+
<div>
101+
<p className="text-muted-foreground">Select a note to start editing</p>
102+
</div>
81103
</div>
82-
</div>
104+
)
83105
)}
84-
</TabsContent>
106+
</div>
85107
</div>
86108

87-
{/* Bottom Tab Bar */}
88-
<div className="border-t bg-background p-2">
89-
<div className="flex items-center">
90-
<TabsList className="flex-1">
91-
<TabsTrigger value="folders" className="flex-1">
92-
Folders
93-
</TabsTrigger>
94-
<TabsTrigger value="notes" className="flex-1">
95-
Notes
96-
</TabsTrigger>
97-
<TabsTrigger
98-
value="editor"
99-
className="flex-1"
100-
disabled={!selectedNote}
109+
{/* Sidebar Overlay */}
110+
{showSidebar && (
111+
<div
112+
className="fixed inset-0 z-50 bg-black/50"
113+
onClick={() => setShowSidebar(false)}
114+
/>
115+
)}
116+
117+
{/* Sidebar Drawer */}
118+
<div
119+
className={`fixed top-0 left-0 z-50 h-full w-80 bg-background border-r shadow-lg transition-transform duration-300 ease-in-out ${
120+
showSidebar ? 'translate-x-0' : '-translate-x-full'
121+
}`}
122+
>
123+
{/* Sidebar Header */}
124+
<div className="border-b p-4">
125+
<div className="flex items-center justify-between">
126+
<div>
127+
<h2 className="text-lg font-semibold">Typelets</h2>
128+
<div className="text-muted-foreground text-xs opacity-80">
129+
v{APP_VERSION}
130+
</div>
131+
</div>
132+
<Button
133+
variant="ghost"
134+
size="sm"
135+
onClick={() => setShowSidebar(false)}
136+
className="h-8 w-8 p-0"
101137
>
102-
Editor
103-
</TabsTrigger>
104-
</TabsList>
138+
<X className="h-4 w-4" />
139+
</Button>
140+
</div>
141+
</div>
142+
143+
{/* Navigation Items */}
144+
<div className="p-4">
145+
<div
146+
className={`w-full text-left p-4 rounded-lg cursor-pointer transition-colors ${
147+
currentView === 'folders'
148+
? 'bg-primary text-primary-foreground'
149+
: 'hover:bg-accent hover:text-accent-foreground'
150+
}`}
151+
onClick={() => handleViewChange('folders')}
152+
>
153+
<div className="font-medium">Folders</div>
154+
</div>
105155

106-
<div className="mx-3 h-9 w-px bg-border" />
156+
<div
157+
className={`w-full text-left p-4 rounded-lg cursor-pointer transition-colors ${
158+
currentView === 'notes'
159+
? 'bg-primary text-primary-foreground'
160+
: 'hover:bg-accent hover:text-accent-foreground'
161+
}`}
162+
onClick={() => handleViewChange('notes')}
163+
>
164+
<div className="font-medium">Notes</div>
165+
</div>
107166

108-
<div className="flex items-center">
109-
<UserButton
110-
appearance={{
111-
elements: {
112-
avatarBox: 'w-8 h-8',
113-
userButtonPopoverCard: 'w-64',
114-
},
115-
}}
116-
afterSignOutUrl="/"
117-
>
167+
<div
168+
className={`w-full text-left p-4 rounded-lg cursor-pointer transition-colors ${
169+
!selectedNote
170+
? 'opacity-50 cursor-not-allowed'
171+
: currentView === 'editor'
172+
? 'bg-primary text-primary-foreground'
173+
: 'hover:bg-accent hover:text-accent-foreground'
174+
}`}
175+
onClick={() => selectedNote && handleViewChange('editor')}
176+
>
177+
<div className="font-medium">Editor</div>
178+
</div>
179+
</div>
180+
181+
{/* User Section */}
182+
<div className="absolute bottom-0 left-0 right-0 border-t p-4">
183+
<div className="flex items-center justify-between mb-4">
184+
<div className="flex items-center gap-3">
185+
<UserButton
186+
appearance={{
187+
elements: {
188+
avatarBox: 'w-10 h-10',
189+
userButtonPopoverCard: 'w-64',
190+
},
191+
}}
192+
afterSignOutUrl="/"
193+
>
118194
<UserButton.MenuItems>
119195
<UserButton.Action
120196
label="Typelets Open Source"
@@ -187,10 +263,22 @@ export function MobileLayout({
187263
)}
188264
</UserButton.MenuItems>
189265
</UserButton>
266+
<div className="min-w-0 flex-1">
267+
<div className="text-foreground truncate text-sm font-medium">
268+
{user?.fullName ??
269+
user?.firstName ??
270+
user?.emailAddresses[0]?.emailAddress}
271+
</div>
272+
<div className="text-muted-foreground text-xs">
273+
Tap avatar for settings
274+
</div>
275+
</div>
276+
</div>
277+
278+
<ThemeToggle />
190279
</div>
191280
</div>
192281
</div>
193-
</Tabs>
194282

195283
<ChangeMasterPasswordDialog
196284
open={showChangePassword}

0 commit comments

Comments
 (0)