style(appShell): 优化应用外壳及底部导航样式与结构

- 在 tailwind 配置中添加应用外壳相关的 safelist 类名
- 替换 App.tsx 中应用外壳容器的类名,去除多余的内边距容器
- 在现代和遗留样式文件中新增移动端全屏、桌面居中列的样式规则
- 新增底部导航外层和内层的样式及响应式布局支持
- appShell.ts 中将样式类改为统一的 x8- 前缀类名
- BottomNav 组件使用新的样式类名以匹配样式调整
- Home 页面 ListRow 组件支持可选缩略图展示,增加界面展示丰富度
This commit is contained in:
eafonyang
2026-06-03 11:52:57 +08:00
parent 28aa90296f
commit ccc404b75a
7 changed files with 156 additions and 15 deletions
+3 -5
View File
@@ -22,7 +22,7 @@ import SelectPage from "./pages/SelectPage";
import CommunityPage from "./pages/CommunityPage";
import { AIDrawerProvider } from "./contexts/AIDrawerContext";
import DesktopAIShell from "./components/DesktopAIShell";
import { appShellColumnClass, appShellGutterClass } from "./lib/appShell";
import { appShellColumnClass, appShellOuterClass } from "./lib/appShell";
const EQPage = lazy(() => import("./pages/EQPage"));
const AIPage = lazy(() => import("./pages/AIPage"));
@@ -75,10 +75,8 @@ function App() {
<Toaster />
<Router hook={useHashLocation}>
<AIDrawerProvider>
<div
className={`min-h-dvh bg-[#0d1117] flex justify-center ${appShellGutterClass}`}
>
<div className={`min-h-dvh ${appShellColumnClass}`}>
<div className={appShellOuterClass}>
<div className={appShellColumnClass}>
<DesktopAIShell>
<AppRouter />
</DesktopAIShell>
+3 -3
View File
@@ -22,7 +22,7 @@ import {
} from "lucide-react";
import { Link, useLocation } from "wouter";
import { useMemo } from "react";
import { appShellColumnClass, appShellGutterClass } from "@/lib/appShell";
import { bottomNavInnerClass, bottomNavOuterClass } from "@/lib/appShell";
type BottomNavLocale = NonNullable<(typeof localeZh)["bottomNav"]>;
@@ -57,10 +57,10 @@ export default function BottomNav() {
return (
<nav
className={`fixed inset-x-0 bottom-0 z-50 flex justify-center pointer-events-none ${appShellGutterClass}`}
className={bottomNavOuterClass}
>
<div
className={`pointer-events-auto ${appShellColumnClass}`}
className={bottomNavInnerClass}
style={{
background: "rgba(10, 10, 12, 0.96)",
backdropFilter: "blur(40px) saturate(200%)",
+57
View File
@@ -119,6 +119,8 @@
html,
body,
#root {
width: 100%;
max-width: 100%;
min-height: 100%;
}
@@ -176,6 +178,61 @@
min-height: 0;
min-width: 0;
}
/* App shell: mobile full-bleed, desktop centered column (modern + legacy share class names). */
.x8-app-shell-outer {
min-height: 100dvh;
width: 100%;
min-width: 0;
display: flex;
justify-content: center;
background-color: #0d1117;
padding-left: 0;
padding-right: 0;
}
.x8-app-shell-column {
min-height: 100dvh;
width: 100%;
min-width: 0;
max-width: none;
}
.x8-bottom-nav-outer {
pointer-events: none;
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 50;
display: flex;
width: 100%;
min-width: 0;
justify-content: center;
padding-left: 0;
padding-right: 0;
}
.x8-bottom-nav-inner {
pointer-events: auto;
width: 100%;
min-width: 0;
max-width: none;
}
@media (min-width: 1024px) {
.x8-app-shell-outer,
.x8-bottom-nav-outer {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.x8-app-shell-column,
.x8-bottom-nav-inner {
max-width: 1180px;
}
}
@media (min-width: 640px) {
.container {
padding-left: 1.5rem;
+69
View File
@@ -6,9 +6,22 @@
html,
body,
#root {
width: 100%;
max-width: 100%;
height: 100%;
}
@media (max-width: 1023px) {
html,
body {
overflow-x: hidden;
overscroll-behavior-x: none;
}
#root {
overflow-x: hidden;
}
}
body {
background: #000;
color: #f2f2f2;
@@ -44,6 +57,62 @@ body {
}
@layer components {
/* App shell: mobile full-bleed, desktop centered column (plain CSS for Chrome 91). */
.x8-app-shell-outer {
min-height: 100vh;
min-height: -webkit-fill-available;
width: 100%;
min-width: 0;
display: flex;
justify-content: center;
background-color: #0d1117;
padding-left: 0;
padding-right: 0;
}
.x8-app-shell-column {
min-height: 100vh;
min-height: -webkit-fill-available;
width: 100%;
min-width: 0;
max-width: none;
}
.x8-bottom-nav-outer {
pointer-events: none;
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 50;
display: flex;
width: 100%;
min-width: 0;
justify-content: center;
padding-left: 0;
padding-right: 0;
}
.x8-bottom-nav-inner {
pointer-events: auto;
width: 100%;
min-width: 0;
max-width: none;
}
@media (min-width: 1024px) {
.x8-app-shell-outer,
.x8-bottom-nav-outer {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.x8-app-shell-column,
.x8-bottom-nav-inner {
max-width: 1180px;
}
}
/* Match modern .cyan-slider styling (input[type=range]) for legacy build. */
.cyan-slider {
-webkit-appearance: none;
+5 -5
View File
@@ -1,8 +1,8 @@
/** Centered phone-style shell width (desktop / ultrawide). */
export const APP_SHELL_MAX_PX = 1180;
/** Inner column inside horizontal gutters — use with a flex/grid centering parent. */
export const appShellColumnClass =
"w-full min-w-0 max-w-[min(1180px,100%)]";
export const appShellGutterClass = "px-4 sm:px-6";
/** Responsive rules live in `.x8-app-shell-*` (index.css + index.legacy.css). */
export const appShellOuterClass = "x8-app-shell-outer";
export const appShellColumnClass = "x8-app-shell-column";
export const bottomNavOuterClass = "x8-bottom-nav-outer";
export const bottomNavInnerClass = "x8-bottom-nav-inner";
+13 -2
View File
@@ -135,7 +135,7 @@ function IOSToggle({ checked, onChange }: { checked: boolean; onChange: (v: bool
// ── List row ──
function ListRow({
icon, label, value, onClick, toggle, checked, onToggle,
icon, label, value, onClick, toggle, checked, onToggle, thumbnailSrc,
}: {
icon: React.ReactNode;
label: string;
@@ -144,6 +144,8 @@ function ListRow({
toggle?: boolean;
checked?: boolean;
onToggle?: (v: boolean) => void;
/** Optional small thumbnail shown between value text and chevron. */
thumbnailSrc?: string;
}) {
return (
<div className="ios-list-row cursor-pointer" onClick={onClick}>
@@ -157,7 +159,15 @@ function ListRow({
{toggle ? (
<IOSToggle checked={!!checked} onChange={onToggle ?? (() => {})} />
) : (
<div className="flex items-center gap-1">
<div className="flex items-center gap-1.5">
{thumbnailSrc && (
<img
src={thumbnailSrc}
alt=""
className="w-16 h-12 rounded-[8px] object-cover flex-shrink-0"
style={{ border: "1px solid rgba(255,255,255,0.12)" }}
/>
)}
{value && <span className="ios-row-value">{value}</span>}
<ChevronRight size={16} className="ios-chevron" />
</div>
@@ -873,6 +883,7 @@ export default function Home() {
icon={<Gauge size={16} />}
label={homeText.vu ?? "VU表"}
value={vuLabel}
thumbnailSrc={`${import.meta.env.BASE_URL}vu/vu${(ds.vu ?? 0) + 1}.png`}
onClick={() => setLocation("/vu")}
/>
<ListRow
+6
View File
@@ -29,5 +29,11 @@ module.exports = {
},
},
plugins: [],
safelist: [
"x8-app-shell-outer",
"x8-app-shell-column",
"x8-bottom-nav-outer",
"x8-bottom-nav-inner",
],
};