Pair the chat header plus button with a popover overflow menu (#427)

This commit is contained in:
Coracle Bot 2026-09-07 20:39:58 +00:00 committed by hodlbod
parent 6da908edd0
commit cfb5dd53b1
4 changed files with 44 additions and 42 deletions

View file

@ -474,7 +474,7 @@ test("US-034 track and clear unread conversations", async ({seed, as}) => {
await expect(unreadDots(bobChat)).toHaveCount(0) await expect(unreadDots(bobChat)).toHaveCount(0)
// Mark all read clears what is left of them in one go // Mark all read clears what is left of them in one go
await alice.locator(".secondary-nav__header").getByRole("button").click() await alice.locator(".secondary-nav__header").getByRole("button", {name: "Chat options"}).click()
await alice.getByRole("button", {name: "Mark all read"}).click() await alice.getByRole("button", {name: "Mark all read"}).click()
await expect(unreadDots(chatList(alice))).toHaveCount(0) await expect(unreadDots(chatList(alice))).toHaveCount(0)

View file

@ -1,43 +1,48 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte"
import {assoc} from "@welshman/lib" import {assoc} from "@welshman/lib"
import Check from "@assets/icons/check.svg?dataurl" import Check from "@assets/icons/check.svg?dataurl"
import Bell from "@assets/icons/bell.svg?dataurl" import Bell from "@assets/icons/bell.svg?dataurl"
import BellOff from "@assets/icons/bell-off.svg?dataurl" import BellOff from "@assets/icons/bell-off.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import {setChecked} from "@app/notifications" import {setChecked} from "@app/notifications"
import {notificationSettings} from "@app/settings" import {notificationSettings} from "@app/settings"
const markAsRead = () => { type Props = {
setChecked("/chat/*") onClick: () => void
history.back()
} }
const enableAlerts = () => notificationSettings.update(assoc("messages", true)) const {onClick}: Props = $props()
const disableAlerts = () => notificationSettings.update(assoc("messages", false)) const markAsRead = () => setChecked("/chat/*")
const toggleAlerts = () =>
notificationSettings.update(assoc("messages", !$notificationSettings.messages))
let ul: Element
onMount(() => {
ul.addEventListener("click", onClick)
})
</script> </script>
<Modal> <ul class="menu whitespace-nowrap rounded-2xl bg-surface p-2" bind:this={ul}>
<ModalBody> <li>
<div class="flex flex-col gap-2"> <Button onclick={markAsRead}>
<Button class="button button-neutral" onclick={markAsRead}> <Icon size={4} icon={Check} />
<Icon size={5} icon={Check} />
Mark all read Mark all read
</Button> </Button>
</li>
<li>
<Button onclick={toggleAlerts}>
{#if $notificationSettings.messages} {#if $notificationSettings.messages}
<Button class="button button-neutral" onclick={disableAlerts}>
<Icon size={4} icon={BellOff} /> <Icon size={4} icon={BellOff} />
Disable alerts Disable alerts
</Button>
{:else} {:else}
<Button class="button button-neutral" onclick={enableAlerts}>
<Icon size={4} icon={Bell} /> <Icon size={4} icon={Bell} />
Enable alerts Enable alerts
</Button>
{/if} {/if}
</div> </Button>
</ModalBody> </li>
</Modal> </ul>

View file

@ -3,12 +3,13 @@
import {onMount} from "svelte" import {onMount} from "svelte"
import {page} from "$app/stores" import {page} from "$app/stores"
import {sleep} from "@welshman/lib" import {sleep} from "@welshman/lib"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl" import Add from "@assets/icons/add.svg?dataurl"
import ChatSquarePlus from "@assets/icons/chat-square-plus.svg?dataurl" import ChatSquarePlus from "@assets/icons/chat-square-plus.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl" import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Page from "@lib/components/Page.svelte" import Page from "@lib/components/Page.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import MenuButton from "@lib/components/MenuButton.svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import FAB from "@lib/components/FAB.svelte" import FAB from "@lib/components/FAB.svelte"
import SecondaryNav from "@lib/components/SecondaryNav.svelte" import SecondaryNav from "@lib/components/SecondaryNav.svelte"
@ -27,8 +28,6 @@
const {children}: Props = $props() const {children}: Props = $props()
const openMenu = () => pushModal(ChatMenu)
const startChat = () => pushModal(ChatStart) const startChat = () => pushModal(ChatStart)
let term = $state("") let term = $state("")
@ -46,14 +45,16 @@
<SecondaryNavSection> <SecondaryNavSection>
<SecondaryNavHeader> <SecondaryNavHeader>
Chats Chats
<Button onclick={openMenu}> <div class="flex items-center">
<Icon icon={MenuDots} /> <Button
class="button button-ghost button-sm button-circle"
aria-label="Start New Chat"
onclick={startChat}>
<Icon icon={Add} />
</Button> </Button>
<MenuButton component={ChatMenu} aria-label="Chat options" />
</div>
</SecondaryNavHeader> </SecondaryNavHeader>
<Button class="button button-primary button-sm w-full" onclick={startChat}>
<Icon icon={ChatSquarePlus} />
Start New Chat
</Button>
<label class="input input-sm flex items-center gap-2"> <label class="input input-sm flex items-center gap-2">
<Icon icon={Magnifier} /> <Icon icon={Magnifier} />
<input bind:value={term} class="grow" type="text" /> <input bind:value={term} class="grow" type="text" />

View file

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import InfoCircle from "@assets/icons/info-circle.svg?dataurl" import InfoCircle from "@assets/icons/info-circle.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl" import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl" import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import PageContent from "@lib/components/PageContent.svelte" import PageContent from "@lib/components/PageContent.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import MenuButton from "@lib/components/MenuButton.svelte"
import ContentSearch from "@lib/components/ContentSearch.svelte" import ContentSearch from "@lib/components/ContentSearch.svelte"
import ChatItem from "@app/components/ChatItem.svelte" import ChatItem from "@app/components/ChatItem.svelte"
import ChatStart from "@app/components/ChatStart.svelte" import ChatStart from "@app/components/ChatStart.svelte"
@ -17,8 +17,6 @@
const startChat = () => pushModal(ChatStart) const startChat = () => pushModal(ChatStart)
const openMenu = () => pushModal(ChatMenu)
const chats = $derived($chatSearch.searchOptions(term)) const chats = $derived($chatSearch.searchOptions(term))
</script> </script>
@ -45,9 +43,7 @@
type="text" type="text"
placeholder="Search for conversations..." /> placeholder="Search for conversations..." />
</label> </label>
<Button class="button button-neutral" onclick={openMenu}> <MenuButton component={ChatMenu} class="button button-neutral" aria-label="Chat options" />
<Icon icon={MenuDots} />
</Button>
</div> </div>
{/snippet} {/snippet}
{#snippet content()} {#snippet content()}