Skip to content

Commit

Permalink
webapp: improve mobile UI
Browse files Browse the repository at this point in the history
  • Loading branch information
0xGingi committed Nov 15, 2024
1 parent 7e73f1e commit 08b78f7
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 148 deletions.
112 changes: 18 additions & 94 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import {
Box,
Tooltip,
Modal,
rem,
Burger,
Drawer,
Anchor,
Image,
} from '@mantine/core';
Expand All @@ -40,6 +38,7 @@ import { useAutoSync } from './hooks/useAutoSync';
import { SyncSettings as SyncSettingsType } from './types/sync';
import { WebStorageService } from './services/webStorage';
import './styles/richtext.css';
import { MobileNav } from './components/MobileNav';

interface Note {
id?: number;
Expand Down Expand Up @@ -236,99 +235,24 @@ async function deleteNote(noteId: number) {
</Group>
</AppShell.Header>
)}

{isMobile ? (
<Drawer
opened={mobileNavOpened}
onClose={() => setMobileNavOpened(false)}
size="100%"
padding="md"
title="Trusty Notes"
hiddenFrom="sm"
>
<Stack h="100%">
<Group justify="space-between">
<Button
leftSection={<IconPlus size={14} />}
variant="light"
onClick={() => {
clearForm();
setMobileNavOpened(false);
}}
fullWidth
>
New Note
</Button>
</Group>

<Group grow mb="md">
<ActionIcon variant="light" onClick={() => toggleColorScheme()} w="100%" h={rem(36)}>
{colorScheme === 'dark' ? <IconSun size={20} /> : <IconMoon size={20} />}
</ActionIcon>
<ActionIcon variant="light" onClick={() => setShowSyncSettings(true)} w="100%" h={rem(36)}>
<IconCloud size={20} />
</ActionIcon>
</Group>

<TextInput
placeholder="Search notes..."
leftSection={<IconSearch size={16} />}
value={searchQuery}
onChange={(e) => setSearchQuery(e.currentTarget.value)}
mb="md"
/>

<Stack gap="xs" style={{ overflow: 'auto', flex: 1 }}>
{filteredNotes.map((note) => (
<Paper
key={note.id}
shadow="xs"
p="md"
onClick={() => {
selectNote(note);
setMobileNavOpened(false);
}}
style={{
cursor: 'pointer',
backgroundColor: selectedNote?.id === note.id ?
'var(--mantine-color-blue-light)' : undefined,
}}
>
<Group justify="space-between" wrap="nowrap">
<Box style={{ flex: 1 }}>
<Text fw={500} truncate="end">
{note.title || 'Untitled'}
</Text>
<Text size="xs" c="dimmed">
{format(note.updated_at, 'MMM d, yyyy HH:mm')}
</Text>
</Box>
<ActionIcon
variant="subtle"
color="red"
onClick={(e) => {
e.stopPropagation();
deleteNote(note.id!);
}}
>
<IconTrash size={16} />
</ActionIcon>
</Group>
</Paper>
))}
</Stack>

<Group grow>
<Button variant="light" leftSection={<IconDownload size={14} />} onClick={exportNotes}>
Export
</Button>
<Button variant="light" leftSection={<IconUpload size={14} />} onClick={importNotes}>
Import
</Button>
</Group>
</Stack>
</Drawer>
) : (
<MobileNav
opened={mobileNavOpened}
onClose={() => setMobileNavOpened(false)}
onNewNote={clearForm}
onSearch={setSearchQuery}
searchQuery={searchQuery}
onToggleTheme={toggleColorScheme}
colorScheme={colorScheme}
onShowSyncSettings={() => setShowSyncSettings(true)}
onExport={exportNotes}
onImport={importNotes}
selectedNote={selectedNote}
notes={filteredNotes}
onSelectNote={selectNote}
onDeleteNote={deleteNote}
/>
) : (
<AppShell.Navbar p="md">
<Stack h="100%" gap="sm">
<Group justify="space-between">
Expand Down
179 changes: 125 additions & 54 deletions src/components/MobileNav.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import { Group, ActionIcon, Drawer, Stack, Button, TextInput } from '@mantine/core';
import { Group, ActionIcon, Drawer, Stack, Button, TextInput, Box, Text, Paper, Image, MantineColorScheme } from '@mantine/core';
import {
IconMenu2,
IconPlus,
IconSearch,
IconSun,
IconMoon,
IconCloud,
IconDownload,
IconUpload
IconUpload,
IconBrandGithub,
IconTrash
} from '@tabler/icons-react';
import { format } from 'date-fns';

interface Note {
id?: number;
title: string;
content: string;
created_at: number;
updated_at: number;
}

interface MobileNavProps {
opened: boolean;
Expand All @@ -17,10 +27,14 @@ interface MobileNavProps {
onSearch: (query: string) => void;
searchQuery: string;
onToggleTheme: () => void;
colorScheme: 'dark' | 'light';
colorScheme: MantineColorScheme;
onShowSyncSettings: () => void;
onExport: () => void;
onImport: () => void;
selectedNote: Note | null;
notes: Note[];
onSelectNote: (note: Note) => void;
onDeleteNote: (id: number) => void;
}

export function MobileNav({
Expand All @@ -33,83 +47,140 @@ export function MobileNav({
colorScheme,
onShowSyncSettings,
onExport,
onImport
onImport,
selectedNote,
notes,
onSelectNote,
onDeleteNote
}: MobileNavProps) {
const isDark = colorScheme === 'dark' || (colorScheme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);

return (
<>
<Group justify="space-between" p="md">
<ActionIcon variant="subtle" onClick={() => onClose()}>
<IconMenu2 size={24} />
</ActionIcon>
<Drawer
opened={opened}
onClose={onClose}
size="100%"
padding="md"
title={
<Group>
<ActionIcon variant="subtle" onClick={onNewNote}>
<IconPlus size={24} />
</ActionIcon>
<Image src="/trusty.jpg" alt="Logo" w={30} h={30} />
<Text size="lg" fw={500}>Trusty Notes</Text>
</Group>
</Group>
}
>
<Stack h="100%" gap="md">
<TextInput
placeholder="Search notes..."
leftSection={<IconSearch size={16} />}
value={searchQuery}
onChange={(e) => onSearch(e.currentTarget.value)}
/>

<Button
variant="light"
leftSection={<IconPlus size={16} />}
onClick={() => {
onNewNote();
onClose();
}}
fullWidth
>
New Note
</Button>

<Box style={{ flex: 1, overflowY: 'auto' }}>
<Stack gap="xs">
{notes.map((note) => (
<Paper
key={note.id}
shadow="xs"
p="md"
onClick={() => {
onSelectNote(note);
onClose();
}}
style={{
cursor: 'pointer',
backgroundColor: selectedNote?.id === note.id ?
'var(--mantine-color-blue-light)' : undefined,
}}
>
<Group justify="space-between" wrap="nowrap">
<Box style={{ flex: 1 }}>
<Text fw={500} truncate="end">
{note.title || 'Untitled'}
</Text>
<Text size="xs" c="dimmed">
{format(note.updated_at, 'MMM d, yyyy HH:mm')}
</Text>
</Box>
<ActionIcon
variant="subtle"
color="red"
onClick={(e) => {
e.stopPropagation();
onDeleteNote(note.id!);
}}
>
<IconTrash size={16} />
</ActionIcon>
</Group>
</Paper>
))}
</Stack>
</Box>

<Stack gap="xs">
<Button
variant="light"
leftSection={isDark ? <IconSun size={16} /> : <IconMoon size={16} />}
onClick={onToggleTheme}
fullWidth
>
{isDark ? 'Light Mode' : 'Dark Mode'}
</Button>

<Drawer opened={opened} onClose={onClose} title="Menu" position="left">
<Stack p="md">
<Button
leftSection={<IconPlus size={16} />}
variant="light"
leftSection={<IconCloud size={16} />}
onClick={() => {
onNewNote();
onShowSyncSettings();
onClose();
}}
fullWidth
>
New Note
Sync Settings
</Button>

<TextInput
placeholder="Search notes..."
leftSection={<IconSearch size={16} />}
value={searchQuery}
onChange={(e) => onSearch(e.currentTarget.value)}
/>

<Group grow>
<Button
variant="light"
leftSection={colorScheme === 'dark' ? <IconSun size={16} /> : <IconMoon size={16} />}
onClick={onToggleTheme}
>
{colorScheme === 'dark' ? 'Light Mode' : 'Dark Mode'}
</Button>
</Group>

<Group grow>
<Button
variant="light"
leftSection={<IconCloud size={16} />}
onClick={() => {
onShowSyncSettings();
onClose();
}}
>
Sync Settings
</Button>
</Group>

<Group grow>
<Button
variant="light"
leftSection={<IconDownload size={16} />}
onClick={onExport}
>
Export Notes
Export
</Button>
<Button
variant="light"
leftSection={<IconUpload size={16} />}
onClick={onImport}
>
Import Notes
Import
</Button>
</Group>

<Button
variant="subtle"
leftSection={<IconBrandGithub size={16} />}
component="a"
href="https://github.com/toolworks-dev/trusty-notes"
target="_blank"
fullWidth
>
GitHub
</Button>
</Stack>
</Drawer>
</>
</Stack>
</Drawer>
);
}
}

0 comments on commit 08b78f7

Please sign in to comment.