diff --git a/Frontend/src/components/shell/TopBar/ProfileMenu.module.scss b/Frontend/src/components/shell/TopBar/ProfileMenu.module.scss
new file mode 100644
index 0000000..b12fd01
--- /dev/null
+++ b/Frontend/src/components/shell/TopBar/ProfileMenu.module.scss
@@ -0,0 +1,165 @@
+.menu {
+ position: absolute;
+ top: calc(100% + var(--space-2));
+ right: 0;
+ width: min(21rem, calc(100vw - (var(--space-4) * 2)));
+ display: grid;
+ gap: var(--space-3);
+ padding: var(--space-3);
+ border: 1px solid var(--color-border-strong);
+ border-radius: calc(var(--radius-lg) + 0.1rem);
+ background: color-mix(in srgb, var(--color-surface-muted) 96%, transparent);
+ box-shadow: 0 18px 36px color-mix(in srgb, black 16%, transparent);
+ backdrop-filter: blur(18px);
+ z-index: 30;
+}
+
+.summary {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr);
+ gap: var(--space-3);
+ align-items: center;
+ padding-bottom: var(--space-3);
+ border-bottom: 1px solid var(--color-border);
+}
+
+.avatar {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 3rem;
+ height: 3rem;
+ align-self: center;
+ margin-right: var(--space-1);
+}
+
+.avatarRing {
+ position: absolute;
+ inset: 0;
+ border-radius: 999px;
+ background:
+ conic-gradient(
+ from 0deg,
+ transparent 0deg 24deg,
+ var(--color-primary-1) 24deg 118deg,
+ transparent 118deg 144deg,
+ var(--color-primary-2) 144deg 238deg,
+ transparent 238deg 264deg,
+ var(--color-primary-3) 264deg 356deg,
+ transparent 356deg 360deg
+ );
+ mask: radial-gradient(circle, transparent 64%, black 67%);
+ -webkit-mask: radial-gradient(circle, transparent 64%, black 67%);
+}
+
+.avatarCore {
+ @include text-label;
+ position: relative;
+ z-index: 1;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 78%;
+ height: 78%;
+ border-radius: 999px;
+ background: var(--color-surface);
+ color: var(--color-text);
+ font-weight: var(--font-weight-semibold);
+}
+
+.summaryCopy {
+ min-width: 0;
+ display: grid;
+ gap: 0.08rem;
+}
+
+.name,
+.itemLabel {
+ @include text-label;
+}
+
+.name {
+ color: var(--color-text);
+}
+
+.email,
+.role,
+.context {
+ @include text-caption;
+ color: var(--color-text-muted);
+}
+
+.context {
+ margin-top: var(--space-1);
+ color: var(--color-text-subtle);
+}
+
+.section {
+ display: grid;
+ gap: 0.2rem;
+}
+
+.section + .section {
+ padding-top: var(--space-3);
+ border-top: 1px solid var(--color-border);
+}
+
+.item {
+ width: 100%;
+ min-width: 0;
+ min-height: 2.65rem;
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr);
+ align-items: center;
+ gap: var(--space-2);
+ padding: var(--space-2);
+ border: 1px solid transparent;
+ border-radius: var(--radius-sm);
+ background: transparent;
+ color: var(--color-text);
+ text-align: left;
+ transition:
+ background-color 160ms var(--easing-standard),
+ border-color 160ms var(--easing-standard),
+ color 160ms var(--easing-standard);
+}
+
+.item:hover {
+ background: color-mix(in srgb, var(--color-surface) 88%, transparent);
+ border-color: color-mix(in srgb, var(--color-border) 20%, transparent);
+}
+
+.item:focus-visible {
+ outline: none;
+ background: color-mix(in srgb, var(--color-surface) 92%, transparent);
+ border-color: color-mix(in srgb, var(--color-accent-soft) 52%, transparent);
+ box-shadow: 0 0 0 0.12rem color-mix(in srgb, var(--color-accent-soft) 14%, transparent);
+}
+
+.itemDanger {
+ color: color-mix(in srgb, var(--color-primary-3) 74%, var(--color-text) 26%);
+}
+
+.itemDanger:hover,
+.itemDanger:focus-visible {
+ background: color-mix(in srgb, var(--color-primary-3) 8%, transparent);
+ border-color: color-mix(in srgb, var(--color-primary-3) 16%, transparent);
+}
+
+.itemIcon {
+ width: 1.9rem;
+ height: 1.9rem;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: var(--radius-sm);
+ background: color-mix(in srgb, var(--color-surface) 92%, transparent);
+ color: currentColor;
+}
+
+@include respond-down(mobile) {
+ .menu {
+ width: min(20rem, calc(100vw - (var(--space-3) * 2)));
+ }
+}
diff --git a/Frontend/src/components/shell/TopBar/ProfileMenu.tsx b/Frontend/src/components/shell/TopBar/ProfileMenu.tsx
new file mode 100644
index 0000000..5d462a1
--- /dev/null
+++ b/Frontend/src/components/shell/TopBar/ProfileMenu.tsx
@@ -0,0 +1,61 @@
+import { For, type JSX } from "solid-js";
+import { User } from "../../../lib/icons";
+import { activeUserProfile, profileMenuSections } from "../data/shell.data";
+import styles from "./ProfileMenu.module.scss";
+
+type ProfileMenuProps = {
+ id: string;
+ menuRef: (element: HTMLDivElement) => void;
+ onSelect: () => void;
+};
+
+export const ProfileMenu = (props: ProfileMenuProps): JSX.Element => {
+ return (
+
+ );
+};
diff --git a/Frontend/src/components/shell/TopBar/TopBar.tsx b/Frontend/src/components/shell/TopBar/TopBar.tsx
index 4bb49a2..7b6bd13 100644
--- a/Frontend/src/components/shell/TopBar/TopBar.tsx
+++ b/Frontend/src/components/shell/TopBar/TopBar.tsx
@@ -5,7 +5,7 @@ import type { Theme } from "../../../theme/runtime";
import { topBarActions } from "../data/shell.data";
import { DepartmentSelector } from "../DepartmentSelector/DepartmentSelector";
import { ThemeToggle } from "./ThemeToggle";
-import { UserNavButton } from "./UserNavButton";
+import { UserNav } from "./UserNav";
import styles from "./TopBar.module.scss";
type TopBarProps = {
@@ -37,7 +37,7 @@ export const TopBar = (props: TopBarProps): JSX.Element => {
-
+
);
diff --git a/Frontend/src/components/shell/TopBar/UserNav.module.scss b/Frontend/src/components/shell/TopBar/UserNav.module.scss
new file mode 100644
index 0000000..bbfe5ed
--- /dev/null
+++ b/Frontend/src/components/shell/TopBar/UserNav.module.scss
@@ -0,0 +1,5 @@
+.root {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+}
diff --git a/Frontend/src/components/shell/TopBar/UserNav.tsx b/Frontend/src/components/shell/TopBar/UserNav.tsx
new file mode 100644
index 0000000..13bf17e
--- /dev/null
+++ b/Frontend/src/components/shell/TopBar/UserNav.tsx
@@ -0,0 +1,54 @@
+import { createEffect, createSignal, createUniqueId, onCleanup, type JSX } from "solid-js";
+import { ProfileMenu } from "./ProfileMenu";
+import { UserNavButton } from "./UserNavButton";
+import styles from "./UserNav.module.scss";
+
+export const UserNav = (): JSX.Element => {
+ const [isOpen, setIsOpen] = createSignal(false);
+ const menuId = createUniqueId();
+ let rootRef: HTMLDivElement | undefined;
+ let menuRef: HTMLDivElement | undefined;
+
+ const closeMenu = (): void => {
+ setIsOpen(false);
+ };
+
+ const toggleMenu = (): void => {
+ setIsOpen((open) => !open);
+ };
+
+ createEffect(() => {
+ if (!isOpen()) return;
+
+ const handlePointerDown = (event: PointerEvent): void => {
+ if (!rootRef) return;
+
+ const target = event.target;
+ if (target instanceof Node && !rootRef.contains(target)) {
+ closeMenu();
+ }
+ };
+
+ const handleKeyDown = (event: KeyboardEvent): void => {
+ if (event.key === "Escape") {
+ closeMenu();
+ }
+ };
+
+ document.addEventListener("pointerdown", handlePointerDown);
+ document.addEventListener("keydown", handleKeyDown);
+ menuRef?.querySelector("[role='menuitem']")?.focus();
+
+ onCleanup(() => {
+ document.removeEventListener("pointerdown", handlePointerDown);
+ document.removeEventListener("keydown", handleKeyDown);
+ });
+ });
+
+ return (
+
+
+ {isOpen() ?
+ );
+};
diff --git a/Frontend/src/components/shell/TopBar/UserNavButton.module.scss b/Frontend/src/components/shell/TopBar/UserNavButton.module.scss
index d33f387..64107d4 100644
--- a/Frontend/src/components/shell/TopBar/UserNavButton.module.scss
+++ b/Frontend/src/components/shell/TopBar/UserNavButton.module.scss
@@ -23,7 +23,12 @@
color: var(--color-text);
}
-.userButton:hover .spinContainer {
+.userButtonOpen {
+ color: var(--color-text);
+}
+
+.userButton:hover .spinContainer,
+.userButtonOpen .spinContainer {
animation-play-state: running;
opacity: 1;
}
diff --git a/Frontend/src/components/shell/TopBar/UserNavButton.tsx b/Frontend/src/components/shell/TopBar/UserNavButton.tsx
index 06b3955..6d28932 100644
--- a/Frontend/src/components/shell/TopBar/UserNavButton.tsx
+++ b/Frontend/src/components/shell/TopBar/UserNavButton.tsx
@@ -4,9 +4,27 @@ import type { JSX } from "solid-js";
import { User } from "../../../lib/icons";
import styles from "./UserNavButton.module.scss";
-export const UserNavButton = (): JSX.Element => {
+type UserNavButtonProps = {
+ isOpen: boolean;
+ menuId: string;
+ onToggle: () => void;
+};
+
+export const UserNavButton = (props: UserNavButtonProps): JSX.Element => {
return (
-