From: Muhammad Rizki <[email protected]>
To: Ammar Faizi <[email protected]>
Cc: Muhammad Rizki <[email protected]>,
Alviro Iskandar Setiawan <[email protected]>,
GNU/Weeb Mailing List <[email protected]>
Subject: [PATCH v2 10/12] chore(sidebar-menu): change sidebar menu look
Date: Sun, 9 Mar 2025 02:26:52 +0700 [thread overview]
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
This commit fixes the logout button in the sidebar menu which
overflowed to main content, with this fix, I change the sidebar footer
with account info and use dropdown menu to display the logout button and
account info.
Signed-off-by: Muhammad Rizki <[email protected]>
---
| 230 ++++++++++++------
1 file changed, 154 insertions(+), 76 deletions(-)
--git a/src/lib/components/customs/app-sidebar.svelte b/src/lib/components/customs/app-sidebar.svelte
index 7ccc59c..2132214 100644
--- a/src/lib/components/customs/app-sidebar.svelte
+++ b/src/lib/components/customs/app-sidebar.svelte
@@ -2,6 +2,9 @@
import { navigations } from "$constants";
import Separator from "$components/ui/separator/separator.svelte";
import * as Sidebar from "$lib/components/ui/sidebar";
+ import * as Avatar from "$lib/components/ui/avatar";
+ import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
+ import * as Dialog from "$lib/components/ui/dialog";
import { useAuth } from "$lib/hooks/auth.svelte";
import LogOut from "lucide-svelte/icons/log-out";
import Mails from "lucide-svelte/icons/mails";
@@ -14,17 +17,27 @@
import { crossfade } from "svelte/transition";
import { cubicInOut } from "svelte/easing";
import IconRoundcube from "$components/icons/icon-roundcube.svelte";
+ import Loading from "./loading.svelte";
+ import ChevronsUpDown from "lucide-svelte/icons/chevrons-up-down";
let { ref = $bindable(null), ...restProps }: ComponentProps<typeof Sidebar.Root> = $props();
const auth = useAuth();
const sidebar = Sidebar.useSidebar();
+ let showModalConfirmation = $state(false);
+
const [send, receive] = crossfade({
duration: 250,
easing: cubicInOut
});
+ const getShortName = () => {
+ const fullName = auth.user?.full_name ?? "";
+ const match = fullName.match(/\b(\w)/g) ?? [];
+ return match.slice(0, 2).join("").toUpperCase();
+ };
+
const handleNavigationMobile = () => {
if (!sidebar.isMobile) return;
sidebar.toggle();
@@ -36,93 +49,158 @@
};
</script>
-<Sidebar.Root bind:ref variant="inset" collapsible="icon" {...restProps}>
- <Sidebar.Content>
- <Sidebar.Group>
- <Sidebar.Header>
- <Sidebar.Menu>
- <Sidebar.MenuItem>
- <Sidebar.MenuButton onclick={() => sidebar.toggle()} size="lg">
- <div
- class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"
- >
- <Mails class="size-4" />
- </div>
- <div class="grid flex-1 text-left text-sm leading-tight">
- <span class="truncate font-semibold">{auth.user?.full_name}</span>
- <span class="truncate text-xs">@{auth.user?.username}</span>
- </div>
- </Sidebar.MenuButton>
- </Sidebar.MenuItem>
- </Sidebar.Menu>
- </Sidebar.Header>
- <Separator class="mb-3" />
- <Sidebar.GroupContent>
- <Sidebar.Menu>
- {#each navigations as item (item.name)}
+<Dialog.Root open={showModalConfirmation} onOpenChange={(e) => (showModalConfirmation = e)}>
+ <Sidebar.Root bind:ref variant="inset" collapsible="icon" {...restProps}>
+ <Sidebar.Content>
+ <Sidebar.Group>
+ <Sidebar.Header>
+ <Sidebar.Menu>
<Sidebar.MenuItem>
- {@const isActive = page.url.pathname.startsWith(item.url)}
- <Sidebar.MenuButton {isActive} class="relative">
+ <Sidebar.MenuButton onclick={() => sidebar.toggle()} size="lg">
+ <div
+ class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"
+ >
+ <Mails class="size-4" />
+ </div>
+ <h2 class="shrink-0 text-lg font-semibold">G/W Mail</h2>
+ </Sidebar.MenuButton>
+ </Sidebar.MenuItem>
+ </Sidebar.Menu>
+ </Sidebar.Header>
+ <Separator class="mb-3" />
+ <Sidebar.GroupContent>
+ <Sidebar.Menu>
+ {#each navigations as item (item.name)}
+ <Sidebar.MenuItem>
+ {@const isActive = page.url.pathname.startsWith(item.url)}
+ <Sidebar.MenuButton {isActive} class="relative">
+ {#snippet child({ props })}
+ {@const className = props.class as string}
+ <a
+ href={item.url}
+ onclick={handleNavigationMobile}
+ {...props}
+ class={cn("relative z-10", className)}
+ >
+ <item.icon />
+ <span>{item.name}</span>
+ </a>
+ {#if isActive}
+ <!-- svelte-ignore element_invalid_self_closing_tag -->
+ <div
+ class={cn(
+ "absolute inset-0 rounded-md bg-sidebar-accent",
+ isActive &&
+ "overflow-hidden before:absolute before:left-0 before:h-full before:w-0.5 before:bg-foreground"
+ )}
+ in:send={{ key: "active-sidebar-tab" }}
+ out:receive={{ key: "active-sidebar-tab" }}
+ />
+ {/if}
+ {/snippet}
+ </Sidebar.MenuButton>
+ </Sidebar.MenuItem>
+ {/each}
+ <Sidebar.MenuItem>
+ <Sidebar.MenuButton>
{#snippet child({ props })}
{@const className = props.class as string}
<a
- href={item.url}
- onclick={handleNavigationMobile}
+ href="https://mail.gnuweeb.org/roundcube/"
{...props}
- class={cn("relative z-10", className)}
+ class={cn("group/roundcube", className)}
+ target="_blank"
>
- <item.icon />
- <span>{item.name}</span>
- </a>
- {#if isActive}
- <!-- svelte-ignore element_invalid_self_closing_tag -->
- <div
- class={cn(
- "absolute inset-0 rounded-md bg-sidebar-accent",
- isActive &&
- "overflow-hidden before:absolute before:left-0 before:h-full before:w-0.5 before:bg-foreground"
- )}
- in:send={{ key: "active-sidebar-tab" }}
- out:receive={{ key: "active-sidebar-tab" }}
+ <IconRoundcube />
+ <span>Roundcube</span>
+ <SquareArrowOutUpRight
+ class="!size-3 transition-transform group-hover/roundcube:!scale-125"
/>
- {/if}
+ </a>
{/snippet}
</Sidebar.MenuButton>
</Sidebar.MenuItem>
- {/each}
- <Sidebar.MenuItem>
- <Sidebar.MenuButton>
+ </Sidebar.Menu>
+ </Sidebar.GroupContent>
+ </Sidebar.Group>
+ </Sidebar.Content>
+ <Sidebar.Footer>
+ <Sidebar.Menu>
+ <Sidebar.MenuItem>
+ <DropdownMenu.Root>
+ <DropdownMenu.Trigger>
{#snippet child({ props })}
- {@const className = props.class as string}
- <a
- href="https://mail.gnuweeb.org/roundcube/"
+ <Sidebar.MenuButton
+ size="lg"
+ class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
{...props}
- class={cn("group/roundcube", className)}
- target="_blank"
>
- <IconRoundcube />
- <span>Roundcube</span>
- <SquareArrowOutUpRight
- class="!size-3 transition-transform group-hover/roundcube:!scale-125"
- />
- </a>
+ <Avatar.Root class="h-8 w-8 rounded-lg">
+ <Avatar.Image src={auth.user?.photo} alt="{auth.user?.username}@gnuweeb.org" />
+ <Avatar.Fallback class="rounded-lg text-xs">
+ {#if !Boolean(auth.user?.photo)}
+ {getShortName()}
+ {:else}
+ <Loading class="size-3 text-black" />
+ {/if}
+ </Avatar.Fallback>
+ </Avatar.Root>
+ <div class="grid flex-1 text-left text-sm leading-tight">
+ <span class="truncate font-semibold">{auth.user?.full_name}</span>
+ <span class="truncate text-xs">{auth.user?.username}@gnuweeb.org</span>
+ </div>
+ <ChevronsUpDown class="ml-auto size-4" />
+ </Sidebar.MenuButton>
{/snippet}
- </Sidebar.MenuButton>
- </Sidebar.MenuItem>
- </Sidebar.Menu>
- </Sidebar.GroupContent>
- </Sidebar.Group>
- </Sidebar.Content>
- <Sidebar.Footer>
- <Sidebar.Menu>
- <Button
- variant="destructive"
- onclick={handleLogout}
- class="flex w-full items-center justify-start"
- >
- <LogOut />
- <span>Logout</span>
- </Button>
- </Sidebar.Menu>
- </Sidebar.Footer>
-</Sidebar.Root>
+ </DropdownMenu.Trigger>
+ <DropdownMenu.Content
+ class="w-[var(--bits-dropdown-menu-anchor-width)] min-w-56 rounded-lg"
+ side={sidebar.isMobile ? "bottom" : "right"}
+ align="end"
+ sideOffset={4}
+ >
+ <DropdownMenu.Label class="p-0 font-normal">
+ <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
+ <Avatar.Root class="h-8 w-8 rounded-lg">
+ <Avatar.Image src={auth.user?.photo} alt="{auth.user?.username}@gnuweeb.org" />
+ <Avatar.Fallback class="rounded-lg text-xs">
+ {#if !Boolean(auth.user?.photo)}
+ {getShortName()}
+ {:else}
+ <Loading class="size-3 text-black" />
+ {/if}
+ </Avatar.Fallback>
+ </Avatar.Root>
+ <div class="grid flex-1 text-left text-sm leading-tight">
+ <span class="truncate font-semibold">{auth.user?.full_name}</span>
+ <span class="truncate text-xs">{auth.user?.username}@gnuweeb.org</span>
+ </div>
+ </div>
+ </DropdownMenu.Label>
+ <DropdownMenu.Separator />
+ <DropdownMenu.Item
+ onclick={() => (showModalConfirmation = true)}
+ class="text-destructive hover:!text-destructive"
+ >
+ <LogOut />
+ Log out
+ </DropdownMenu.Item>
+ </DropdownMenu.Content>
+ </DropdownMenu.Root>
+ </Sidebar.MenuItem>
+ </Sidebar.Menu>
+ </Sidebar.Footer>
+ </Sidebar.Root>
+ <Dialog.Content class="sm:max-w-[425px]">
+ <Dialog.Header>
+ <Dialog.Title>Logout Confirmation</Dialog.Title>
+ <Dialog.Description>Confirm logout from your profile.</Dialog.Description>
+ </Dialog.Header>
+ <p class="text-sm font-medium">
+ You are about to logout from your profile, click logout button below to proceed.
+ </p>
+ <Dialog.Footer>
+ <Button type="submit" variant="destructive" onclick={handleLogout}>Logout</Button>
+ </Dialog.Footer>
+ </Dialog.Content>
+</Dialog.Root>
--
Muhammad Rizki
next prev parent reply other threads:[~2025-03-08 19:27 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-08 19:26 [PATCH v2 00/12] Fix Auth Guard, Move SEO Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 01/12] fix(svelte): use relative false Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 02/12] fix(avatar): change avatarImg state to use from auth.user.photo state Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 03/12] chore(profile): add toUpperCase() on getShortName() Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 04/12] fix(profile): make social fields default to empty string Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 05/12] chore(toaster): change toast message position and use richColors Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 06/12] chore(profile): reset password value on success Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 07/12] fix(profile-avatar): add delete avatar method Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 08/12] chore(profile): add space for password confirmation form Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 09/12] feat(ui): add dropdown-menu and update bits-ui version Muhammad Rizki
2025-03-08 19:26 ` Muhammad Rizki [this message]
2025-03-08 19:26 ` [PATCH v2 11/12] chore(seo): move seo from layout to /home page Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 12/12] fix(auth): fix auth guard when credentials is invalid Muhammad Rizki
2025-03-08 19:38 ` [PATCH v2 00/12] Fix Auth Guard, Move SEO Ammar Faizi
2025-03-08 19:51 ` Alviro Iskandar Setiawan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
[email protected] \
[email protected] \
[email protected] \
[email protected] \
[email protected] \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox