Files
scrabble-game/ui/src/screens/SettingsHub.svelte
T
Ilia Denisov fc1261e078
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 39s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 59s
UI: tab-bar navigation — drop the hamburger
Replace Menu.svelte (hamburger) everywhere with tab-bar navigation:
- Settings hub (SettingsHub) from the lobby ⚙️ tab: Settings/Profile/
  Friends/About as in-place tabs, back → lobby; the lobby ⚙️ badge counts
  incoming friend requests (invitations keep their own lobby section).
- Comms hub (CommsHub) from the move-history 💬: Chat/Dictionary tabs,
  back → game; Dictionary only while the game is active.
- Game menu items relocate into the open history: 🏁 leave / 📤 export in
  the header, 🤝 add-friend per opponent card, 💬 comms; unread chat is
  badged on the score bar + the 💬.
- TapConfirm (tap → fading  → tap) replaces the Skip/Hint press-and-hold
  popovers and drives the add-friend confirm.
- Fix the move-history "jump": the slid board is inert and the stage can't
  scroll, so a swipe up genuinely closes the history.

Remove Menu.svelte + HoldConfirm.svelte. Docs: UI_DESIGN, FUNCTIONAL(+ru),
PRERELEASE. UI check/unit/build/bundle/e2e (Chromium+WebKit) all green.
2026-06-11 14:13:54 +02:00

65 lines
2.5 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import Screen from '../components/Screen.svelte';
import TabBar from '../components/TabBar.svelte';
import Settings from './Settings.svelte';
import Profile from './Profile.svelte';
import Friends from './Friends.svelte';
import About from './About.svelte';
import { app } from '../lib/app.svelte';
import { t, type MessageKey } from '../lib/i18n/index.svelte';
// The Settings hub: a single nav bar + bottom tab bar hosting the Settings / Profile /
// Friends / About bodies. Tabs switch in place (no navigation), so the back control
// always returns to the lobby. Guests have no social surface, so the Friends tab hides.
type SettingsTab = 'settings' | 'profile' | 'friends' | 'about';
let { initialTab = 'settings' }: { initialTab?: SettingsTab } = $props();
const guest = $derived(app.profile?.isGuest ?? true);
// The active tab is seeded once from the entry route's tab and then owned locally;
// the hub is keyed by route in App.svelte, so initialTab is constant for its lifetime.
// svelte-ignore state_referenced_locally
let tab = $state<SettingsTab>(initialTab);
// A guest who deep-links to the Friends tab falls back to Settings.
$effect(() => {
if (guest && tab === 'friends') tab = 'settings';
});
const titleKey: Record<SettingsTab, MessageKey> = {
settings: 'settings.title',
profile: 'profile.title',
friends: 'friends.title',
about: 'about.title',
};
</script>
<Screen title={t(titleKey[tab])} back="/">
{#if tab === 'settings'}
<Settings />
{:else if tab === 'profile'}
<Profile />
{:else if tab === 'friends'}
<Friends />
{:else}
<About />
{/if}
{#snippet tabbar()}
<TabBar>
<button class="tab" class:active={tab === 'settings'} onclick={() => (tab = 'settings')} aria-label={t('settings.title')}>
<span class="sq">⚙️</span>
</button>
<button class="tab" class:active={tab === 'profile'} onclick={() => (tab = 'profile')} aria-label={t('profile.title')}>
<span class="sq">👤</span>
</button>
{#if !guest}
<button class="tab" class:active={tab === 'friends'} onclick={() => (tab = 'friends')} aria-label={t('friends.title')}>
<span class="sq">🤝{#if app.notifications > 0}<span class="badge">{app.notifications}</span>{/if}</span>
</button>
{/if}
<button class="tab" class:active={tab === 'about'} onclick={() => (tab = 'about')} aria-label={t('about.title')}>
<span class="sq"></span>
</button>
</TabBar>
{/snippet}
</Screen>