Skip to content

Commit a5b72eb

Browse files
committed
add actual mobile support
1 parent 03e1e73 commit a5b72eb

File tree

5 files changed

+207
-120
lines changed

5 files changed

+207
-120
lines changed

frontend/src/lib/components/SideBar.svelte

Lines changed: 180 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,23 @@
1111
import LucideSunMedium from '~icons/lucide/sun-medium';
1212
import LucideLogOut from '~icons/lucide/log-out';
1313
import LucideChevronsRight from '~icons/lucide/chevrons-right';
14+
import LucideMenu from '~icons/lucide/menu';
15+
import LucideX from '~icons/lucide/x';
1416
import { onMount } from 'svelte';
1517
import UserTag from '$lib/components/UserTag.svelte';
1618
1719
let currentTheme: 'light' | 'dark' = 'light';
1820
let collapsed: boolean = false;
21+
let buttonMode: boolean = false;
22+
let showMobileSidebar: boolean = false;
23+
24+
$: sidebarPositionClass = buttonMode
25+
? showMobileSidebar
26+
? 'fixed inset-y-0 left-0 top-0 z-50 h-[100dvh] max-h-[100dvh] overflow-y-auto shadow-2xl'
27+
: 'hidden'
28+
: 'block relative h-full';
29+
30+
$: sidebarWidth = buttonMode ? 'min(20rem, 100vw)' : collapsed ? '5rem' : '16rem';
1931
2032
function toggleCollapse() {
2133
collapsed = !collapsed;
@@ -46,6 +58,25 @@
4658
goto(resolve('/'));
4759
};
4860
61+
const closeMobileSidebar = () => {
62+
showMobileSidebar = false;
63+
};
64+
65+
const openMobileSidebar = () => {
66+
showMobileSidebar = true;
67+
};
68+
69+
const updateViewportMode = () => {
70+
const shouldUseButtonMode = window.innerWidth < 768;
71+
buttonMode = shouldUseButtonMode;
72+
73+
if (shouldUseButtonMode) {
74+
collapsed = false;
75+
} else {
76+
showMobileSidebar = false;
77+
}
78+
};
79+
4980
onMount(() => {
5081
const savedTheme = localStorage.getItem('theme');
5182
if (savedTheme === 'dark') {
@@ -58,130 +89,178 @@
5889
if (savedCollapsed === '1') {
5990
collapsed = true;
6091
}
92+
93+
updateViewportMode();
94+
window.addEventListener('resize', updateViewportMode);
95+
96+
return () => {
97+
window.removeEventListener('resize', updateViewportMode);
98+
};
6199
});
62100
</script>
63101

102+
{#if buttonMode && !showMobileSidebar}
103+
<button
104+
class="cursor-pointer md:hidden fixed top-4 left-4 z-50 flex items-center gap-2 bg-base text-text border border-surface0 rounded-md px-2 py-2 shadow-lg hover:bg-surface0/60 transition-colors"
105+
onclick={openMobileSidebar}
106+
aria-label="Open sidebar"
107+
>
108+
<LucideMenu class="w-7 h-7" />
109+
</button>
110+
{/if}
111+
112+
{#if buttonMode && showMobileSidebar}
113+
<button
114+
type="button"
115+
class="fixed inset-0 bg-surface0/60 backdrop-blur-sm z-40 md:hidden"
116+
onclick={closeMobileSidebar}
117+
aria-label="Close sidebar overlay"
118+
></button>
119+
{/if}
120+
64121
<div
65-
class="block relative bg-base text-text h-full p-4 border-r border-surface0 transition-all duration-300 md:static md:top-auto md:left-auto md:z-auto {collapsed
66-
? ''
67-
: 'fixed left-0 top-0 z-40'}"
68-
style="width: {collapsed ? '5rem' : '16rem'};"
122+
class="{sidebarPositionClass} md:static md:top-auto md:left-auto md:z-auto"
123+
style={`width: ${sidebarWidth};`}
69124
>
70125
<div
71-
class="flex items-center gap-4 transition-all duration-300 {$auth.user
72-
? collapsed
73-
? 'justify-center mb-3'
74-
: 'justify-start mb-6'
75-
: ''}"
126+
class="bg-base text-text h-full p-4 border-r border-surface0 transition-all duration-300 relative flex flex-col justify-start"
76127
>
77-
{#if $auth.user}
78-
{#if $auth.user.avatar_url}
79-
<img
80-
src={$auth.user.avatar_url}
81-
alt="Profile"
82-
class="{collapsed
83-
? 'h-8 w-8'
84-
: 'h-16 w-16'} transition-all duration-300 rounded-full border-2 border-ctp-green-500"
85-
/>
86-
{/if}
87-
<div class={collapsed ? 'hidden' : ''}>
88-
<div class="flex flex-row items-center gap-1 align-middle">
89-
<UserTag is_admin={$auth.user.is_admin} />
128+
<div
129+
class="flex items-center gap-4 transition-all duration-300 {$auth.user
130+
? collapsed
131+
? 'justify-center mb-3'
132+
: 'justify-start mb-6'
133+
: ''}"
134+
>
135+
{#if $auth.user}
136+
{#if $auth.user.avatar_url}
137+
<img
138+
src={$auth.user.avatar_url}
139+
alt="Profile"
140+
class="{collapsed
141+
? 'h-8 w-8'
142+
: 'h-16 w-16'} transition-all duration-300 rounded-full border-2 border-ctp-green-500"
143+
/>
144+
{/if}
145+
<div class={collapsed ? 'hidden' : ''}>
146+
<div class="flex flex-row items-center gap-1 align-middle">
147+
<UserTag is_admin={$auth.user.is_admin} />
148+
</div>
149+
<h2 class="{getNameSizeClass($auth.user.name)} text-subtext1 font-bold">
150+
{$auth.user.name || 'User'}
151+
</h2>
90152
</div>
91-
<h2 class="{getNameSizeClass($auth.user.name)} text-subtext1 font-bold">
92-
{$auth.user.name || 'User'}
93-
</h2>
94-
</div>
95-
{/if}
96-
</div>
97-
<div class="flex flex-col justify-between transition-all duration-300">
98-
<nav class="space-y-2 flex flex-col transition-all duration-300">
99-
<a
100-
href={resolve('/')}
101-
data-sveltekit-preload-data="hover"
102-
class="py-2 rounded-md items-center inline-flex {page.url.pathname === '/'
103-
? 'bg-surface0/70 text-lavender'
104-
: 'hover:bg-surface1/50'} {collapsed ? 'justify-center' : 'px-3'}"
105-
>
106-
<LucideHouse class="w-6 h-6 inline" /><span class={collapsed ? 'hidden' : 'ml-2'}>Home</span
107-
>
108-
</a>
109-
<a
110-
href={resolve('/dashboard')}
111-
data-sveltekit-preload-data="hover"
112-
class="w-full text-left cursor-pointer py-2 rounded-md items-center inline-flex {page.url
113-
.pathname === '/dashboard'
114-
? 'bg-surface0/70 text-lavender'
115-
: 'hover:bg-surface1/50'} {collapsed ? 'justify-center' : 'px-3'}"
116-
>
117-
<LucideLayoutDashboard class="w-6 h-6 inline" /><span class={collapsed ? 'hidden' : 'ml-2'}
118-
>Dashboard</span
153+
{/if}
154+
</div>
155+
<div class="flex flex-col justify-between transition-all duration-300">
156+
<nav class="space-y-2 flex flex-col transition-all duration-300">
157+
<a
158+
href={resolve('/')}
159+
onclick={() => setTimeout(closeMobileSidebar, 100)}
160+
data-sveltekit-preload-data="hover"
161+
class="py-2 rounded-md items-center inline-flex {page.url.pathname === '/'
162+
? 'bg-surface0/70 text-lavender'
163+
: 'hover:bg-surface1/50'} {collapsed ? 'justify-center' : 'px-3'}"
119164
>
120-
</a>
121-
{#if $auth.user?.is_admin}
165+
<LucideHouse class="w-6 h-6 inline" /><span class={collapsed ? 'hidden' : 'ml-2'}
166+
>Home</span
167+
>
168+
</a>
122169
<a
123-
href={resolve('/admin')}
170+
href={resolve('/dashboard')}
171+
onclick={() => setTimeout(closeMobileSidebar, 100)}
124172
data-sveltekit-preload-data="hover"
125-
class="w-full text-left py-2 cursor-pointer rounded-md items-center outline-dashed bg-yellow/5 outline-1 outline-yellow inline-flex {page
126-
.url.pathname === '/admin'
173+
class="w-full text-left cursor-pointer py-2 rounded-md items-center inline-flex {page.url
174+
.pathname === '/dashboard'
127175
? 'bg-surface0/70 text-lavender'
128176
: 'hover:bg-surface1/50'} {collapsed ? 'justify-center' : 'px-3'}"
129177
>
130-
<LucideWrench class="w-6 h-6 inline" /><span class={collapsed ? 'hidden' : 'ml-2'}
131-
>Admin</span
178+
<LucideLayoutDashboard class="w-6 h-6 inline" /><span
179+
class={collapsed ? 'hidden' : 'ml-2'}>Dashboard</span
132180
>
133181
</a>
134-
{/if}
135-
{#if $auth.isAuthenticated && $auth.user}
182+
{#if $auth.user?.is_admin}
183+
<a
184+
href={resolve('/admin')}
185+
onclick={() => setTimeout(closeMobileSidebar, 100)}
186+
data-sveltekit-preload-data="hover"
187+
class="w-full text-left py-2 cursor-pointer rounded-md items-center outline-dashed bg-yellow/5 outline-1 outline-yellow inline-flex {page
188+
.url.pathname === '/admin'
189+
? 'bg-surface0/70 text-lavender'
190+
: 'hover:bg-surface1/50'} {collapsed ? 'justify-center' : 'px-3'}"
191+
>
192+
<LucideWrench class="w-6 h-6 inline" /><span class={collapsed ? 'hidden' : 'ml-2'}
193+
>Admin</span
194+
>
195+
</a>
196+
{/if}
197+
{#if $auth.isAuthenticated && $auth.user}
198+
<button
199+
onclick={() => {
200+
handleLogout();
201+
setTimeout(closeMobileSidebar, 100);
202+
}}
203+
class="py-2 rounded-md cursor-pointer items-center inline-flex hover:bg-surface1/50 {collapsed
204+
? 'justify-center'
205+
: 'px-3'}"
206+
>
207+
<LucideLogOut class="w-6 h-6 inline" /><span class={collapsed ? 'hidden' : 'ml-2'}
208+
>Logout</span
209+
>
210+
</button>
211+
{:else}
212+
<button
213+
onclick={() => {
214+
auth.login();
215+
setTimeout(closeMobileSidebar, 100);
216+
}}
217+
class="py-2 rounded-md cursor-pointer items-center bg-ctp-mauve/50 outline outline-mauve inline-flex hover:bg-ctp-mauve/65 {collapsed
218+
? 'justify-center'
219+
: 'px-3'}"
220+
>
221+
<LucideLogIn class="w-6 h-6 inline" /><span class={collapsed ? 'hidden' : 'ml-2'}
222+
>Login</span
223+
>
224+
</button>
225+
{/if}
226+
</nav>
227+
<button
228+
class="absolute {collapsed
229+
? 'bottom-18'
230+
: 'bottom-6'} left-6 cursor-pointer rounded-md items-center inline-flex hover:text-blue"
231+
onclick={switchTheme}
232+
aria-label="Toggle theme"
233+
>
234+
{#if currentTheme === 'light'}
235+
<LucideMoon class="w-8 h-8 text-subtext0" />
236+
{:else}
237+
<LucideSunMedium class="w-8 h-8 text-subtext0" />
238+
{/if}
239+
<span class={collapsed ? 'hidden' : ''}></span>
240+
</button>
241+
242+
{#if buttonMode}
136243
<button
137-
onclick={handleLogout}
138-
class="py-2 rounded-md cursor-pointer items-center inline-flex hover:bg-surface1/50 {collapsed
139-
? 'justify-center'
140-
: 'px-3'}"
244+
class="absolute top-4 right-4 cursor-pointer rounded-md items-center inline-flex hover:text-blue"
245+
onclick={closeMobileSidebar}
246+
aria-label="Close sidebar"
141247
>
142-
<LucideLogOut class="w-6 h-6 inline" /><span class={collapsed ? 'hidden' : 'ml-2'}
143-
>Logout</span
144-
>
248+
<LucideX class="w-8 h-8 text-subtext0" />
145249
</button>
146250
{:else}
147251
<button
148-
onclick={auth.login}
149-
class="py-2 rounded-md cursor-pointer items-center bg-ctp-mauve/50 outline outline-mauve inline-flex hover:bg-ctp-mauve/65 {collapsed
150-
? 'justify-center'
151-
: 'px-3'}"
252+
class="absolute bottom-6 {collapsed
253+
? 'right-6'
254+
: 'right-2'} right-6 cursor-pointer rounded-md items-center inline-flex hover:text-blue"
255+
onclick={toggleCollapse}
256+
aria-label="Toggle sidebar collapse"
152257
>
153-
<LucideLogIn class="w-6 h-6 inline" /><span class={collapsed ? 'hidden' : 'ml-2'}
154-
>Login</span
155-
>
258+
<LucideChevronsRight
259+
class="w-8 h-8 text-subtext0 transition-transform duration-400"
260+
style="transform: rotate({collapsed ? '0deg' : '-180deg'})"
261+
/>
156262
</button>
157263
{/if}
158-
</nav>
159-
<button
160-
class="absolute {collapsed
161-
? 'bottom-18'
162-
: 'bottom-6'} left-6 cursor-pointer rounded-md items-center inline-flex hover:text-blue"
163-
onclick={switchTheme}
164-
aria-label="Toggle theme"
165-
>
166-
{#if currentTheme === 'light'}
167-
<LucideMoon class="w-8 h-8 text-subtext0" />
168-
{:else}
169-
<LucideSunMedium class="w-8 h-8 text-subtext0" />
170-
{/if}
171-
<span class={collapsed ? 'hidden' : ''}></span>
172-
</button>
173-
174-
<button
175-
class="absolute bottom-6 {collapsed
176-
? 'right-6'
177-
: 'right-2'} right-6 cursor-pointer rounded-md items-center inline-flex hover:text-blue"
178-
onclick={toggleCollapse}
179-
aria-label="Toggle sidebar collapse"
180-
>
181-
<LucideChevronsRight
182-
class="w-8 h-8 text-subtext0 transition-transform duration-400"
183-
style="transform: rotate({collapsed ? '0deg' : '-180deg'})"
184-
/>
185-
</button>
264+
</div>
186265
</div>
187266
</div>

