|
11 | 11 | import LucideSunMedium from '~icons/lucide/sun-medium'; |
12 | 12 | import LucideLogOut from '~icons/lucide/log-out'; |
13 | 13 | import LucideChevronsRight from '~icons/lucide/chevrons-right'; |
| 14 | + import LucideMenu from '~icons/lucide/menu'; |
| 15 | + import LucideX from '~icons/lucide/x'; |
14 | 16 | import { onMount } from 'svelte'; |
15 | 17 | import UserTag from '$lib/components/UserTag.svelte'; |
16 | 18 |
|
17 | 19 | let currentTheme: 'light' | 'dark' = 'light'; |
18 | 20 | 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'; |
19 | 31 |
|
20 | 32 | function toggleCollapse() { |
21 | 33 | collapsed = !collapsed; |
|
46 | 58 | goto(resolve('/')); |
47 | 59 | }; |
48 | 60 |
|
| 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 | +
|
49 | 80 | onMount(() => { |
50 | 81 | const savedTheme = localStorage.getItem('theme'); |
51 | 82 | if (savedTheme === 'dark') { |
|
58 | 89 | if (savedCollapsed === '1') { |
59 | 90 | collapsed = true; |
60 | 91 | } |
| 92 | +
|
| 93 | + updateViewportMode(); |
| 94 | + window.addEventListener('resize', updateViewportMode); |
| 95 | +
|
| 96 | + return () => { |
| 97 | + window.removeEventListener('resize', updateViewportMode); |
| 98 | + }; |
61 | 99 | }); |
62 | 100 | </script> |
63 | 101 |
|
| 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 | + |
64 | 121 | <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};`} |
69 | 124 | > |
70 | 125 | <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" |
76 | 127 | > |
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> |
90 | 152 | </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'}" |
119 | 164 | > |
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> |
122 | 169 | <a |
123 | | - href={resolve('/admin')} |
| 170 | + href={resolve('/dashboard')} |
| 171 | + onclick={() => setTimeout(closeMobileSidebar, 100)} |
124 | 172 | 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' |
127 | 175 | ? 'bg-surface0/70 text-lavender' |
128 | 176 | : 'hover:bg-surface1/50'} {collapsed ? 'justify-center' : 'px-3'}" |
129 | 177 | > |
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 |
132 | 180 | > |
133 | 181 | </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} |
136 | 243 | <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" |
141 | 247 | > |
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" /> |
145 | 249 | </button> |
146 | 250 | {:else} |
147 | 251 | <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" |
152 | 257 | > |
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 | + /> |
156 | 262 | </button> |
157 | 263 | {/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> |
186 | 265 | </div> |
187 | 266 | </div> |
0 commit comments