feat: notification filter controls pill buttons (#1718)

## Summary
- replace the notification type dropdown with inline pill buttons for
quick filtering
- expose accessible role and pressed state on the new filter buttons

## Testing
- pnpm --filter @unraid/web lint

------
https://chatgpt.com/codex/tasks/task_e_68d184ad60348323b60c9b8e19146025

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Notifications sidebar now uses a pill-style button group instead of a
dropdown for filtering by importance/type.
  * One-tap switching applies filters instantly for faster navigation.
* Active filters are more visible, improving clarity and accessibility.
* No changes to existing workflows or public behavior; settings and
filtering semantics remain unchanged.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Eli Bosley
2025-09-26 10:15:27 -04:00
committed by GitHub
parent b7afaf4632
commit 661865f976

View File

@@ -4,7 +4,6 @@ import { useMutation, useQuery, useSubscription } from '@vue/apollo-composable';
import {
Button,
Select,
Sheet,
SheetContent,
SheetHeader,
@@ -46,9 +45,8 @@ const { mutate: recalculateOverview } = useMutation(resetOverview);
const { confirm } = useConfirm();
const importance = ref<Importance | undefined>(undefined);
const filterItems = [
{ type: 'label' as const, label: 'Notification Types' },
{ label: 'All Types', value: 'all' },
const filterOptions: Array<{ label: string; value?: Importance }> = [
{ label: 'All Types' },
{ label: 'Alert', value: Importance.ALERT },
{ label: 'Info', value: Importance.INFO },
{ label: 'Warning', value: Importance.WARNING },
@@ -99,8 +97,6 @@ onNotificationAdded(({ data }) => {
if (notif.timestamp) {
latestNotificationTimestamp.value = notif.timestamp;
}
// probably smart to leave this log outside the if-block for the initial release
console.log('incoming notification', notif);
if (!globalThis.toast) {
return;
}
@@ -203,32 +199,46 @@ const prepareToViewNotifications = () => {
</TabsContent>
</div>
<div class="mt-2 flex items-center justify-between gap-2 px-3">
<Select
:items="filterItems"
placeholder="Filter By"
class="h-8 px-3 text-sm"
@update:model-value="
(val: unknown) => {
const strVal = String(val);
importance = strVal === 'all' || !strVal ? undefined : (strVal as Importance);
}
"
/>
<TooltipProvider>
<Tooltip :delay-duration="0">
<TooltipTrigger as-child>
<a href="/Settings/Notifications">
<Button variant="ghost" size="sm" class="h-8 w-8 p-0">
<Settings class="h-4 w-4" />
</Button>
</a>
</TooltipTrigger>
<TooltipContent>
<p>Edit Notification Settings</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<div class="mt-3 flex items-start justify-between gap-3 px-3">
<div class="flex min-w-0 flex-1 flex-col gap-2">
<div
class="border-border/60 bg-muted/60 flex flex-wrap items-center gap-1 rounded-xl border p-1"
role="group"
>
<Button
v-for="option in filterOptions"
:key="option.label"
variant="ghost"
size="sm"
class="h-8 rounded-lg border border-transparent px-3 text-xs font-medium transition-colors"
:class="
importance === option.value
? 'border-border bg-background text-foreground'
: 'text-muted-foreground hover:border-border/60 hover:bg-muted/40 hover:text-foreground'
"
:aria-pressed="importance === option.value"
@click="importance = option.value"
>
{{ option.label }}
</Button>
</div>
</div>
<div class="shrink-0">
<TooltipProvider>
<Tooltip :delay-duration="0">
<TooltipTrigger as-child>
<a href="/Settings/Notifications">
<Button variant="ghost" size="sm" class="h-8 w-8 p-0">
<Settings class="h-4 w-4" />
</Button>
</a>
</TooltipTrigger>
<TooltipContent>
<p>Edit Notification Settings</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
<TabsContent value="unread" class="min-h-0 flex-1 flex-col">