frontend/src/routes/+layout.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
<AuthErrorWarning />
7171
</div>
7272

73-
<div class="side-bar relative w-20 md:w-auto flex-shrink-0 h-full">
73+
<div class="side-bar relative w-0 md:w-auto md:flex-shrink-0 h-full">
7474
<SideBar />
7575
</div>
7676

frontend/src/routes/+page.svelte

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
<div class="flex text-text items-center justify-center gap-3 mb-4">
8585
<h1 class="text-5xl font-bold">rustytime</h1>
8686
</div>
87-
<p class="text-xl text-subtext0">Blazingly fast time tracking for developers.</p>
87+
<p class="text-xl text-subtext0 px-1">Blazingly fast time tracking for developers.</p>
8888
</header>
8989

9090
<!-- Main Content -->
@@ -100,7 +100,7 @@
100100
class="w-16 h-16 rounded-full border-2 border-ctp-green-500"
101101
/>
102102
{/if}
103-
<div>
103+
<div class="self-start text-left break-words w-[min-content] max-w-full">
104104
<h2 class="text-2xl text-subtext1 font-bold">
105105
Welcome, {authState.user.name || 'User'}!
106106
</h2>
@@ -111,26 +111,28 @@
111111
</div>
112112
</div>
113113

114-
<div class="space-y-4">
115-
<a
116-
href={resolve('/dashboard')}
117-
class="inline-block bg-ctp-mauve-400 hover:bg-ctp-mauve-500 text-ctp-base font-semibold py-3 px-6 rounded-lg"
118-
>
119-
Go to Dashboard
120-
</a>
121-
122-
{#if authState.user.is_admin}
114+
<div class="flex flex-col items-center space-y-4">
115+
<div class="flex flex-col items-center space-y-4 sm:flex-row sm:space-y-0 sm:space-x-4">
123116
<a
124-
href={resolve('/admin')}
125-
class="inline-block bg-ctp-red-400 hover:bg-ctp-red-500 text-ctp-base font-semibold py-3 px-6 rounded-lg ml-4"
117+
href={resolve('/dashboard')}
118+
class="inline-block bg-ctp-mauve-400 hover:bg-ctp-mauve-500 text-ctp-base font-semibold py-3 px-6 rounded-lg"
126119
>
127-
Admin Panel
120+
Go to Dashboard
128121
</a>
129-
{/if}
122+
123+
{#if authState.user.is_admin}
124+
<a
125+
href={resolve('/admin')}
126+
class="inline-block bg-ctp-red-400 hover:bg-ctp-red-500 text-ctp-base font-semibold py-3 px-6 rounded-lg"
127+
>
128+
Admin Panel
129+
</a>
130+
{/if}
131+
</div>
130132

131133
<button
132134
onclick={auth.logout}
133-
class="cursor-pointer block mx-auto text-ctp-text/70 hover:text-ctp-text/50 underline"
135+
class="cursor-pointer text-ctp-text/70 hover:text-ctp-text/50 underline"
134136
>
135137
Logout
136138
</button>

0 commit comments

Comments
 (0)