@@ -12,7 +12,7 @@ import { RegionSelect } from '@/components/sidebar/RegionSelect'
1212import { CardList } from '@/components/sidebar/CardList'
1313import { useUIStore } from '@/store/useUIStore'
1414import { useFilteredLocations } from '@/hooks/useFilteredLocations'
15- import { Menu , X } from 'lucide-react'
15+ import { ChevronRight , List , Menu , X } from 'lucide-react'
1616import { cn } from '@/utils/cn'
1717
1818interface MobileLayoutProps {
@@ -24,15 +24,15 @@ export function MobileLayout({ locations }: MobileLayoutProps) {
2424 const setSidebarOpen = useUIStore ( s => s . setSidebarOpen )
2525 const { filteredLocations, regionList } = useFilteredLocations ( )
2626
27- // Bug 1: Lock body scroll when drawer is open
27+ // 抽屉打开时锁定背景滚动
2828 useEffect ( ( ) => {
2929 if ( sidebarOpen ) {
3030 document . body . style . overflow = 'hidden'
3131 return ( ) => { document . body . style . overflow = '' }
3232 }
3333 } , [ sidebarOpen ] )
3434
35- // Bug 4: Close drawer on Escape key
35+ // Esc 关闭抽屉
3636 useEffect ( ( ) => {
3737 const handleKeyDown = ( e : KeyboardEvent ) => {
3838 if ( e . key === 'Escape' && sidebarOpen ) {
@@ -52,93 +52,122 @@ export function MobileLayout({ locations }: MobileLayoutProps) {
5252 </ MapView >
5353
5454 { /* 地图控件 */ }
55- < MapControls className = "absolute top-16 right-3 z-[999]" />
56-
57- { /* 顶部搜索栏 — 侧边栏打开时不渲染 */ }
55+ < MapControls
56+ className = "absolute right-3 z-[999]"
57+ style = { { top : 'calc(env(safe-area-inset-top, 0px) + 64px)' } }
58+ />
5859 { ! sidebarOpen && (
5960 < div className = "absolute top-0 left-0 right-0 z-[1000] p-2 pt-safe pointer-events-none" >
6061 < div className = "flex items-center gap-2 pointer-events-auto" >
6162 < button
63+ type = "button"
6264 onClick = { ( ) => setSidebarOpen ( true ) }
63- className = "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl glass shadow-soft border border-[var(--color-border)] active:scale-95 transition-transform"
65+ className = "flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl glass border border-[var(--color-border)] shadow-soft transition-[ transform,box-shadow] duration-200 active:scale-95 "
6466 aria-label = "打开菜单"
6567 >
6668 < Menu className = "h-5 w-5 text-[var(--color-text-dim)]" />
6769 </ button >
68- < div className = "flex-1 glass rounded-xl shadow-soft border border-[var(--color-border)] pointer-events-auto" >
70+ < div className = "min-w-0 flex-1 rounded-2xl glass border border-[var(--color-border)] shadow-soft pointer-events-auto" >
6971 < SearchBar />
7072 </ div >
7173 </ div >
7274 </ div >
7375 ) }
7476
75- { /* 遮罩 — 点击关闭抽屉 */ }
77+ { /* 遮罩 */ }
7678 < div
7779 role = "dialog"
7880 aria-label = "关闭菜单"
7981 className = { cn (
80- 'fixed inset-0 z-40 bg-black/40 transition-opacity duration-300' ,
82+ 'fixed inset-0 z-40 bg-slate-900/45 backdrop-blur-[2px] transition-opacity duration-300' ,
8183 sidebarOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'
8284 ) }
8385 onClick = { ( ) => setSidebarOpen ( false ) }
8486 />
8587
86- { /* 左侧滑出抽屉 — 弹簧动画 */ }
88+ { /* 左侧滑出抽屉 */ }
8789 < div
8890 className = { cn (
89- 'fixed top-0 left-0 bottom-0 z-50 w-[85vw] max-w-[360px]' ,
90- 'sidebar-gradient flex flex-col' ,
91- 'shadow-xl'
91+ 'sidebar-gradient fixed top-0 left-0 bottom-0 z-50 flex w-[88vw] max-w-[400px] flex-col' ,
92+ 'overflow-hidden rounded-r-[28px] border-r border-white/80 shadow-xl'
9293 ) }
9394 style = { {
94- transform : sidebarOpen ? 'translateX(0)' : 'translateX(-100 %)' ,
95- transition : 'transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)' ,
95+ transform : sidebarOpen ? 'translateX(0)' : 'translateX(-104 %)' ,
96+ transition : 'transform 0.38s cubic-bezier(0.34, 1.56, 0.64, 1)' ,
9697 willChange : 'transform' ,
98+ pointerEvents : sidebarOpen ? 'auto' : 'none' ,
9799 paddingTop : 'env(safe-area-inset-top, 0px)' ,
98100 paddingBottom : 'env(safe-area-inset-bottom, 0px)' ,
99101 } }
100102 >
101- { /* Header — compact single line */ }
102- < div className = "header-gradient shrink-0 px-4 py-3 text-white flex items-center justify-between" >
103- < div className = "flex items-center gap-2" >
104- < h1 className = "text-lg font-bold" > 🗾 日本动漫店铺地图</ h1 >
105- < span className = "inline-flex items-center rounded-full bg-white/20 px-2 py-0.5 text-xs" >
106- 📍 { locations . length }
107- </ span >
103+ { /* 品牌头部 */ }
104+ < div className = "header-gradient relative shrink-0 px-4 pb-3.5 pt-4 text-white" >
105+ < div className = "relative z-10 flex items-center justify-between gap-2" >
106+ < div className = "flex min-w-0 items-center gap-2.5" >
107+ < div className = "flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-white/20 text-[17px] shadow-[inset_0_1px_0_rgba(255,255,255,0.35)]" >
108+ 🗾
109+ </ div >
110+ < div className = "min-w-0" >
111+ < h1 className = "truncate text-[15px] font-extrabold tracking-tight" >
112+ 日本动漫店铺地图
113+ </ h1 >
114+ < p className = "mt-0.5 truncate text-[9px] font-semibold uppercase tracking-[0.16em] text-white/60" >
115+ Otaku Store Map · { locations . length } stores
116+ </ p >
117+ </ div >
118+ </ div >
119+ < button
120+ type = "button"
121+ onClick = { ( ) => setSidebarOpen ( false ) }
122+ className = "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-white/20 transition-[background-color,transform] duration-200 hover:bg-white/35 active:scale-90"
123+ aria-label = "关闭"
124+ >
125+ < X className = "h-4 w-4" />
126+ </ button >
108127 </ div >
109- < button
110- type = "button"
111- onClick = { ( ) => setSidebarOpen ( false ) }
112- className = "flex h-8 w-8 items-center justify-center rounded-full bg-white/20 hover:bg-white/30 active:scale-90 transition-all"
113- aria-label = "关闭"
114- >
115- < X className = "h-4 w-4" />
116- </ button >
117128 </ div >
118129
119130 { /* 搜索 + 筛选 + 地区 */ }
120- < div className = "shrink-0 space-y-3 px-3 py-3 glass border-b border-[var(--color-border)]" >
121- { /* SearchBar + SortPopover 同行 */ }
131+ < div className = "shrink-0 space-y-2.5 border-b border-[var(--color-border)] bg-white/55 px-3 py-3" >
122132 < div className = "flex items-center gap-2" >
123- < div className = "flex-1" >
133+ < div className = "min-w-0 flex-1" >
124134 < SearchBar />
125135 </ div >
126136 < SortPopover />
127137 </ div >
128-
129- { /* FilterPanel — compact dots */ }
130- < FilterPanel />
131-
132- { /* RegionSelect — 地区下拉 */ }
133- < RegionSelect regionList = { regionList } / >
138+ < div className = "flex items-center gap-2" >
139+ < div className = "min-w-0 flex-1" >
140+ < FilterPanel locations = { locations } />
141+ </ div >
142+ < RegionSelect regionList = { regionList } />
143+ </ div >
134144 </ div >
135145
136146 { /* 列表 */ }
137- < div className = "flex-1 overflow-y-auto px-3 pt-2 pb-safe" >
147+ < div className = "flex-1 overflow-y-auto overscroll-contain px-3 pt-3 pb-safe" >
138148 < CardList locations = { filteredLocations } total = { locations . length } />
139149 </ div >
140150 </ div >
141151
152+ { /* 底部结果栏 — 提升列表入口的可见性 */ }
153+ { ! sidebarOpen && (
154+ < button
155+ type = "button"
156+ onClick = { ( ) => setSidebarOpen ( true ) }
157+ className = { cn (
158+ 'absolute left-3 right-[92px] z-[998] flex h-12 items-center justify-center gap-1.5' ,
159+ 'rounded-2xl glass border border-[var(--color-border)] shadow-elevated' ,
160+ 'text-[12.5px] font-semibold text-[var(--color-text)]' ,
161+ 'transition-[transform,box-shadow] duration-200 active:scale-[0.97]'
162+ ) }
163+ style = { { bottom : 'calc(env(safe-area-inset-bottom, 0px) + 14px)' } }
164+ >
165+ < List className = "h-4 w-4 text-[var(--color-accent)]" />
166+ { filteredLocations . length } /{ locations . length } 个地点
167+ < ChevronRight className = "h-3.5 w-3.5 text-[var(--color-text-dim)]" />
168+ </ button >
169+ ) }
170+
142171 { /* 导航面板 — 底部 Sheet */ }
143172 < NavigationPanel />
144173 </ div >
0 commit comments