From 17525763741450f59df1d7b22e62d7ff41b9dd1c Mon Sep 17 00:00:00 2001 From: jason5ng32 Date: Wed, 27 May 2026 01:10:35 +0800 Subject: [PATCH 01/52] Feat(mobile): scroll-hide nav, iOS PWA safe-area painter, theme refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nav slides up on scroll-down / down on scroll-up on mobile, with threshold + top-of-page protection - iOS PWA: apple-mobile-web-app-status-bar-style=black-translucent + fixed-position safe-area painter in Nav, driven by --page-bg CSS var. Works around WebKit's refusal to honor JS theme-color updates and media-variant theme-color tags in standalone PWA mode. - Sheet + Drawer primitives now respect safe-area-inset via inline padding (top/left/right get inset-top; bottom gets inset-bottom) - Extract theme orchestration from Preferences.vue into use-theme.js composable mounted at App root — Preferences is now pure UI - Align page-bg color literals across style.css (--page-bg), index.html (html bg + meta theme-color), and manifest.webmanifest Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/App.vue | 4 + frontend/components/Nav.vue | 78 ++++++++++++++++--- .../components/ui/drawer/DrawerContent.vue | 1 + frontend/components/ui/sheet/SheetContent.vue | 21 +++++ frontend/components/widgets/Preferences.vue | 57 +------------- frontend/composables/use-theme.js | 35 +++++++++ frontend/style/style.css | 6 ++ index.html | 22 ++++-- public/manifest.webmanifest | 2 +- 9 files changed, 153 insertions(+), 73 deletions(-) create mode 100644 frontend/composables/use-theme.js diff --git a/frontend/App.vue b/frontend/App.vue index 3773154e1..64c16f385 100644 --- a/frontend/App.vue +++ b/frontend/App.vue @@ -60,6 +60,7 @@ import { useInfoMask } from '@/composables/use-info-mask.js'; import { useRefreshOrchestrator } from '@/composables/use-refresh-orchestrator.js'; import { useShortcuts } from '@/composables/use-shortcuts.js'; import { useSectionTracking } from '@/composables/use-section-tracking.js'; +import { useTheme } from '@/composables/use-theme.js'; const { t } = useI18n(); const store = useMainStore(); @@ -137,6 +138,9 @@ const { loadShortcuts } = useShortcuts({ // Scroll monitoring + section tracking (logic from widgets/Patch.vue) useSectionTracking(); +// Theme orchestration: initial apply, OS flip listener, preference watcher. +useTheme(); + onMounted(() => { loadingControl(); loadShortcuts(); diff --git a/frontend/components/Nav.vue b/frontend/components/Nav.vue index 242770f51..cf7ebb1d2 100644 --- a/frontend/components/Nav.vue +++ b/frontend/components/Nav.vue @@ -1,7 +1,18 @@ diff --git a/frontend/components/ui/drawer/DrawerContent.vue b/frontend/components/ui/drawer/DrawerContent.vue index f31b1b3f9..fe817ced2 100644 --- a/frontend/components/ui/drawer/DrawerContent.vue +++ b/frontend/components/ui/drawer/DrawerContent.vue @@ -25,6 +25,7 @@ defineEmits(['close', 'escapeKeyDown']); 'fixed inset-x-0 bottom-0 z-[10001] mt-24 flex h-auto flex-col rounded-t-[14px] border bg-background shadow-lg outline-none', props.class, )" + :style="{ paddingBottom: 'env(safe-area-inset-bottom)' }" @escape-key-down="$emit('escapeKeyDown', $event)" > // shadcn-vue Sheet 面板:四向滑入,包含 overlay // 见 refactor/01 阶段 B +import { computed } from 'vue'; import { DialogContent, DialogOverlay, DialogPortal, DialogTitle, VisuallyHidden } from 'reka-ui'; import { cva } from 'class-variance-authority'; import { cn } from '@/lib/utils'; @@ -30,6 +31,25 @@ const props = defineProps({ onInteractOutside: { type: Function, default: undefined }, }); defineEmits(['close', 'escapeKeyDown']); + +// Push content past the iOS safe-area insets so it doesn't render +// behind the transparent status bar / home indicator. Inline style +// so consumers' `p-0` doesn't override it. +const safeAreaStyle = computed(() => { + switch (props.side) { + case 'top': + return { paddingTop: 'env(safe-area-inset-top)' }; + case 'bottom': + return { paddingBottom: 'env(safe-area-inset-bottom)' }; + case 'left': + case 'right': + default: + return { + paddingTop: 'env(safe-area-inset-top)', + paddingBottom: 'env(safe-area-inset-bottom)', + }; + } +});