feat:(#145) add start chat FAB #152

Merged
hodlbod merged 4 commits from Prat_09/flotilla:145-startChat-FAB into dev 2026-04-07 17:02:41 +00:00
4 changed files with 71 additions and 11 deletions
Showing only changes of commit 113e0ff10a - Show all commits
-9
View File
@@ -1,6 +1,5 @@
<script lang="ts">
import {assoc} from "@welshman/lib"
import ChatSquare from "@assets/icons/chat-square.svg?dataurl"
import Check from "@assets/icons/check.svg?dataurl"
import Bell from "@assets/icons/bell.svg?dataurl"
import BellOff from "@assets/icons/bell-off.svg?dataurl"
@@ -8,13 +7,9 @@
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ChatStart from "@app/components/ChatStart.svelte"
import {setChecked} from "@app/util/notifications"
import {pushModal} from "@app/util/modal"
import {notificationSettings} from "@app/core/state"
const startChat = () => pushModal(ChatStart, {}, {replaceState: true})
const markAsRead = () => {
setChecked("/chat/*")
history.back()
@@ -28,10 +23,6 @@
<Modal>
<ModalBody>
<div class="flex flex-col gap-2">
<Button class="btn btn-primary" onclick={startChat}>
<Icon size={5} icon={ChatSquare} />
Start chat
</Button>
<Button class="btn btn-neutral" onclick={markAsRead}>
<Icon size={5} icon={Check} />
Mark all read
+48
View File
@@ -0,0 +1,48 @@
<script lang="ts">
import Button from "@lib/components/Button.svelte"
const {
onclick = () => {},
className = "",
size = 54,
}: {
onclick?: () => void
className?: string
size?: number
} = $props()
const bubbleSize = $derived(Math.round(size * 0.55))
const plusSize = $derived(Math.round(size * 0.33))
</script>
<div class="fixed bottom-20 right-sai z-nav m-4 md:m-8 hide-on-keyboard {className}">
<Button
class="btn bg-[#7161ff] border-none text-white shadow-xl hover:bg-[#5e51d6] transition-all p-0"
style="width: {size}px; height: {size}px; border-radius: {size * 0.33}px;"
{onclick}>
<div class="relative" style="width: {bubbleSize}px; height: {bubbleSize}px;" data-node-id="2:8">
<svg
viewBox="0 0 24 24"
fill="white"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="absolute inset-0 size-full">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
</svg>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="#7161ff"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
class="absolute left-1/2 top-[42%] -translate-x-1/2 -translate-y-1/2"
style="width: {plusSize}px; height: {plusSize}px;">
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
Outdated
Review

The SVGs shouldn't be hard-coded in here. Instead, include a children snippet in props and use the Icon component with one of the icons already included in the project.

The SVGs shouldn't be hard-coded in here. Instead, include a `children` snippet in props and use the `Icon` component with one of the icons already included in the project.
</div>
</Button>
</div>
+13 -2
View File
@@ -10,10 +10,12 @@
import Page from "@lib/components/Page.svelte"
import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import FAB from "@lib/components/FAB.svelte"
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
import SecondaryNavHeader from "@lib/components/SecondaryNavHeader.svelte"
import SecondaryNavSection from "@lib/components/SecondaryNavSection.svelte"
import ChatMenu from "@app/components/ChatMenu.svelte"
import ChatStart from "@app/components/ChatStart.svelte"
import ChatItem from "@app/components/ChatItem.svelte"
import {chatSearch} from "@app/core/state"
import {pushModal} from "@app/util/modal"
@@ -26,18 +28,22 @@
const openMenu = () => pushModal(ChatMenu)
const startChat = () => pushModal(ChatStart)
let term = $state("")
let isClient = $state(false)
hodlbod marked this conversation as resolved Outdated
Outdated
Review

We don't do SSR, so there's no need to add this check

We don't do SSR, so there's no need to add this check
const chats = $derived($chatSearch.searchOptions(term))
const promise = sleep(10000)
onMount(() => {
isClient = true
shouldUnwrap.set(true)
})
</script>
<SecondaryNav>
<SecondaryNav class="relative">
<SecondaryNavSection>
<SecondaryNavHeader>
Chats
@@ -50,7 +56,7 @@
<Icon icon={Magnifier} />
<input bind:value={term} class="grow" type="text" />
</label>
<div class="overflow-auto">
<div class="overflow-auto pb-20">
{#each chats as { id, pubkeys, messages } (id)}
<ChatItem {id} {pubkeys} {messages} />
{/each}
@@ -60,6 +66,11 @@
</div>
{/await}
</div>
{#if isClient}
<div class="absolute bottom-10 right-4 hidden md:block">
<FAB onclick={startChat} size={44} className="!static !m-0" />
</div>
Outdated
Review

We shouldn't need a wrapper and important overrides. I think it would make sense to show the FAB on the bottom right of the screen on desktop, rather than in the secondary nav.

We shouldn't need a wrapper and important overrides. I think it would make sense to show the FAB on the bottom right of the screen on desktop, rather than in the secondary nav.
Outdated
Review

Thanks for the feedback @hodlbod ! I’ll refactor the FAB to use the Icon component and clean up the SSR-related logic.

Regarding FAB placement, I noticed that having it fixed at the bottom-right works well generally, but in desktop during an active conversation it can overlap message content or interfere with interactions near the input area.

Would it make sense to:

  • Show the FAB as a floating button (bottom-right) when the user is not inside a conversation, and
  • Place it in the secondary nav when inside an active conversation to avoid overlap?

This keeps the action easily accessible while adapting to the context. Happy to implement this if it aligns with your vision.

Thanks for the feedback @hodlbod ! I’ll refactor the FAB to use the Icon component and clean up the SSR-related logic. Regarding FAB placement, I noticed that having it fixed at the bottom-right works well generally, but in desktop during an active conversation it can overlap message content or interfere with interactions near the input area. Would it make sense to: * Show the FAB as a floating button (bottom-right) when the user is not inside a conversation, and * Place it in the secondary nav when inside an active conversation to avoid overlap? This keeps the action easily accessible while adapting to the context. Happy to implement this if it aligns with your vision.
{/if}
</SecondaryNav>
<Page>
{#key $page.url.pathname}
+10
View File
@@ -11,8 +11,15 @@
import ChatMenu from "@app/components/ChatMenu.svelte"
import {chatSearch} from "@app/core/state"
import {pushModal} from "@app/util/modal"
import FAB from "@lib/components/FAB.svelte"
import {onMount} from "svelte"
let term = $state("")
let isClient = $state(false)
onMount(() => {
isClient = true
})
Outdated
Review

No need to include this

No need to include this
const startChat = () => pushModal(ChatStart)
@@ -64,6 +71,9 @@
</Button>
</div>
{/each}
{#if isClient}
<FAB onclick={startChat} className="md:hidden" />
{/if}
</div>
{/snippet}
</ContentSearch>