mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 00:41:09 +02:00
feat(manalink): optimize mobile layout with bottom sheets, compact header, and touch interactions
- Add long-press on messages to open action bottom sheet (reply, forward, edit, delete, reactions) - Compact mobile chat list header into single row (title + status + unread inline) - Convert emoji picker and attachment menu to bottom sheets on mobile - Shrink room avatar in header on mobile (h-8 instead of h-10) - Hide PillNavigation and spacer in mobile room view for more chat space - Use compact time format in room list (Min., Std., T., Wo.) - Replace hover translate with active:scale tap feedback on room items - Widen swipe-back edge zone (50px) and lower threshold (80px) - Hide keyboard hint text on mobile - Hide duplicate "Neu" button in room list on mobile - Add slide-up animation for bottom sheets Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3500ac5e23
commit
1edbc190a6
9 changed files with 289 additions and 70 deletions
|
|
@ -39,6 +39,20 @@
|
|||
animation: fade-in 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* Slide-up animation for bottom sheets */
|
||||
@keyframes slide-up {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-slide-up {
|
||||
animation: slide-up 0.25s ease-out;
|
||||
}
|
||||
|
||||
/* Custom scrollbar for chat */
|
||||
.chat-scrollbar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
|
|
|
|||
|
|
@ -60,10 +60,47 @@
|
|||
);
|
||||
|
||||
let showActions = $state(false);
|
||||
let showMobileActions = $state(false);
|
||||
let showEmojiPicker = $state(false);
|
||||
let imageLoading = $state(true);
|
||||
let imageError = $state(false);
|
||||
|
||||
// Long-press for mobile
|
||||
let longPressTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let touchMoved = false;
|
||||
|
||||
function handleTouchStart() {
|
||||
touchMoved = false;
|
||||
longPressTimer = setTimeout(() => {
|
||||
if (!touchMoved && !message.redacted) {
|
||||
showMobileActions = true;
|
||||
// Vibrate if available
|
||||
if (navigator.vibrate) navigator.vibrate(20);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleTouchMove() {
|
||||
touchMoved = true;
|
||||
if (longPressTimer) {
|
||||
clearTimeout(longPressTimer);
|
||||
longPressTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function handleTouchEnd() {
|
||||
if (longPressTimer) {
|
||||
clearTimeout(longPressTimer);
|
||||
longPressTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function closeMobileActions() {
|
||||
showMobileActions = false;
|
||||
showEmojiPicker = false;
|
||||
showFullPicker = false;
|
||||
}
|
||||
|
||||
// Quick reaction emojis (always visible)
|
||||
const quickEmojis = ['👍', '❤️', '😂', '😮', '😢', '🎉'];
|
||||
|
||||
|
|
@ -320,6 +357,10 @@
|
|||
role="article"
|
||||
onmouseenter={() => (showActions = true)}
|
||||
onmouseleave={() => (showActions = false)}
|
||||
ontouchstart={handleTouchStart}
|
||||
ontouchmove={handleTouchMove}
|
||||
ontouchend={handleTouchEnd}
|
||||
ontouchcancel={handleTouchEnd}
|
||||
>
|
||||
<!-- Avatar -->
|
||||
{#if showAvatar}
|
||||
|
|
@ -339,7 +380,7 @@
|
|||
<div
|
||||
class="flex flex-col {message.isOwn
|
||||
? 'items-end'
|
||||
: 'items-start'} max-w-[85%] sm:max-w-[75%] relative"
|
||||
: 'items-start'} max-w-[80%] sm:max-w-[75%] relative"
|
||||
>
|
||||
<!-- Sender name (for others only) -->
|
||||
{#if showAvatar && !message.isOwn}
|
||||
|
|
@ -725,3 +766,80 @@
|
|||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Action Bottom Sheet -->
|
||||
{#if showMobileActions}
|
||||
<button
|
||||
class="fixed inset-0 z-[100] bg-black/40 backdrop-blur-sm"
|
||||
onclick={closeMobileActions}
|
||||
aria-label="Schließen"
|
||||
></button>
|
||||
<div
|
||||
class="fixed bottom-0 left-0 right-0 z-[101] bg-surface-elevated border-t border-border rounded-t-2xl safe-area-bottom animate-slide-up"
|
||||
>
|
||||
<!-- Quick reactions row -->
|
||||
<div class="flex items-center justify-center gap-3 px-4 pt-4 pb-2">
|
||||
{#each quickEmojis as emoji}
|
||||
<button
|
||||
class="text-2xl p-2 rounded-full hover:bg-surface-hover active:scale-90 transition-all"
|
||||
onclick={() => {
|
||||
handleReaction(emoji);
|
||||
closeMobileActions();
|
||||
}}
|
||||
>
|
||||
{emoji}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="h-px bg-border mx-4"></div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="p-2">
|
||||
<button
|
||||
class="flex items-center gap-3 w-full px-4 py-3 rounded-xl active:bg-surface-hover transition-colors"
|
||||
onclick={() => {
|
||||
onReply?.(message);
|
||||
closeMobileActions();
|
||||
}}
|
||||
>
|
||||
<ArrowBendUpLeft class="h-5 w-5 text-muted-foreground" />
|
||||
<span class="text-sm font-medium">Antworten</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center gap-3 w-full px-4 py-3 rounded-xl active:bg-surface-hover transition-colors"
|
||||
onclick={() => {
|
||||
onForward?.(message);
|
||||
closeMobileActions();
|
||||
}}
|
||||
>
|
||||
<ArrowBendUpRight class="h-5 w-5 text-muted-foreground" />
|
||||
<span class="text-sm font-medium">Weiterleiten</span>
|
||||
</button>
|
||||
{#if message.isOwn && message.type === 'm.text'}
|
||||
<button
|
||||
class="flex items-center gap-3 w-full px-4 py-3 rounded-xl active:bg-surface-hover transition-colors"
|
||||
onclick={() => {
|
||||
onEdit?.(message);
|
||||
closeMobileActions();
|
||||
}}
|
||||
>
|
||||
<PencilSimple class="h-5 w-5 text-muted-foreground" />
|
||||
<span class="text-sm font-medium">Bearbeiten</span>
|
||||
</button>
|
||||
{/if}
|
||||
{#if message.isOwn}
|
||||
<button
|
||||
class="flex items-center gap-3 w-full px-4 py-3 rounded-xl active:bg-surface-hover transition-colors"
|
||||
onclick={() => {
|
||||
handleDelete();
|
||||
closeMobileActions();
|
||||
}}
|
||||
>
|
||||
<Trash class="h-5 w-5 text-red-500" />
|
||||
<span class="text-sm font-medium text-red-500">Löschen</span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -626,13 +626,13 @@
|
|||
{#if showAttachMenu}
|
||||
<!-- Backdrop -->
|
||||
<button
|
||||
class="fixed inset-0 z-40"
|
||||
class="fixed inset-0 z-40 lg:bg-transparent bg-black/40"
|
||||
onclick={() => (showAttachMenu = false)}
|
||||
aria-label="Menü schließen"
|
||||
></button>
|
||||
<!-- Dropdown menu -->
|
||||
<!-- Desktop: Dropdown above button -->
|
||||
<div
|
||||
class="absolute bottom-full left-0 mb-2 z-50 w-44 rounded-xl bg-surface-elevated border border-border p-1.5 shadow-xl"
|
||||
class="hidden lg:block absolute bottom-full left-0 mb-2 z-50 w-44 rounded-xl bg-surface-elevated border border-border p-1.5 shadow-xl"
|
||||
>
|
||||
<button
|
||||
onclick={() => {
|
||||
|
|
@ -655,6 +655,33 @@
|
|||
Datei
|
||||
</button>
|
||||
</div>
|
||||
<!-- Mobile: Bottom sheet -->
|
||||
<div
|
||||
class="lg:hidden fixed bottom-0 left-0 right-0 z-50 bg-surface-elevated border-t border-border rounded-t-2xl safe-area-bottom animate-slide-up"
|
||||
>
|
||||
<div class="p-2">
|
||||
<button
|
||||
onclick={() => {
|
||||
openFilePicker();
|
||||
showAttachMenu = false;
|
||||
}}
|
||||
class="flex items-center gap-3 w-full px-4 py-3.5 rounded-xl active:bg-surface-hover transition-colors"
|
||||
>
|
||||
<Image class="h-5 w-5 text-muted-foreground" />
|
||||
<span class="text-sm font-medium">Bild oder Video</span>
|
||||
</button>
|
||||
<button
|
||||
onclick={() => {
|
||||
openFilePicker();
|
||||
showAttachMenu = false;
|
||||
}}
|
||||
class="flex items-center gap-3 w-full px-4 py-3.5 rounded-xl active:bg-surface-hover transition-colors"
|
||||
>
|
||||
<FileIcon class="h-5 w-5 text-muted-foreground" />
|
||||
<span class="text-sm font-medium">Datei</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
@ -697,19 +724,18 @@
|
|||
<Smiley size={22} class="text-muted-foreground" />
|
||||
</button>
|
||||
|
||||
<!-- Emoji Picker Popup -->
|
||||
<!-- Emoji Picker -->
|
||||
{#if showEmojiPicker}
|
||||
<!-- Backdrop -->
|
||||
<button
|
||||
class="fixed inset-0 z-40"
|
||||
class="fixed inset-0 z-40 lg:bg-transparent bg-black/40"
|
||||
onclick={() => (showEmojiPicker = false)}
|
||||
aria-label="Emoji-Picker schließen"
|
||||
></button>
|
||||
<!-- Picker -->
|
||||
<!-- Desktop: Popup above input -->
|
||||
<div
|
||||
class="absolute bottom-full right-0 mb-2 z-50 w-72 max-h-80 overflow-y-auto rounded-xl bg-surface-elevated border border-border p-2 shadow-xl"
|
||||
class="hidden lg:block absolute bottom-full right-0 mb-2 z-50 w-72 max-h-80 overflow-y-auto rounded-xl bg-surface-elevated border border-border p-2 shadow-xl"
|
||||
>
|
||||
<!-- Recent/Frequently used emojis -->
|
||||
{#if recentEmojis.length > 0}
|
||||
<div class="mb-2">
|
||||
<p class="text-[10px] text-muted-foreground uppercase font-medium px-1 mb-1">
|
||||
|
|
@ -728,7 +754,6 @@
|
|||
</div>
|
||||
<div class="border-t border-border my-2"></div>
|
||||
{/if}
|
||||
<!-- All emojis -->
|
||||
<div class="grid grid-cols-8 gap-1">
|
||||
{#each commonEmojis as emoji}
|
||||
<button
|
||||
|
|
@ -740,6 +765,41 @@
|
|||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Mobile: Bottom sheet -->
|
||||
<div
|
||||
class="lg:hidden fixed bottom-0 left-0 right-0 z-50 bg-surface-elevated border-t border-border rounded-t-2xl safe-area-bottom animate-slide-up"
|
||||
>
|
||||
<div class="p-3 max-h-[50vh] overflow-y-auto">
|
||||
{#if recentEmojis.length > 0}
|
||||
<div class="mb-3">
|
||||
<p class="text-[10px] text-muted-foreground uppercase font-medium px-1 mb-1">
|
||||
Häufig benutzt
|
||||
</p>
|
||||
<div class="grid grid-cols-8 gap-1">
|
||||
{#each recentEmojis as emoji}
|
||||
<button
|
||||
class="p-2 text-2xl active:scale-90 rounded-lg transition-transform"
|
||||
onclick={() => insertEmoji(emoji)}
|
||||
>
|
||||
{emoji}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-border my-2"></div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-8 gap-1">
|
||||
{#each commonEmojis as emoji}
|
||||
<button
|
||||
class="p-2 text-2xl active:scale-90 rounded-lg transition-transform"
|
||||
onclick={() => insertEmoji(emoji)}
|
||||
>
|
||||
{emoji}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
@ -775,8 +835,8 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Hint -->
|
||||
<p class="text-[10px] text-muted-foreground/60 text-center mt-1.5">
|
||||
<!-- Hint (desktop only) -->
|
||||
<p class="hidden lg:block text-[10px] text-muted-foreground/60 text-center mt-1.5">
|
||||
{#if editMessage}
|
||||
Enter = Speichern · Escape = Abbrechen
|
||||
{:else}
|
||||
|
|
|
|||
|
|
@ -98,13 +98,17 @@
|
|||
<!-- Room avatar with online indicator -->
|
||||
<div class="relative flex-shrink-0">
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-full shadow-md
|
||||
class="flex h-8 w-8 lg:h-10 lg:w-10 items-center justify-center rounded-full shadow-md
|
||||
bg-gradient-to-br from-violet-500 to-purple-600 text-white"
|
||||
>
|
||||
{#if room.avatar}
|
||||
<img src={room.avatar} alt={room.name} class="h-10 w-10 rounded-full object-cover" />
|
||||
<img
|
||||
src={room.avatar}
|
||||
alt={room.name}
|
||||
class="h-8 w-8 lg:h-10 lg:w-10 rounded-full object-cover"
|
||||
/>
|
||||
{:else}
|
||||
<span class="text-sm font-semibold">{room.name.charAt(0).toUpperCase()}</span>
|
||||
<span class="text-xs lg:text-sm font-semibold">{room.name.charAt(0).toUpperCase()}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- Online indicator for DMs -->
|
||||
|
|
@ -161,16 +165,16 @@
|
|||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex items-center gap-0.5 lg:gap-1">
|
||||
<button
|
||||
class="p-2.5 rounded-xl glass-button shadow-sm"
|
||||
class="p-2 lg:p-2.5 rounded-lg lg:rounded-xl lg:glass-button lg:shadow-sm hover:bg-surface-hover transition-colors"
|
||||
title="Suchen"
|
||||
onclick={onSearchClick}
|
||||
>
|
||||
<MagnifyingGlass class="h-5 w-5 text-muted-foreground" />
|
||||
</button>
|
||||
<button
|
||||
class="hidden sm:flex p-2.5 rounded-xl glass-button shadow-sm transition-colors
|
||||
class="hidden sm:flex p-2 lg:p-2.5 rounded-lg lg:rounded-xl lg:glass-button lg:shadow-sm transition-colors
|
||||
{canCall ? 'hover:bg-green-500/10 hover:text-green-500' : 'opacity-40 cursor-not-allowed'}"
|
||||
title={canCall ? 'Sprachanruf' : 'Anrufe nur in Direktnachrichten verfügbar'}
|
||||
disabled={!canCall}
|
||||
|
|
@ -179,7 +183,7 @@
|
|||
<Phone class="h-5 w-5" />
|
||||
</button>
|
||||
<button
|
||||
class="hidden sm:flex p-2.5 rounded-xl glass-button shadow-sm transition-colors
|
||||
class="hidden sm:flex p-2 lg:p-2.5 rounded-lg lg:rounded-xl lg:glass-button lg:shadow-sm transition-colors
|
||||
{canCall ? 'hover:bg-violet-500/10 hover:text-violet-500' : 'opacity-40 cursor-not-allowed'}"
|
||||
title={canCall ? 'Videoanruf' : 'Anrufe nur in Direktnachrichten verfügbar'}
|
||||
disabled={!canCall}
|
||||
|
|
@ -188,7 +192,7 @@
|
|||
<VideoCamera class="h-5 w-5" />
|
||||
</button>
|
||||
<button
|
||||
class="p-2.5 rounded-xl glass-button shadow-sm"
|
||||
class="p-2 lg:p-2.5 rounded-lg lg:rounded-xl lg:glass-button lg:shadow-sm hover:bg-surface-hover transition-colors"
|
||||
title="Rauminfo"
|
||||
onclick={onInfoClick}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,17 @@
|
|||
if (!room.lastMessageTime) return '';
|
||||
const date = new Date(room.lastMessageTime);
|
||||
if (!isValid(date) || date.getTime() === 0) return '';
|
||||
// Compact time format
|
||||
const diffMs = Date.now() - date.getTime();
|
||||
const diffMin = Math.floor(diffMs / 60000);
|
||||
if (diffMin < 1) return 'jetzt';
|
||||
if (diffMin < 60) return `${diffMin} Min.`;
|
||||
const diffH = Math.floor(diffMin / 60);
|
||||
if (diffH < 24) return `${diffH} Std.`;
|
||||
const diffD = Math.floor(diffH / 24);
|
||||
if (diffD < 7) return `${diffD} T.`;
|
||||
const diffW = Math.floor(diffD / 7);
|
||||
if (diffW < 5) return `${diffW} Wo.`;
|
||||
return formatDistanceToNow(date, { addSuffix: false, locale: de });
|
||||
});
|
||||
|
||||
|
|
@ -49,7 +60,7 @@
|
|||
class="flex w-full items-center gap-3 px-3 py-2.5 mb-1 rounded-xl transition-all duration-200
|
||||
{selected
|
||||
? 'bg-surface-elevated shadow-md border border-border'
|
||||
: 'hover:bg-surface-hover hover:-translate-y-0.5'}"
|
||||
: 'hover:bg-surface-hover lg:hover:-translate-y-0.5 active:scale-[0.98]'}"
|
||||
{onclick}
|
||||
>
|
||||
<!-- Avatar with online indicator -->
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
<div class="flex h-full flex-col">
|
||||
<!-- Room List with Sections -->
|
||||
<div class="chat-scrollbar flex-1 overflow-y-auto px-3">
|
||||
<!-- New Chat action row -->
|
||||
<!-- Header row with room count + new chat (desktop only, mobile has it in page header) -->
|
||||
<div class="flex items-center justify-between px-2 py-2 mb-1">
|
||||
<span
|
||||
class="text-xs font-semibold uppercase text-muted-foreground tracking-wide flex items-center gap-2"
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
</span>
|
||||
</span>
|
||||
<button
|
||||
class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium
|
||||
class="hidden lg:flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium
|
||||
bg-gradient-to-r from-violet-500 to-purple-600 text-white
|
||||
shadow-sm hover:shadow-md hover:-translate-y-px transition-all duration-200"
|
||||
onclick={onCreateRoom}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,14 @@
|
|||
// Navigation state
|
||||
let isCollapsed = $state(false);
|
||||
|
||||
// Hide PillNavigation on mobile when inside a room view
|
||||
let isMobileRoomView = $derived(
|
||||
typeof window !== 'undefined' &&
|
||||
window.innerWidth < 1024 &&
|
||||
$page.url.pathname.startsWith('/chat/') &&
|
||||
$page.url.pathname !== '/chat'
|
||||
);
|
||||
|
||||
// Theme state
|
||||
let isDark = $derived(theme.isDark);
|
||||
|
||||
|
|
@ -382,34 +390,36 @@
|
|||
{:else if matrixStore.isReady}
|
||||
<!-- Ready - Show navigation and content -->
|
||||
<div class="layout-container">
|
||||
<!-- PillNavigation -->
|
||||
<PillNavigation
|
||||
items={navItems}
|
||||
currentPath={$page.url.pathname}
|
||||
appName="Manalink"
|
||||
homeRoute="/chat"
|
||||
onToggleTheme={handleToggleTheme}
|
||||
{isDark}
|
||||
{isCollapsed}
|
||||
onCollapsedChange={handleCollapsedChange}
|
||||
showThemeToggle={true}
|
||||
showThemeVariants={true}
|
||||
{themeVariantItems}
|
||||
{currentThemeVariantLabel}
|
||||
themeMode={theme.mode}
|
||||
onThemeModeChange={handleThemeModeChange}
|
||||
showLanguageSwitcher={true}
|
||||
{languageItems}
|
||||
{currentLanguageLabel}
|
||||
showLogout={true}
|
||||
onLogout={handleLogout}
|
||||
primaryColor="#8b5cf6"
|
||||
showAppSwitcher={true}
|
||||
{appItems}
|
||||
{userEmail}
|
||||
settingsHref="/settings"
|
||||
allAppsHref="https://mana.how"
|
||||
/>
|
||||
<!-- PillNavigation (hidden on mobile when in a room) -->
|
||||
{#if !isMobileRoomView}
|
||||
<PillNavigation
|
||||
items={navItems}
|
||||
currentPath={$page.url.pathname}
|
||||
appName="Manalink"
|
||||
homeRoute="/chat"
|
||||
onToggleTheme={handleToggleTheme}
|
||||
{isDark}
|
||||
{isCollapsed}
|
||||
onCollapsedChange={handleCollapsedChange}
|
||||
showThemeToggle={true}
|
||||
showThemeVariants={true}
|
||||
{themeVariantItems}
|
||||
{currentThemeVariantLabel}
|
||||
themeMode={theme.mode}
|
||||
onThemeModeChange={handleThemeModeChange}
|
||||
showLanguageSwitcher={true}
|
||||
{languageItems}
|
||||
{currentLanguageLabel}
|
||||
showLogout={true}
|
||||
onLogout={handleLogout}
|
||||
primaryColor="#8b5cf6"
|
||||
showAppSwitcher={true}
|
||||
{appItems}
|
||||
{userEmail}
|
||||
settingsHref="/settings"
|
||||
allAppsHref="https://mana.how"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content bg-background">
|
||||
|
|
@ -493,8 +503,10 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Spacer for PillNavigation -->
|
||||
<div class="pill-nav-spacer"></div>
|
||||
<!-- Spacer for PillNavigation (hidden when nav is hidden) -->
|
||||
{#if !isMobileRoomView}
|
||||
<div class="pill-nav-spacer"></div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Unknown state - redirect to login -->
|
||||
|
|
|
|||
|
|
@ -177,24 +177,24 @@
|
|||
{#if isMobile}
|
||||
<!-- Mobile: Full-screen room list -->
|
||||
<div class="flex flex-col h-full bg-background safe-area-bottom">
|
||||
<!-- User Info / Status Bar -->
|
||||
<div class="border-b border-border px-4 py-3 bg-surface-elevated safe-area-top">
|
||||
<!-- Compact Mobile Header -->
|
||||
<div class="border-b border-border px-4 py-2.5 bg-surface-elevated safe-area-top">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-xl font-bold text-foreground">Manalink</h1>
|
||||
<p class="flex items-center gap-1.5 text-xs text-muted-foreground mt-0.5">
|
||||
<span class="h-2 w-2 rounded-full bg-green-500"></span>
|
||||
<div class="flex items-center gap-2">
|
||||
<h1 class="text-lg font-bold text-foreground">Manalink</h1>
|
||||
<span class="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-green-500"></span>
|
||||
{matrixStore.syncState === 'SYNCING' ? 'Verbunden' : matrixStore.syncState}
|
||||
{#if matrixStore.totalUnreadCount > 0}
|
||||
<span
|
||||
class="ml-2 px-1.5 py-0.5 rounded-full bg-gradient-to-r from-blue-500 to-indigo-600 text-white text-xs font-medium"
|
||||
>
|
||||
{matrixStore.totalUnreadCount} ungelesen
|
||||
</span>
|
||||
{/if}
|
||||
</p>
|
||||
</span>
|
||||
{#if matrixStore.totalUnreadCount > 0}
|
||||
<span
|
||||
class="px-1.5 py-0.5 rounded-full bg-gradient-to-r from-blue-500 to-indigo-600 text-white text-[10px] font-medium"
|
||||
>
|
||||
{matrixStore.totalUnreadCount}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex items-center gap-0.5">
|
||||
<a
|
||||
href="/settings"
|
||||
class="p-2 rounded-lg hover:bg-surface-hover transition-colors"
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
let touchStartY = 0;
|
||||
let isSwiping = $state(false);
|
||||
let swipeProgress = $state(0);
|
||||
const SWIPE_THRESHOLD = 100; // px to trigger back navigation
|
||||
const EDGE_ZONE = 30; // px from left edge to start swipe
|
||||
const SWIPE_THRESHOLD = 80; // px to trigger back navigation
|
||||
const EDGE_ZONE = 50; // px from left edge to start swipe
|
||||
|
||||
let showRoomSettings = $state(false);
|
||||
let showSearch = $state(false);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue