Stage 17 (contour round 3): Telegram Mini Apps polish, board scroll, keyboard overlay
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 10s
CI / ui (pull_request) Successful in 27s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 54s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 10s
CI / ui (pull_request) Successful in 27s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 54s
- Telegram (lib/telegram.ts): chrome colours (setHeaderColor/setBackgroundColor/setBottomBarColor) match Telegram's header/bg/bottom bar to the app; native BackButton on sub-screens (app chevron hidden in TG); HapticFeedback on tile place/commit/error; enableClosingConfirmation while a game is open; disableVerticalSwipes so swipe-to-minimise doesn't fight tile drag / board scroll - #9 board-only vertical scroll: Screen 'column' mode lets the board area scroll while score/status/rack/tab bar stay fixed (zoom keeps its own scroll) - #10 check-word dialog opens in Modal keyboard-overlay mode (top-anchored, keyboard overlays the empty area) — no resize/relayout jank; other modals stay keyboard-aware - docs: UI_DESIGN Telegram integration + vertical fit/keyboard; PLAN round 2-3 follow-ups
This commit is contained in:
@@ -1,14 +1,19 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import { navigate } from '../lib/router.svelte';
|
||||
import { insideTelegram } from '../lib/telegram';
|
||||
|
||||
let { title, back, menu, grow = false }: { title: string; back?: string; menu?: Snippet; grow?: boolean } =
|
||||
$props();
|
||||
|
||||
// Inside Telegram the native header back button (App.svelte) is the back control, so
|
||||
// the app's own chevron is hidden to avoid two back affordances.
|
||||
const showBack = $derived(!!back && !insideTelegram());
|
||||
</script>
|
||||
|
||||
<header class="nav" class:grow>
|
||||
<div class="bar">
|
||||
{#if back}
|
||||
{#if showBack}
|
||||
<button class="icon back" onclick={() => back && navigate(back)} aria-label="Back">
|
||||
<span class="chev"></span>
|
||||
</button>
|
||||
|
||||
@@ -4,18 +4,21 @@
|
||||
let {
|
||||
title = '',
|
||||
onclose,
|
||||
overlayKeyboard = false,
|
||||
children,
|
||||
}: { title?: string; onclose?: () => void; children?: Snippet } = $props();
|
||||
}: { title?: string; onclose?: () => void; overlayKeyboard?: boolean; children?: Snippet } = $props();
|
||||
|
||||
// Track the visual viewport so the backdrop covers only the area above an open
|
||||
// mobile keyboard: dvh alone shrinks the sheet but the fixed, layout-viewport
|
||||
// backdrop still centres it behind the keyboard. Sizing the backdrop to
|
||||
// visualViewport keeps the sheet (and the start of a chat) fully on screen.
|
||||
// overlayKeyboard opts out: the sheet is small and top-anchored, so the keyboard
|
||||
// simply overlays the empty lower area — no resize, no relayout jank (e.g. check word).
|
||||
let vh = $state(0);
|
||||
let top = $state(0);
|
||||
$effect(() => {
|
||||
const vv = typeof window !== 'undefined' ? window.visualViewport : null;
|
||||
if (!vv) return;
|
||||
if (!vv || overlayKeyboard) return;
|
||||
const update = () => {
|
||||
vh = vv.height;
|
||||
top = vv.offsetTop;
|
||||
@@ -34,6 +37,7 @@
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="backdrop"
|
||||
class:overlay={overlayKeyboard}
|
||||
style:height={vh ? `${vh}px` : null}
|
||||
style:top={vh ? `${top}px` : null}
|
||||
onclick={() => onclose?.()}
|
||||
@@ -61,6 +65,12 @@
|
||||
padding: 16px;
|
||||
z-index: 40;
|
||||
}
|
||||
/* Overlay mode: top-anchor the (small) sheet and don't track the keyboard, so the
|
||||
soft keyboard overlays the empty lower area without resizing/relaying out. */
|
||||
.backdrop.overlay {
|
||||
align-items: flex-start;
|
||||
padding-top: 12vh;
|
||||
}
|
||||
.sheet {
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
children,
|
||||
scroll = true,
|
||||
growNav = false,
|
||||
column = false,
|
||||
}: {
|
||||
title: string;
|
||||
back?: string;
|
||||
@@ -22,13 +23,16 @@
|
||||
children?: Snippet;
|
||||
scroll?: boolean;
|
||||
growNav?: boolean;
|
||||
// column lays the content out as a flex column so a child can own the vertical fit
|
||||
// (the game makes only its board scroll while the score/rack/tab bar stay put).
|
||||
column?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="screen">
|
||||
<Header {title} {back} {menu} grow={growNav} />
|
||||
<AdBanner />
|
||||
<main class="content" class:scroll class:fill={!growNav}>{@render children?.()}</main>
|
||||
<main class="content" class:scroll class:fill={!growNav} class:column>{@render children?.()}</main>
|
||||
{#if tabbar}
|
||||
<nav class="tabbar">{@render tabbar()}</nav>
|
||||
{/if}
|
||||
@@ -50,6 +54,10 @@
|
||||
.content.scroll {
|
||||
overflow-y: auto;
|
||||
}
|
||||
.content.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.tabbar {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user