move notification indicator to menu button
shows mail icon with pulse animation when notifications exist
This commit is contained in:
parent
907b4503dd
commit
62dd3f5cd7
2 changed files with 99 additions and 36 deletions
3
src/assets/icons/mail.svg
Normal file
3
src/assets/icons/mail.svg
Normal 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 |
|
|
@ -17,7 +17,6 @@
|
||||||
import UserSettingsModal from './UserSettingsModal.svelte'
|
import UserSettingsModal from './UserSettingsModal.svelte'
|
||||||
import InvitationsModal from './crew/InvitationsModal.svelte'
|
import InvitationsModal from './crew/InvitationsModal.svelte'
|
||||||
import { authStore } from '$lib/stores/auth.store'
|
import { authStore } from '$lib/stores/auth.store'
|
||||||
import { crewStore } from '$lib/stores/crew.store.svelte'
|
|
||||||
|
|
||||||
// Props from layout data
|
// Props from layout data
|
||||||
const {
|
const {
|
||||||
|
|
@ -145,6 +144,15 @@
|
||||||
// Database back button hover state
|
// Database back button hover state
|
||||||
let databaseBackHovered = $state(false)
|
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)
|
// Query for pending invitations (only when authenticated)
|
||||||
const pendingInvitationsQuery = createQuery(() => ({
|
const pendingInvitationsQuery = createQuery(() => ({
|
||||||
...crewQueries.pendingInvitations(),
|
...crewQueries.pendingInvitations(),
|
||||||
|
|
@ -154,7 +162,7 @@
|
||||||
// Query for pending phantom claims (only when authenticated and in a crew)
|
// Query for pending phantom claims (only when authenticated and in a crew)
|
||||||
const pendingPhantomClaimsQuery = createQuery(() => ({
|
const pendingPhantomClaimsQuery = createQuery(() => ({
|
||||||
...crewQueries.pendingPhantomClaims(),
|
...crewQueries.pendingPhantomClaims(),
|
||||||
enabled: isAuth && crewStore.isInCrew
|
enabled: isAuth && isInCrew
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Derived counts
|
// Derived counts
|
||||||
|
|
@ -264,23 +272,16 @@
|
||||||
aria-label="Your account"
|
aria-label="Your account"
|
||||||
class="profile-link"
|
class="profile-link"
|
||||||
>
|
>
|
||||||
<span class="avatar-container">
|
{#if avatarSrc}
|
||||||
{#if avatarSrc}
|
<img
|
||||||
<img
|
src={avatarSrc}
|
||||||
src={avatarSrc}
|
srcset={avatarSrcSet}
|
||||||
srcset={avatarSrcSet}
|
alt={username}
|
||||||
alt={username}
|
class="user-avatar"
|
||||||
class="user-avatar"
|
width="24"
|
||||||
width="24"
|
height="24"
|
||||||
height="24"
|
/>
|
||||||
/>
|
{/if}
|
||||||
{/if}
|
|
||||||
{#if totalNotificationCount > 0}
|
|
||||||
<span class="avatar-badge">
|
|
||||||
<NotificationBadge count={totalNotificationCount} />
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
<span>{username}</span>
|
<span>{username}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -298,8 +299,14 @@
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
<DropdownMenu.Trigger class="nav-more-trigger">
|
<DropdownMenu.Trigger
|
||||||
<Icon name="ellipsis" size={14} />
|
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.Trigger>
|
||||||
|
|
||||||
<DropdownMenu.Portal>
|
<DropdownMenu.Portal>
|
||||||
|
|
@ -317,11 +324,13 @@
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownMenu.Separator class="dropdown-separator" />
|
<DropdownMenu.Separator class="dropdown-separator" />
|
||||||
{/if}
|
{/if}
|
||||||
{#if isAuth && totalNotificationCount > 0}
|
{#if isAuth}
|
||||||
<DropdownItem>
|
<DropdownItem>
|
||||||
<button class="dropdown-button-with-badge" onclick={() => (invitationsModalOpen = true)}>
|
<button class="dropdown-button-with-badge" onclick={() => (invitationsModalOpen = true)}>
|
||||||
<span>Notifications</span>
|
<span>Notifications</span>
|
||||||
<NotificationBadge count={totalNotificationCount} showCount />
|
{#if totalNotificationCount > 0}
|
||||||
|
<NotificationBadge count={totalNotificationCount} showCount element={userElement} />
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -556,25 +565,12 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: spacing.$unit-half;
|
gap: spacing.$unit-half;
|
||||||
|
|
||||||
.avatar-container {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-avatar {
|
.user-avatar {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-badge {
|
|
||||||
position: absolute;
|
|
||||||
top: -2px;
|
|
||||||
right: -4px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dropdown button with badge (for Invitations)
|
// 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
|
// Style the new team button as a prominent circular button
|
||||||
// Remove redundant styles that the Button component already handles
|
// Remove redundant styles that the Button component already handles
|
||||||
:global(.new-team-button) {
|
:global(.new-team-button) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue