move notification indicator to menu button

shows mail icon with pulse animation when notifications exist
This commit is contained in:
Justin Edmund 2025-12-17 20:05:34 -08:00
parent 907b4503dd
commit 62dd3f5cd7
2 changed files with 99 additions and 36 deletions

View file

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M12 10C12 10.5523 11.5523 11 11 11H3C2.44772 11 2 10.5523 2 10V6.42915C2 6.23669 2.20835 6.11641 2.37502 6.21266L6.54395 8.62012C6.64258 8.67706 6.75256 8.69416 6.85742 8.68066C6.9449 8.6822 7.03392 8.66303 7.11523 8.61621L11.625 6.01241C11.7917 5.91619 12 6.03647 12 6.22892V10ZM11 3C11.5523 3 12 3.44772 12 4V4.35196C12 4.53059 11.9047 4.69565 11.75 4.78496L7.07621 7.48356C6.92149 7.5729 6.73086 7.57289 6.57615 7.48354L2.24995 4.98517C2.09528 4.89584 2 4.7308 2 4.55218V4C2 3.44772 2.44772 3 3 3H11Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 647 B

View file

@ -17,7 +17,6 @@
import UserSettingsModal from './UserSettingsModal.svelte'
import InvitationsModal from './crew/InvitationsModal.svelte'
import { authStore } from '$lib/stores/auth.store'
import { crewStore } from '$lib/stores/crew.store.svelte'
// Props from layout data
const {
@ -145,6 +144,15 @@
// Database back button hover state
let databaseBackHovered = $state(false)
// Query for the user's crew (to determine if phantom claims should be fetched)
const myCrewQuery = createQuery(() => ({
...crewQueries.myCrew(),
enabled: isAuth
}))
// Derived: whether the user is in a crew (from query, not store)
const isInCrew = $derived(myCrewQuery.data != null)
// Query for pending invitations (only when authenticated)
const pendingInvitationsQuery = createQuery(() => ({
...crewQueries.pendingInvitations(),
@ -154,7 +162,7 @@
// Query for pending phantom claims (only when authenticated and in a crew)
const pendingPhantomClaimsQuery = createQuery(() => ({
...crewQueries.pendingPhantomClaims(),
enabled: isAuth && crewStore.isInCrew
enabled: isAuth && isInCrew
}))
// Derived counts
@ -264,23 +272,16 @@
aria-label="Your account"
class="profile-link"
>
<span class="avatar-container">
{#if avatarSrc}
<img
src={avatarSrc}
srcset={avatarSrcSet}
alt={username}
class="user-avatar"
width="24"
height="24"
/>
{/if}
{#if totalNotificationCount > 0}
<span class="avatar-badge">
<NotificationBadge count={totalNotificationCount} />
</span>
{/if}
</span>
{#if avatarSrc}
<img
src={avatarSrc}
srcset={avatarSrcSet}
alt={username}
class="user-avatar"
width="24"
height="24"
/>
{/if}
<span>{username}</span>
</a>
</li>
@ -298,8 +299,14 @@
<li>
<DropdownMenu.Root>
<DropdownMenu.Trigger class="nav-more-trigger">
<Icon name="ellipsis" size={14} />
<DropdownMenu.Trigger
class="nav-more-trigger {totalNotificationCount > 0 ? `has-notification ${userElement ?? ''}` : ''}"
>
{#if totalNotificationCount > 0}
<Icon name="mail" size={18} />
{:else}
<Icon name="ellipsis" size={14} />
{/if}
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
@ -317,11 +324,13 @@
</DropdownItem>
<DropdownMenu.Separator class="dropdown-separator" />
{/if}
{#if isAuth && totalNotificationCount > 0}
{#if isAuth}
<DropdownItem>
<button class="dropdown-button-with-badge" onclick={() => (invitationsModalOpen = true)}>
<span>Notifications</span>
<NotificationBadge count={totalNotificationCount} showCount />
{#if totalNotificationCount > 0}
<NotificationBadge count={totalNotificationCount} showCount element={userElement} />
{/if}
</button>
</DropdownItem>
{/if}
@ -556,25 +565,12 @@
align-items: center;
gap: spacing.$unit-half;
.avatar-container {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.user-avatar {
width: 24px;
height: 24px;
border-radius: 50%;
object-fit: cover;
}
.avatar-badge {
position: absolute;
top: -2px;
right: -4px;
}
}
// Dropdown button with badge (for Invitations)
@ -630,6 +626,70 @@
}
}
// Notification pulse animation for the more trigger
@keyframes notification-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.6;
}
}
:global(.nav-more-trigger.has-notification) {
animation: notification-pulse 2s ease-in-out infinite;
// Default pulse color (no element selected)
background-color: var(--button-primary-bg);
color: white;
// Compensate for larger mail icon (18px vs 14px ellipsis)
padding: spacing.$unit calc(spacing.$unit + 3px);
&:hover {
animation: none;
background-color: var(--button-primary-bg-hover);
}
}
// Element-specific notification colors
:global(.nav-more-trigger.has-notification.wind) {
background-color: var(--wind-button-bg);
&:hover {
background-color: var(--wind-button-bg-hover);
}
}
:global(.nav-more-trigger.has-notification.fire) {
background-color: var(--fire-button-bg);
&:hover {
background-color: var(--fire-button-bg-hover);
}
}
:global(.nav-more-trigger.has-notification.water) {
background-color: var(--water-button-bg);
&:hover {
background-color: var(--water-button-bg-hover);
}
}
:global(.nav-more-trigger.has-notification.earth) {
background-color: var(--earth-button-bg);
&:hover {
background-color: var(--earth-button-bg-hover);
}
}
:global(.nav-more-trigger.has-notification.light) {
background-color: var(--light-button-bg);
color: black;
&:hover {
background-color: var(--light-button-bg-hover);
}
}
:global(.nav-more-trigger.has-notification.dark) {
background-color: var(--dark-button-bg);
&:hover {
background-color: var(--dark-button-bg-hover);
}
}
// Style the new team button as a prominent circular button
// Remove redundant styles that the Button component already handles
:global(.new-team-button) {