feat: redesign mobile space sidebar with card-based layout. (#318)
Co-authored-by: Aditya Chaudhary <30+useradityaa@noreply.coracle.social> Co-committed-by: Aditya Chaudhary <30+useradityaa@noreply.coracle.social>
This commit is contained in:
parent
7011d0ac81
commit
f4fc9b82c3
6 changed files with 370 additions and 188 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {derived} from "svelte/store"
|
import {derived} from "svelte/store"
|
||||||
|
import cx from "classnames"
|
||||||
import {displayRelayUrl, EVENT_TIME, ZAP_GOAL, THREAD, CLASSIFIED, POLL} from "@welshman/util"
|
import {displayRelayUrl, EVENT_TIME, ZAP_GOAL, THREAD, CLASSIFIED, POLL} from "@welshman/util"
|
||||||
import {deriveRelay, createSearch, pubkey} from "@welshman/app"
|
import {deriveRelay, createSearch, pubkey} from "@welshman/app"
|
||||||
import {fly} from "@lib/transition"
|
import {fly} from "@lib/transition"
|
||||||
|
|
@ -59,7 +60,12 @@
|
||||||
import {makeSpacePath, goToChat} from "@app/routes"
|
import {makeSpacePath, goToChat} from "@app/routes"
|
||||||
import {notifications} from "@app/notifications"
|
import {notifications} from "@app/notifications"
|
||||||
|
|
||||||
const {url} = $props()
|
type Props = {
|
||||||
|
url: string
|
||||||
|
mobile?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url, mobile = false}: Props = $props()
|
||||||
|
|
||||||
const relay = deriveRelay(url)
|
const relay = deriveRelay(url)
|
||||||
const chatPath = makeSpacePath(url, "chat")
|
const chatPath = makeSpacePath(url, "chat")
|
||||||
|
|
@ -136,11 +142,13 @@
|
||||||
let element: Element | undefined = $state()
|
let element: Element | undefined = $state()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={element} class="flex min-h-0 flex-1 flex-col">
|
{#snippet spaceHeader()}
|
||||||
<SecondaryNavSection class="min-h-0 flex-1 flex flex-col pb-0">
|
|
||||||
<div class="shrink-0">
|
|
||||||
<Button
|
<Button
|
||||||
class="relative flex w-full flex-col rounded-xl p-3 transition-all hover:bg-surface"
|
class={cx(
|
||||||
|
mobile
|
||||||
|
? "space-menu__header-button text-content"
|
||||||
|
: "relative flex w-full flex-col rounded-xl p-3 transition-all hover:bg-surface",
|
||||||
|
)}
|
||||||
onclick={openMenu}>
|
onclick={openMenu}>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<strong class="flex items-center gap-1 relative">
|
<strong class="flex items-center gap-1 relative">
|
||||||
|
|
@ -219,8 +227,9 @@
|
||||||
</ul>
|
</ul>
|
||||||
</Popover>
|
</Popover>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
{/snippet}
|
||||||
<div class="flex min-h-0 flex-1 flex-col gap-1 py-1 overflow-auto">
|
|
||||||
|
{#snippet spaceNavItems()}
|
||||||
<SecondaryNavItem href={makeSpacePath(url, "about")}>
|
<SecondaryNavItem href={makeSpacePath(url, "about")}>
|
||||||
<Icon icon={Home} /> Space Details
|
<Icon icon={Home} /> Space Details
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
|
|
@ -270,15 +279,24 @@
|
||||||
<SecondaryNavItem onclick={openSearch}>
|
<SecondaryNavItem onclick={openSearch}>
|
||||||
<Icon icon={Magnifier} /> Search
|
<Icon icon={Magnifier} /> Search
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet spaceRooms()}
|
||||||
|
{#if hasNip29($relay)}
|
||||||
{#if $userRooms.length > 0}
|
{#if $userRooms.length > 0}
|
||||||
|
{#if !mobile}
|
||||||
<div class="h-2 shrink-0"></div>
|
<div class="h-2 shrink-0"></div>
|
||||||
|
{/if}
|
||||||
<SecondaryNavHeader>Your Rooms</SecondaryNavHeader>
|
<SecondaryNavHeader>Your Rooms</SecondaryNavHeader>
|
||||||
{/if}
|
{/if}
|
||||||
{#each $userRooms as h (h)}
|
{#each $userRooms as h (h)}
|
||||||
<SpaceMenuRoomItem {url} {h} />
|
<SpaceMenuRoomItem {url} {h} tooltip={!mobile} />
|
||||||
{/each}
|
{/each}
|
||||||
{#if $otherRooms.length > 0}
|
{#if $otherRooms.length > 0}
|
||||||
|
{#if !mobile}
|
||||||
<div class="h-2 shrink-0"></div>
|
<div class="h-2 shrink-0"></div>
|
||||||
|
{/if}
|
||||||
<SecondaryNavHeader>
|
<SecondaryNavHeader>
|
||||||
{#if $userRooms.length > 0}
|
{#if $userRooms.length > 0}
|
||||||
Other Rooms
|
Other Rooms
|
||||||
|
|
@ -294,13 +312,15 @@
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
{#each $roomSearch.searchValues(term) as h (h)}
|
{#each $roomSearch.searchValues(term) as h (h)}
|
||||||
<SpaceMenuRoomItem {url} {h} />
|
<SpaceMenuRoomItem {url} {h} tooltip={!mobile} />
|
||||||
{/each}
|
{/each}
|
||||||
{#if $otherVoiceRooms.length > 0}
|
{#if $otherVoiceRooms.length > 0}
|
||||||
|
{#if !mobile}
|
||||||
<div class="h-2 shrink-0"></div>
|
<div class="h-2 shrink-0"></div>
|
||||||
|
{/if}
|
||||||
<SecondaryNavHeader>Voice Rooms</SecondaryNavHeader>
|
<SecondaryNavHeader>Voice Rooms</SecondaryNavHeader>
|
||||||
{#each $otherVoiceRooms as h (h)}
|
{#each $otherVoiceRooms as h (h)}
|
||||||
<SpaceMenuRoomItem {url} {h} />
|
<SpaceMenuRoomItem {url} {h} tooltip={!mobile} />
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
{#if $canCreateRoom}
|
{#if $canCreateRoom}
|
||||||
|
|
@ -310,13 +330,51 @@
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet spaceFooter()}
|
||||||
|
<VoiceWidget />
|
||||||
|
<Link
|
||||||
|
href={makeSpacePath(url, "about")}
|
||||||
|
class={cx(mobile ? "space-menu__status-link" : "button button-neutral button-sm h-10")}>
|
||||||
|
<SocketStatusIndicator {url} />
|
||||||
|
</Link>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<div bind:this={element} class="flex min-h-0 flex-1 flex-col">
|
||||||
|
{#if mobile}
|
||||||
|
<SecondaryNavSection class="space-menu min-h-0 flex-1 flex flex-col gap-3 p-0">
|
||||||
|
<div class="card space-menu__card space-menu__header shrink-0">
|
||||||
|
{@render spaceHeader()}
|
||||||
|
</div>
|
||||||
|
<div class="space-menu__scroll flex flex-col">
|
||||||
|
<div class="card space-menu__card flex flex-col gap-1">
|
||||||
|
<SecondaryNavHeader>Space</SecondaryNavHeader>
|
||||||
|
{@render spaceNavItems()}
|
||||||
|
</div>
|
||||||
|
{#if hasNip29($relay) && ($userRooms.length > 0 || $otherRooms.length > 0 || $otherVoiceRooms.length > 0 || $canCreateRoom)}
|
||||||
|
<div class="card space-menu__card flex flex-col gap-1">
|
||||||
|
{@render spaceRooms()}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="card space-menu__card space-menu__status shrink-0">
|
||||||
|
{@render spaceFooter()}
|
||||||
|
</div>
|
||||||
|
</SecondaryNavSection>
|
||||||
|
{:else}
|
||||||
|
<SecondaryNavSection class="min-h-0 flex-1 flex flex-col pb-0">
|
||||||
|
<div class="shrink-0">
|
||||||
|
{@render spaceHeader()}
|
||||||
|
</div>
|
||||||
|
<div class="flex min-h-0 flex-1 flex-col gap-1 py-1 overflow-auto">
|
||||||
|
{@render spaceNavItems()}
|
||||||
|
{@render spaceRooms()}
|
||||||
<div class="h-5 shrink-0"></div>
|
<div class="h-5 shrink-0"></div>
|
||||||
</div>
|
</div>
|
||||||
</SecondaryNavSection>
|
</SecondaryNavSection>
|
||||||
<div class="flex shrink-0 flex-col gap-2 p-2 pt-0 -mt-4 pb-1 md:pb-2 z-nav">
|
<div class="flex shrink-0 flex-col gap-2 p-2 pt-0 -mt-4 pb-1 md:pb-2 z-nav">
|
||||||
<VoiceWidget />
|
{@render spaceFooter()}
|
||||||
<Link href={makeSpacePath(url, "about")} class="button button-neutral button-sm h-10">
|
|
||||||
<SocketStatusIndicator {url} />
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,14 @@
|
||||||
import {notifications} from "@app/notifications"
|
import {notifications} from "@app/notifications"
|
||||||
import {makeRoomPath} from "@app/routes"
|
import {makeRoomPath} from "@app/routes"
|
||||||
|
|
||||||
interface Props {
|
type Props = {
|
||||||
url: any
|
url: string
|
||||||
h: any
|
h: string
|
||||||
replaceState?: boolean
|
replaceState?: boolean
|
||||||
|
tooltip?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const {url, h, replaceState = false}: Props = $props()
|
const {url, h, replaceState = false, tooltip = true}: Props = $props()
|
||||||
|
|
||||||
const room = deriveRoom(url, h)
|
const room = deriveRoom(url, h)
|
||||||
const roomType = $derived(getRoomType($room))
|
const roomType = $derived(getRoomType($room))
|
||||||
|
|
@ -31,7 +32,7 @@
|
||||||
{#if roomType === RoomType.Voice}
|
{#if roomType === RoomType.Voice}
|
||||||
<VoiceRoomItem {url} {h} {replaceState} {notification} />
|
<VoiceRoomItem {url} {h} {replaceState} {notification} />
|
||||||
{:else}
|
{:else}
|
||||||
<SecondaryNavItem href={path} title={roomName} {replaceState} {notification}>
|
<SecondaryNavItem href={path} title={tooltip ? roomName : ""} {replaceState} {notification}>
|
||||||
<RoomNameWithImage {url} {h} />
|
<RoomNameWithImage {url} {h} />
|
||||||
{#if showDifferenceIcon}
|
{#if showDifferenceIcon}
|
||||||
<Icon icon={$shouldNotifyForRoom ? Bell : BellOff} size={4} class="ml-auto opacity-50" />
|
<Icon icon={$shouldNotifyForRoom ? Bell : BellOff} size={4} class="ml-auto opacity-50" />
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
const {children}: Props = $props()
|
const {children}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center justify-between px-1 py-2 text-sm font-bold uppercase">
|
<div
|
||||||
|
class="secondary-nav__header flex items-center justify-between px-1 py-2 text-sm font-bold uppercase">
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
125
src/lib/components/space-menu.css
Normal file
125
src/lib/components/space-menu.css
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
@layer components {
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.secondary-nav--mobile-space {
|
||||||
|
@apply mt-sai bg-backdrop mb-0 w-full max-w-full grow border-0;
|
||||||
|
|
||||||
|
.tip[data-tip]::before {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-nav__nav-item {
|
||||||
|
@apply rounded-2xl font-medium;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply bg-surface-less text-content;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-nav__nav-item--active {
|
||||||
|
@apply text-content;
|
||||||
|
background-color: color-mix(in srgb, var(--primary) 14%, var(--surface));
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply text-content;
|
||||||
|
background-color: color-mix(in srgb, var(--primary) 18%, var(--surface));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-menu {
|
||||||
|
@apply gap-3 p-3 pt-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-menu__card {
|
||||||
|
@apply p-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-menu__card .secondary-nav__header {
|
||||||
|
@apply text-content-muted px-2 py-1 text-xs font-semibold tracking-wider uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-menu__card .secondary-nav__header:first-child {
|
||||||
|
@apply text-content pb-0.5 font-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-menu__card .secondary-nav__header:not(:first-child) {
|
||||||
|
@apply border-line-less mt-2 border-t pt-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-menu__header {
|
||||||
|
@apply p-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-menu__header-button {
|
||||||
|
@apply rounded-2xl p-0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply bg-transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-menu__scroll {
|
||||||
|
@apply min-h-0 flex-1 gap-3 overflow-y-auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-menu__status {
|
||||||
|
@apply flex w-full flex-col gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-menu__status > .card {
|
||||||
|
@apply w-full gap-1 border-0 bg-transparent p-0 shadow-none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-menu__status-link {
|
||||||
|
@apply h-auto w-full border-0 bg-transparent p-0 shadow-none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@apply text-content border-0 bg-transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-menu__status-link .flex {
|
||||||
|
@apply justify-start px-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
[data-theme="dark"] .secondary-nav--mobile-space {
|
||||||
|
@apply bg-surface-less;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-fl-theme="clay"] .secondary-nav--mobile-space {
|
||||||
|
.secondary-nav__nav-item:hover,
|
||||||
|
.secondary-nav__nav-item:active {
|
||||||
|
background-color: var(--surface-less);
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-nav__nav-item--active,
|
||||||
|
.secondary-nav__nav-item--active:hover {
|
||||||
|
background-color: color-mix(in srgb, var(--primary) 14%, var(--surface));
|
||||||
|
color: var(--content);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-fl-theme="clay"] .space-menu__status-link:hover,
|
||||||
|
[data-fl-theme="clay"] .space-menu__status-link:active {
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-menu__status .text-sm {
|
||||||
|
@apply font-medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
@import "./room.css";
|
@import "./room.css";
|
||||||
@import "./spinner.css";
|
@import "./spinner.css";
|
||||||
@import "./secondary-nav.css";
|
@import "./secondary-nav.css";
|
||||||
|
@import "./space-menu.css";
|
||||||
@import "./tooltip.css";
|
@import "./tooltip.css";
|
||||||
|
|
||||||
@config "../../../tailwind.config.js";
|
@config "../../../tailwind.config.js";
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
||||||
import {decodeRelay} from "@app/relays"
|
import {decodeRelay} from "@app/relays"
|
||||||
import {goToSpace} from "@app/routes"
|
import {goToSpace} from "@app/routes"
|
||||||
import PrimaryNavSpaces from "@app/components/PrimaryNavSpaces.svelte"
|
|
||||||
import SpaceMenu from "@app/components/SpaceMenu.svelte"
|
import SpaceMenu from "@app/components/SpaceMenu.svelte"
|
||||||
|
|
||||||
const url = decodeRelay($page.params.relay!)
|
const url = decodeRelay($page.params.relay!)
|
||||||
|
|
@ -22,10 +21,7 @@
|
||||||
<svelte:window bind:innerWidth={width} />
|
<svelte:window bind:innerWidth={width} />
|
||||||
|
|
||||||
{#if width <= md}
|
{#if width <= md}
|
||||||
<div class="primary-nav">
|
<SecondaryNav visible class="secondary-nav--mobile-space w-auto grow pb-16">
|
||||||
<PrimaryNavSpaces />
|
<SpaceMenu {url} mobile />
|
||||||
</div>
|
|
||||||
<SecondaryNav visible class="w-auto grow pb-16">
|
|
||||||
<SpaceMenu {url} />
|
|
||||||
</SecondaryNav>
|
</SecondaryNav>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue