feat(ui): 全站画布氛围层、侧栏 sticky 对齐与长页回顶
用顶栏/主栏高度令牌消除 sticky 首滚空走,面包屑叠到中栏上方,公开页增加回顶浮钮。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -28,7 +28,6 @@ export default async function AdminLayout({ children }: { children: React.ReactN
|
|||||||
id="main"
|
id="main"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
className="min-h-screen flex items-center justify-center px-4 outline-none"
|
className="min-h-screen flex items-center justify-center px-4 outline-none"
|
||||||
style={{ background: "var(--bg)" }}
|
|
||||||
>
|
>
|
||||||
<div className="max-w-md w-full panel p-8 text-center">
|
<div className="max-w-md w-full panel p-8 text-center">
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -103,6 +103,15 @@ export default async function AnnouncementPage({ params }: PageProps) {
|
|||||||
<SiteDocShell
|
<SiteDocShell
|
||||||
left={left}
|
left={left}
|
||||||
right={right}
|
right={right}
|
||||||
|
breadcrumb={
|
||||||
|
<SiteDocBreadcrumb
|
||||||
|
items={[
|
||||||
|
{ href: "/", label: "首页" },
|
||||||
|
{ href: "/announcements", label: "站点公告" },
|
||||||
|
{ label: a.title },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
mobileExtras={
|
mobileExtras={
|
||||||
<>
|
<>
|
||||||
{toc.length > 0 ? <PostToc items={toc} /> : null}
|
{toc.length > 0 ? <PostToc items={toc} /> : null}
|
||||||
@@ -111,14 +120,6 @@ export default async function AnnouncementPage({ params }: PageProps) {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SiteDocBreadcrumb
|
|
||||||
items={[
|
|
||||||
{ href: "/", label: "首页" },
|
|
||||||
{ href: "/announcements", label: "站点公告" },
|
|
||||||
{ label: a.title },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<article className="panel p-6 sm:p-8">
|
<article className="panel p-6 sm:p-8">
|
||||||
<SiteDocMasthead
|
<SiteDocMasthead
|
||||||
kicker="站点公告"
|
kicker="站点公告"
|
||||||
|
|||||||
@@ -132,15 +132,16 @@ export default async function AnnouncementsListPage({ searchParams }: PageProps)
|
|||||||
<SiteDocShell
|
<SiteDocShell
|
||||||
left={left}
|
left={left}
|
||||||
right={right}
|
right={right}
|
||||||
|
breadcrumb={
|
||||||
|
<SiteDocBreadcrumb
|
||||||
|
items={[
|
||||||
|
{ href: "/", label: "首页" },
|
||||||
|
{ label: "站点公告" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
mobileExtras={<SiteDocPageNav pages={sidebarPages} title="关于本站" />}
|
mobileExtras={<SiteDocPageNav pages={sidebarPages} title="关于本站" />}
|
||||||
>
|
>
|
||||||
<SiteDocBreadcrumb
|
|
||||||
items={[
|
|
||||||
{ href: "/", label: "首页" },
|
|
||||||
{ label: "站点公告" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<header className="mb-5">
|
<header className="mb-5">
|
||||||
<h1
|
<h1
|
||||||
className="text-[24px] sm:text-[28px] font-extrabold tracking-tight flex items-center gap-2"
|
className="text-[24px] sm:text-[28px] font-extrabold tracking-tight flex items-center gap-2"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ function ComposeInner() {
|
|||||||
export default function ComposePage() {
|
export default function ComposePage() {
|
||||||
return (
|
return (
|
||||||
<div className="j13-compose-page">
|
<div className="j13-compose-page">
|
||||||
<nav className="meta mb-4 flex items-center gap-1.5 text-[13px] shrink-0">
|
<nav className="meta -mt-4 sm:-mt-6 mb-1.5 flex items-center gap-1 text-[12.5px] leading-none shrink-0">
|
||||||
<Link href="/" className="hover:text-[var(--accent)] transition-colors">
|
<Link href="/" className="hover:text-[var(--accent)] transition-colors">
|
||||||
首页
|
首页
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -56,6 +56,14 @@
|
|||||||
--r: 18px;
|
--r: 18px;
|
||||||
--r-sm: 12px;
|
--r-sm: 12px;
|
||||||
|
|
||||||
|
/* 顶栏 68px 条 + 1px 底边;main 桌面 py-9。侧栏 sticky 必须等于
|
||||||
|
栏在 scrollY=0 时的视口 top,否则第一下滚动会先空走一截再吸住 */
|
||||||
|
--header-bar: 68px;
|
||||||
|
--header-h: calc(var(--header-bar) + 1px);
|
||||||
|
--main-pad-y: 2.25rem;
|
||||||
|
--rail-top: calc(var(--header-h) + var(--main-pad-y));
|
||||||
|
--rail-max-h: calc(100vh - var(--rail-top) - 1rem);
|
||||||
|
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +111,7 @@
|
|||||||
|
|
||||||
html {
|
html {
|
||||||
scrollbar-color: var(--line-2) transparent;
|
scrollbar-color: var(--line-2) transparent;
|
||||||
|
background-color: var(--bg); /* 橡皮筋 overscroll 与画布同色 */
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -113,6 +122,59 @@ body {
|
|||||||
-webkit-font-smoothing: auto;
|
-webkit-font-smoothing: auto;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
font-synthesis: none; /* 禁止伪粗,避免缺档字重糊成一团 */
|
font-synthesis: none; /* 禁止伪粗,避免缺档字重糊成一团 */
|
||||||
|
isolation: isolate; /* 让 z-index:-1 的画布层停在内容之下、html 之上 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 浏览器最后一层画布:氛围光 + 石墨微粒。
|
||||||
|
纯 CSS(令牌 + 内联 SVG),不跟位图——跟浅/暗/accent 换肤,无限缩放,无额外请求。 */
|
||||||
|
body::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(ellipse 80% 52% at 12% -18%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 62%),
|
||||||
|
radial-gradient(ellipse 48% 36% at 100% 0%, color-mix(in srgb, var(--ink) 4%, transparent), transparent 58%),
|
||||||
|
radial-gradient(ellipse 42% 28% at 78% 100%, color-mix(in srgb, var(--accent) 6%, transparent), transparent 60%),
|
||||||
|
linear-gradient(180deg, color-mix(in srgb, var(--panel) 40%, transparent) 0%, transparent 32%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body::after {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.032;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 160' width='160' height='160'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||||
|
background-size: 160px 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark body::before {
|
||||||
|
background-image:
|
||||||
|
radial-gradient(ellipse 80% 52% at 12% -18%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 62%),
|
||||||
|
radial-gradient(ellipse 48% 36% at 100% 0%, color-mix(in srgb, var(--ink) 6%, transparent), transparent 58%),
|
||||||
|
radial-gradient(ellipse 42% 28% at 78% 100%, color-mix(in srgb, var(--accent) 9%, transparent), transparent 60%),
|
||||||
|
linear-gradient(180deg, color-mix(in srgb, var(--panel) 22%, transparent) 0%, transparent 36%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark body::after {
|
||||||
|
opacity: 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body::before,
|
||||||
|
body::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-contrast: more) {
|
||||||
|
body::before,
|
||||||
|
body::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 全站悬停即显提示气泡(配合 TipHost + data-tip,替代原生 title 延迟) */
|
/* 全站悬停即显提示气泡(配合 TipHost + data-tip,替代原生 title 延迟) */
|
||||||
@@ -2319,7 +2381,6 @@ body:has([data-admin-viewport]) {
|
|||||||
height: 100dvh;
|
height: 100dvh;
|
||||||
max-height: 100dvh;
|
max-height: 100dvh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--bg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-shell {
|
.admin-shell {
|
||||||
@@ -2328,7 +2389,7 @@ body:has([data-admin-viewport]) {
|
|||||||
max-height: 100dvh;
|
max-height: 100dvh;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--bg);
|
background: transparent; /* 透出全站画布层 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-rail {
|
.admin-rail {
|
||||||
@@ -5136,6 +5197,58 @@ html.dark .prose-content pre[data-theme] span {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 长文回顶:右下角浮钮 */
|
||||||
|
.j13-back-top {
|
||||||
|
position: fixed;
|
||||||
|
right: 1rem;
|
||||||
|
bottom: 1.25rem;
|
||||||
|
z-index: 45;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2.75rem;
|
||||||
|
height: 2.75rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--line-2);
|
||||||
|
background: color-mix(in srgb, var(--panel) 92%, transparent);
|
||||||
|
color: var(--ink-2);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
backdrop-filter: blur(14px);
|
||||||
|
-webkit-backdrop-filter: blur(14px);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
.j13-back-top:hover,
|
||||||
|
.j13-back-top:focus-visible {
|
||||||
|
color: var(--accent);
|
||||||
|
background: var(--accent-soft);
|
||||||
|
border-color: color-mix(in srgb, var(--accent) 28%, var(--line-2));
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
/* 帖子移动端底栏上方抬高,避免遮挡 */
|
||||||
|
.j13-back-top.is-lifted {
|
||||||
|
bottom: 5.25rem;
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.j13-back-top.is-lifted {
|
||||||
|
bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.j13-back-top {
|
||||||
|
right: 1.35rem;
|
||||||
|
bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.j13-back-top.is-lifted {
|
||||||
|
bottom: 5.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.j13-back-top.is-lifted {
|
||||||
|
bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 评论楼层树:抖音风格
|
/* 评论楼层树:抖音风格
|
||||||
主评论全宽;子回复折叠在 panel-2 背景容器内,点击"展开 X 条回复"显示
|
主评论全宽;子回复折叠在 panel-2 背景容器内,点击"展开 X 条回复"显示
|
||||||
子回复之间用淡分隔线;depth≥2 时昵称后显示 ▶ 被回复者 */
|
子回复之间用淡分隔线;depth≥2 时昵称后显示 ▶ 被回复者 */
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default async function NotificationsPage({ searchParams }: NotificationsP
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-2xl mx-auto">
|
<div className="max-w-2xl mx-auto">
|
||||||
<nav className="meta mb-5 flex items-center gap-1.5 text-[13px]">
|
<nav className="meta -mt-4 sm:-mt-6 mb-1.5 flex items-center gap-1 text-[12.5px] leading-none">
|
||||||
<Link href="/" className="hover:text-[var(--accent)] transition-colors">首页</Link>
|
<Link href="/" className="hover:text-[var(--accent)] transition-colors">首页</Link>
|
||||||
<ChevronRight size={13} />
|
<ChevronRight size={13} />
|
||||||
<span style={{ color: "var(--accent)" }}>通知中心</span>
|
<span style={{ color: "var(--accent)" }}>通知中心</span>
|
||||||
|
|||||||
@@ -97,6 +97,15 @@ export default async function SitePagePublic({ params }: PageProps) {
|
|||||||
<SiteDocShell
|
<SiteDocShell
|
||||||
left={left}
|
left={left}
|
||||||
right={right}
|
right={right}
|
||||||
|
breadcrumb={
|
||||||
|
<SiteDocBreadcrumb
|
||||||
|
items={[
|
||||||
|
{ href: "/", label: "首页" },
|
||||||
|
{ label: "关于本站" },
|
||||||
|
{ label: p.title },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
mobileExtras={
|
mobileExtras={
|
||||||
<>
|
<>
|
||||||
{toc.length > 0 ? <PostToc items={toc} /> : null}
|
{toc.length > 0 ? <PostToc items={toc} /> : null}
|
||||||
@@ -107,14 +116,6 @@ export default async function SitePagePublic({ params }: PageProps) {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SiteDocBreadcrumb
|
|
||||||
items={[
|
|
||||||
{ href: "/", label: "首页" },
|
|
||||||
{ label: "关于本站" },
|
|
||||||
{ label: p.title },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<article className="panel p-6 sm:p-8">
|
<article className="panel p-6 sm:p-8">
|
||||||
<SiteDocMasthead
|
<SiteDocMasthead
|
||||||
kicker="官方文件"
|
kicker="官方文件"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import PostRow from "@/components/PostRow";
|
|||||||
import FeedTabs, { type FeedSort } from "@/components/home/FeedTabs";
|
import FeedTabs, { type FeedSort } from "@/components/home/FeedTabs";
|
||||||
import FeedNewTip from "@/components/home/FeedNewTip";
|
import FeedNewTip from "@/components/home/FeedNewTip";
|
||||||
import { WorkspaceNav, WorkspaceNavMobile } from "@/components/home/WorkspaceNav";
|
import { WorkspaceNav, WorkspaceNavMobile } from "@/components/home/WorkspaceNav";
|
||||||
|
import { SiteDocBreadcrumb } from "@/components/site-doc";
|
||||||
import { CommunityPanels, CommunityPanelsMobile } from "@/components/home/CommunityPanels";
|
import { CommunityPanels, CommunityPanelsMobile } from "@/components/home/CommunityPanels";
|
||||||
import {
|
import {
|
||||||
BoardPanels,
|
BoardPanels,
|
||||||
@@ -166,11 +167,21 @@ export default async function HomePage({ searchParams }: HomePageProps) {
|
|||||||
<div>
|
<div>
|
||||||
<WorkspaceNavMobile boards={boards} user={user} />
|
<WorkspaceNavMobile boards={boards} user={user} />
|
||||||
|
|
||||||
<div className="flex items-start gap-6 xl:gap-7 mt-5 lg:mt-0">
|
<div className={`flex items-start gap-6 xl:gap-7 ${activeBoard ? "" : "mt-5 lg:mt-0"}`}>
|
||||||
<WorkspaceNav boards={boards} user={user} />
|
<WorkspaceNav boards={boards} user={user} />
|
||||||
|
|
||||||
{/* 中栏:tab 栏与帖子流为同一个整体卡片(左栏切板块只替换这一列) */}
|
{/* 中栏:tab 栏与帖子流为同一个整体卡片(左栏切板块只替换这一列) */}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0 relative">
|
||||||
|
{activeBoard ? (
|
||||||
|
<div className="mt-4 lg:mt-0">
|
||||||
|
<SiteDocBreadcrumb
|
||||||
|
items={[
|
||||||
|
{ href: "/", label: "首页" },
|
||||||
|
{ label: activeBoard.name },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<h1 className="sr-only">{heading}</h1>
|
<h1 className="sr-only">{heading}</h1>
|
||||||
|
|
||||||
<div className="panel overflow-hidden">
|
<div className="panel overflow-hidden">
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default function PointsClient() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<nav className="meta mb-5 flex items-center gap-1.5 text-[13px]">
|
<nav className="meta -mt-4 sm:-mt-6 mb-1.5 flex items-center gap-1 text-[12.5px] leading-none">
|
||||||
<Link href="/" className="hover:text-[var(--accent)] transition-colors">
|
<Link href="/" className="hover:text-[var(--accent)] transition-colors">
|
||||||
首页
|
首页
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default async function EditPostPage({ params }: PageProps) {
|
|||||||
data-compose-viewport
|
data-compose-viewport
|
||||||
className="j13-compose-page w-full"
|
className="j13-compose-page w-full"
|
||||||
>
|
>
|
||||||
<nav className="meta mb-4 flex items-center gap-1.5 flex-wrap text-[13px] shrink-0">
|
<nav className="meta -mt-4 sm:-mt-6 mb-1.5 flex items-center gap-1 flex-wrap text-[12.5px] leading-none shrink-0">
|
||||||
<Link href="/" className="hover:text-[var(--accent)] transition-colors">
|
<Link href="/" className="hover:text-[var(--accent)] transition-colors">
|
||||||
首页
|
首页
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import CommentSection from "@/components/CommentSection";
|
|||||||
import PostActions from "@/components/PostActions";
|
import PostActions from "@/components/PostActions";
|
||||||
import LikeButton from "@/components/LikeButton";
|
import LikeButton from "@/components/LikeButton";
|
||||||
import PostToc from "@/components/PostToc";
|
import PostToc from "@/components/PostToc";
|
||||||
|
import { SiteDocBreadcrumb } from "@/components/site-doc";
|
||||||
import MobilePostBar from "@/components/MobilePostBar";
|
import MobilePostBar from "@/components/MobilePostBar";
|
||||||
import Avatar from "@/components/Avatar";
|
import Avatar from "@/components/Avatar";
|
||||||
import RoleBadge from "@/components/RoleBadge";
|
import RoleBadge from "@/components/RoleBadge";
|
||||||
@@ -557,7 +558,7 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
||||||
{/* 移动端浮动操作条:长文阅读中途可点赞 / 跳评论 / 回顶部 */}
|
{/* 移动端浮动操作条:长文读到中途可点赞 / 跳评论框 */}
|
||||||
<MobilePostBar
|
<MobilePostBar
|
||||||
postId={id}
|
postId={id}
|
||||||
liked={post.liked}
|
liked={post.liked}
|
||||||
@@ -571,7 +572,7 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
|||||||
{/* sticky 必须直接挂在 grid 子项上:aside 作为 grid item,其可粘行程
|
{/* sticky 必须直接挂在 grid 子项上:aside 作为 grid item,其可粘行程
|
||||||
等于行高(正文高度);内层包裹会被 items-start 缩成内容高而失去行程 */}
|
等于行高(正文高度);内层包裹会被 items-start 缩成内容高而失去行程 */}
|
||||||
<aside
|
<aside
|
||||||
className="hidden lg:flex w-56 xl:w-60 shrink-0 sticky top-24 flex-col gap-4 max-h-[calc(100vh_-_7rem)] overflow-hidden"
|
className="hidden lg:flex w-56 xl:w-60 shrink-0 sticky top-[var(--rail-top)] flex-col gap-4 max-h-[var(--rail-max-h)] overflow-hidden"
|
||||||
aria-label="帖子导航"
|
aria-label="帖子导航"
|
||||||
>
|
>
|
||||||
<BoardCard board={post.board} />
|
<BoardCard board={post.board} />
|
||||||
@@ -580,16 +581,14 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
{/* 中栏:正文主体 */}
|
{/* 中栏:正文主体 */}
|
||||||
<div className="min-w-0">
|
<div className="min-w-0 relative">
|
||||||
|
<SiteDocBreadcrumb
|
||||||
|
items={[
|
||||||
|
{ href: "/", label: "首页" },
|
||||||
|
{ href: `/?board=${post.board.id}`, label: post.board.name },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
<article className="w-full">
|
<article className="w-full">
|
||||||
<nav className="meta mb-5 flex items-center gap-1.5 flex-wrap text-[13px]">
|
|
||||||
<Link href="/" className="hover:text-[var(--accent)] transition-colors">首页</Link>
|
|
||||||
<ChevronRight size={13} />
|
|
||||||
<Link href={`/?board=${post.board.id}`} className="hover:text-[var(--accent)] transition-colors">
|
|
||||||
{post.board.name}
|
|
||||||
</Link>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div className="panel p-6 sm:p-8">
|
<div className="panel p-6 sm:p-8">
|
||||||
<header className="mb-7">
|
<header className="mb-7">
|
||||||
<div className="flex items-center gap-2 mb-4 flex-wrap">
|
<div className="flex items-center gap-2 mb-4 flex-wrap">
|
||||||
@@ -810,7 +809,7 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
|||||||
|
|
||||||
{/* 右栏:目录 / 同板块热议(阅读数据已并入标题下元信息条) */}
|
{/* 右栏:目录 / 同板块热议(阅读数据已并入标题下元信息条) */}
|
||||||
<aside
|
<aside
|
||||||
className="hidden lg:flex w-[280px] xl:w-[300px] shrink-0 sticky top-24 flex-col gap-4 max-h-[calc(100vh_-_7rem)] overflow-hidden"
|
className="hidden lg:flex w-[280px] xl:w-[300px] shrink-0 sticky top-[var(--rail-top)] flex-col gap-4 max-h-[var(--rail-max-h)] overflow-hidden"
|
||||||
aria-label="阅读辅助"
|
aria-label="阅读辅助"
|
||||||
>
|
>
|
||||||
{toc.length > 0 && <PostToc items={toc} />}
|
{toc.length > 0 && <PostToc items={toc} />}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<nav className="meta mb-5 flex items-center gap-1.5 text-[13px]">
|
<nav className="meta -mt-4 sm:-mt-6 mb-1.5 flex items-center gap-1 text-[12.5px] leading-none">
|
||||||
<Link href="/" className="hover:text-[var(--accent)] transition-colors">首页</Link>
|
<Link href="/" className="hover:text-[var(--accent)] transition-colors">首页</Link>
|
||||||
<ChevronRight size={13} />
|
<ChevronRight size={13} />
|
||||||
<span style={{ color: "var(--accent)" }}>{u.nickname || u.username}</span>
|
<span style={{ color: "var(--accent)" }}>{u.nickname || u.username}</span>
|
||||||
@@ -276,7 +276,7 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
|
|||||||
{/* ═══ 三栏:左导航 / 中内容 / 右信息 ═══ */}
|
{/* ═══ 三栏:左导航 / 中内容 / 右信息 ═══ */}
|
||||||
<div className="grid grid-cols-1 items-start lg:grid-cols-[224px_minmax(0,1fr)_280px] xl:grid-cols-[240px_minmax(0,1fr)_300px] gap-x-6 xl:gap-x-7">
|
<div className="grid grid-cols-1 items-start lg:grid-cols-[224px_minmax(0,1fr)_280px] xl:grid-cols-[240px_minmax(0,1fr)_300px] gap-x-6 xl:gap-x-7">
|
||||||
{/* 左栏:Tab 导航 */}
|
{/* 左栏:Tab 导航 */}
|
||||||
<aside className="hidden lg:flex w-56 xl:w-60 shrink-0 sticky top-24 flex-col gap-4">
|
<aside className="hidden lg:flex w-56 xl:w-60 shrink-0 sticky top-[var(--rail-top)] flex-col gap-4">
|
||||||
<section className="j13-module !p-2">
|
<section className="j13-module !p-2">
|
||||||
<p
|
<p
|
||||||
className="px-2.5 pt-1.5 pb-2 text-[11.5px] font-bold tracking-[0.08em] uppercase"
|
className="px-2.5 pt-1.5 pb-2 text-[11.5px] font-bold tracking-[0.08em] uppercase"
|
||||||
@@ -341,7 +341,7 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 右栏:账号信息 + 积分 + 安全提示 */}
|
{/* 右栏:账号信息 + 积分 + 安全提示 */}
|
||||||
<aside className="hidden lg:flex w-[280px] xl:w-[300px] shrink-0 sticky top-24 flex-col gap-4 max-h-[calc(100vh_-_7rem)] overflow-hidden">
|
<aside className="hidden lg:flex w-[280px] xl:w-[300px] shrink-0 sticky top-[var(--rail-top)] flex-col gap-4 max-h-[var(--rail-max-h)] overflow-hidden">
|
||||||
<section className="j13-module">
|
<section className="j13-module">
|
||||||
<p className="j13-module-title mb-3">
|
<p className="j13-module-title mb-3">
|
||||||
<Hash size={15} style={{ color: "var(--accent)" }} /> 账号信息
|
<Hash size={15} style={{ color: "var(--accent)" }} /> 账号信息
|
||||||
|
|||||||
39
frontend/components/BackToTop.tsx
Normal file
39
frontend/components/BackToTop.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { ArrowUp } from "lucide-react";
|
||||||
|
import { scrollBehavior } from "@/lib/motion";
|
||||||
|
|
||||||
|
const SHOW_AFTER = 400;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公开站长页回顶:滚过阈值后右下角浮现。
|
||||||
|
* 短页自然不出现;帖子移动端底栏时抬高,避免叠住操作条。
|
||||||
|
*/
|
||||||
|
export default function BackToTop({
|
||||||
|
liftForMobileBar = false,
|
||||||
|
}: {
|
||||||
|
liftForMobileBar?: boolean;
|
||||||
|
}) {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onScroll = () => setVisible(window.scrollY > SHOW_AFTER);
|
||||||
|
onScroll();
|
||||||
|
window.addEventListener("scroll", onScroll, { passive: true });
|
||||||
|
return () => window.removeEventListener("scroll", onScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!visible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`j13-back-top j13-toast-in${liftForMobileBar ? " is-lifted" : ""}`}
|
||||||
|
onClick={() => window.scrollTo({ top: 0, behavior: scrollBehavior() })}
|
||||||
|
aria-label="回到顶部"
|
||||||
|
>
|
||||||
|
<ArrowUp size={18} strokeWidth={2.25} aria-hidden />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -204,7 +204,7 @@ export default function Header({
|
|||||||
WebkitBackdropFilter: "blur(14px)",
|
WebkitBackdropFilter: "blur(14px)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="max-w-[1440px] mx-auto px-4 sm:px-6 h-[68px] flex items-center gap-4 lg:gap-7">
|
<div className="max-w-[1440px] mx-auto px-4 sm:px-6 h-[var(--header-bar)] flex items-center gap-4 lg:gap-7">
|
||||||
{/* 左组:品牌 + 主导航 */}
|
{/* 左组:品牌 + 主导航 */}
|
||||||
<div className="flex items-center gap-3 lg:gap-5 shrink-0">
|
<div className="flex items-center gap-3 lg:gap-5 shrink-0">
|
||||||
<Link href="/" className="flex items-center gap-2.5 group">
|
<Link href="/" className="flex items-center gap-2.5 group">
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { MessageSquare } from "lucide-react";
|
||||||
import { ArrowUp, MessageSquare } from "lucide-react";
|
|
||||||
import LikeButton from "./LikeButton";
|
import LikeButton from "./LikeButton";
|
||||||
import { scrollBehavior } from "@/lib/motion";
|
import { scrollBehavior } from "@/lib/motion";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 帖子详情移动端浮动操作条(lg 以下显示):
|
* 帖子详情移动端浮动操作条(lg 以下显示):
|
||||||
* 长文读到中途也能点赞 / 跳评论框 / 回顶部,不必滚到文末。
|
* 长文读到中途也能点赞 / 跳评论框;回顶由全局 BackToTop 承担。
|
||||||
*/
|
*/
|
||||||
export default function MobilePostBar({
|
export default function MobilePostBar({
|
||||||
postId,
|
postId,
|
||||||
@@ -20,15 +19,6 @@ export default function MobilePostBar({
|
|||||||
likeCount: number;
|
likeCount: number;
|
||||||
allowComments?: boolean;
|
allowComments?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [showTop, setShowTop] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const onScroll = () => setShowTop(window.scrollY > 400);
|
|
||||||
onScroll();
|
|
||||||
window.addEventListener("scroll", onScroll, { passive: true });
|
|
||||||
return () => window.removeEventListener("scroll", onScroll);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const scrollToComments = () => {
|
const scrollToComments = () => {
|
||||||
document.getElementById("comment-input")?.scrollIntoView({
|
document.getElementById("comment-input")?.scrollIntoView({
|
||||||
behavior: scrollBehavior(),
|
behavior: scrollBehavior(),
|
||||||
@@ -36,13 +26,6 @@ export default function MobilePostBar({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollToTop = () => {
|
|
||||||
window.scrollTo({
|
|
||||||
top: 0,
|
|
||||||
behavior: scrollBehavior(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="lg:hidden fixed bottom-4 left-1/2 -translate-x-1/2 z-40" role="navigation" aria-label="阅读操作">
|
<div className="lg:hidden fixed bottom-4 left-1/2 -translate-x-1/2 z-40" role="navigation" aria-label="阅读操作">
|
||||||
<div
|
<div
|
||||||
@@ -66,21 +49,9 @@ export default function MobilePostBar({
|
|||||||
<MessageSquare size={18} />
|
<MessageSquare size={18} />
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
{/* 放大点赞按钮的触控区域(样式见 globals.css .j13-bar-like) */}
|
|
||||||
<div className="j13-bar-like">
|
<div className="j13-bar-like">
|
||||||
<LikeButton postId={postId} liked={liked} likeCount={likeCount} variant="compact" />
|
<LikeButton postId={postId} liked={liked} likeCount={likeCount} variant="compact" />
|
||||||
</div>
|
</div>
|
||||||
{showTop && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={scrollToTop}
|
|
||||||
className="w-11 h-11 flex items-center justify-center rounded-full transition-colors hover:bg-[var(--accent-soft)] focus-visible:bg-[var(--accent-soft)] j13-toast-in"
|
|
||||||
style={{ color: "var(--ink-2)" }}
|
|
||||||
aria-label="回到顶部"
|
|
||||||
>
|
|
||||||
<ArrowUp size={18} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import Header from "@/components/Header";
|
import Header from "@/components/Header";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
|
import BackToTop from "@/components/BackToTop";
|
||||||
import type { User } from "@/lib/api";
|
import type { User } from "@/lib/api";
|
||||||
|
|
||||||
function pathIsAdmin(pathname: string | null) {
|
function pathIsAdmin(pathname: string | null) {
|
||||||
@@ -11,6 +12,17 @@ function pathIsAdmin(pathname: string | null) {
|
|||||||
return pathname === "/admin" || pathname.startsWith("/admin/");
|
return pathname === "/admin" || pathname.startsWith("/admin/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pathIsChat(pathname: string | null) {
|
||||||
|
if (!pathname) return false;
|
||||||
|
return pathname === "/chat" || pathname.startsWith("/chat/");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 帖子详情(非 edit)移动端有底栏,回顶需抬高 */
|
||||||
|
function pathIsPostDetail(pathname: string | null) {
|
||||||
|
if (!pathname) return false;
|
||||||
|
return /^\/post\/[^/]+$/.test(pathname);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 公开站 chrome 闸门:/admin 不渲染 Header/Footer/1440 main。
|
* 公开站 chrome 闸门:/admin 不渲染 Header/Footer/1440 main。
|
||||||
* 首屏用 middleware 的 x-pathname 初值保证 hydration 一致;
|
* 首屏用 middleware 的 x-pathname 初值保证 hydration 一致;
|
||||||
@@ -63,6 +75,9 @@ export default function SiteChrome({
|
|||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 聊天页锁视口、主滚动在对话区,窗口回顶无意义
|
||||||
|
const showBackToTop = !pathIsChat(pathname);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header
|
<Header
|
||||||
@@ -76,11 +91,14 @@ export default function SiteChrome({
|
|||||||
<main
|
<main
|
||||||
id="main"
|
id="main"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
className="flex-1 w-full max-w-[1440px] mx-auto px-4 sm:px-6 py-7 sm:py-9 outline-none"
|
className="flex-1 w-full max-w-[1440px] mx-auto px-4 sm:px-6 py-7 sm:py-[var(--main-pad-y)] outline-none"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
<Footer siteName={siteName} />
|
<Footer siteName={siteName} />
|
||||||
|
{showBackToTop ? (
|
||||||
|
<BackToTop liftForMobileBar={pathIsPostDetail(pathname)} />
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -283,8 +283,11 @@ function BoardPanelsBody(props: BoardPanelsProps) {
|
|||||||
|
|
||||||
export function BoardPanels(props: BoardPanelsProps) {
|
export function BoardPanels(props: BoardPanelsProps) {
|
||||||
return (
|
return (
|
||||||
<aside className="hidden w-[280px] shrink-0 lg:block xl:w-[300px]" aria-label="板块面板">
|
<aside
|
||||||
<div className="sticky top-24 space-y-4">
|
className="hidden w-[280px] shrink-0 lg:block xl:w-[300px]"
|
||||||
|
aria-label="板块面板"
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
<BoardPanelsBody {...props} />
|
<BoardPanelsBody {...props} />
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -302,18 +305,19 @@ export function BoardPanelsMobile(props: BoardPanelsProps) {
|
|||||||
/** 板块不存在时的简卡 */
|
/** 板块不存在时的简卡 */
|
||||||
export function BoardMissingPanel() {
|
export function BoardMissingPanel() {
|
||||||
return (
|
return (
|
||||||
<aside className="hidden w-[280px] shrink-0 lg:block xl:w-[300px]" aria-label="板块面板">
|
<aside
|
||||||
<div className="sticky top-24">
|
className="hidden w-[280px] shrink-0 lg:block xl:w-[300px]"
|
||||||
<section className="j13-module">
|
aria-label="板块面板"
|
||||||
<p className="j13-module-title mb-2">
|
>
|
||||||
<LayoutGrid size={15} style={{ color: "var(--ink-3)" }} /> 板块
|
<section className="j13-module">
|
||||||
</p>
|
<p className="j13-module-title mb-2">
|
||||||
<p className="meta">板块不存在或已删除</p>
|
<LayoutGrid size={15} style={{ color: "var(--ink-3)" }} /> 板块
|
||||||
<Link href="/" className="btn btn-line btn-sm mt-3 w-full justify-center">
|
</p>
|
||||||
返回全部讨论
|
<p className="meta">板块不存在或已删除</p>
|
||||||
</Link>
|
<Link href="/" className="btn btn-line btn-sm mt-3 w-full justify-center">
|
||||||
</section>
|
返回全部讨论
|
||||||
</div>
|
</Link>
|
||||||
|
</section>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -450,8 +450,11 @@ function CommunityPanelsBody({
|
|||||||
|
|
||||||
export function CommunityPanels(props: PanelsProps) {
|
export function CommunityPanels(props: PanelsProps) {
|
||||||
return (
|
return (
|
||||||
<aside className="hidden w-[280px] shrink-0 lg:block xl:w-[300px]" aria-label="社区面板">
|
<aside
|
||||||
<div className="sticky top-24 space-y-4">
|
className="hidden w-[280px] shrink-0 lg:block xl:w-[300px]"
|
||||||
|
aria-label="社区面板"
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
<CommunityPanelsBody {...props} />
|
<CommunityPanelsBody {...props} />
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -16,8 +16,12 @@ export function WorkspaceNav({ boards }: { boards: BoardWithCount[]; user?: User
|
|||||||
const activeBoard = Number(params.get("board")) || 0;
|
const activeBoard = Number(params.get("board")) || 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="hidden lg:block w-56 xl:w-60 shrink-0" aria-label="工作台导航">
|
// sticky 必须挂在 flex 子项上:父级 items-start 会把内层缩成内容高,失去粘滞行程
|
||||||
<div className="sticky top-24 space-y-5">
|
<nav
|
||||||
|
className="hidden lg:block w-56 xl:w-60 shrink-0 sticky top-[var(--rail-top)] max-h-[var(--rail-max-h)] j13-rail-scroll"
|
||||||
|
aria-label="工作台导航"
|
||||||
|
>
|
||||||
|
<div className="space-y-5">
|
||||||
{/* 「全部讨论」是重置筛选的动作入口,与板块目录分区展示;卡片样式与下方板块一致 */}
|
{/* 「全部讨论」是重置筛选的动作入口,与板块目录分区展示;卡片样式与下方板块一致 */}
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Link href="/" className="j13-bcard" aria-current={activeBoard === 0 ? "page" : undefined}>
|
<Link href="/" className="j13-bcard" aria-current={activeBoard === 0 ? "page" : undefined}>
|
||||||
|
|||||||
@@ -3,24 +3,29 @@ import { ChevronRight } from "lucide-react";
|
|||||||
|
|
||||||
export type Crumb = { href?: string; label: string };
|
export type Crumb = { href?: string; label: string };
|
||||||
|
|
||||||
/** 公文页面包屑 */
|
/** 位置指引:桌面叠在中栏卡片上方的内边距里,不拉低卡片、不对齐到左栏 */
|
||||||
export default function SiteDocBreadcrumb({ items }: { items: Crumb[] }) {
|
export default function SiteDocBreadcrumb({ items }: { items: Crumb[] }) {
|
||||||
return (
|
return (
|
||||||
<nav className="meta mb-5 flex items-center gap-1.5 flex-wrap text-[13px]" aria-label="面包屑">
|
<nav
|
||||||
|
className="meta mb-2 flex items-center gap-1 flex-wrap text-[12.5px] leading-none lg:absolute lg:bottom-full lg:left-0 lg:right-0 lg:mb-0 lg:pb-2 lg:pl-1"
|
||||||
|
aria-label="面包屑"
|
||||||
|
>
|
||||||
{items.map((item, i) => {
|
{items.map((item, i) => {
|
||||||
const last = i === items.length - 1;
|
const last = i === items.length - 1;
|
||||||
|
const cls = last ? "truncate" : "shrink-0";
|
||||||
return (
|
return (
|
||||||
<span key={`${item.label}-${i}`} className="inline-flex items-center gap-1.5 min-w-0">
|
<span key={`${item.label}-${i}`} className="inline-flex items-center gap-1 min-w-0">
|
||||||
{i > 0 ? <ChevronRight size={13} className="shrink-0" /> : null}
|
{i > 0 ? <ChevronRight size={12} className="shrink-0 opacity-70" aria-hidden /> : null}
|
||||||
{item.href && !last ? (
|
{item.href ? (
|
||||||
<Link href={item.href} className="hover:text-[var(--accent)] transition-colors shrink-0">
|
<Link
|
||||||
|
href={item.href}
|
||||||
|
className={`hover:text-[var(--accent)] transition-colors ${cls}`}
|
||||||
|
style={last ? { color: "var(--accent)" } : undefined}
|
||||||
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span className={cls} style={last ? { color: "var(--accent)" } : undefined}>
|
||||||
className={last ? "truncate" : "shrink-0"}
|
|
||||||
style={last ? { color: "var(--accent)" } : undefined}
|
|
||||||
>
|
|
||||||
{item.label}
|
{item.label}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ export default function SiteDocShell({
|
|||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
children,
|
children,
|
||||||
|
breadcrumb,
|
||||||
mobileExtras,
|
mobileExtras,
|
||||||
}: {
|
}: {
|
||||||
left?: ReactNode;
|
left?: ReactNode;
|
||||||
right?: ReactNode;
|
right?: ReactNode;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
/** 中栏卡片上方的位置指引;桌面绝对定位,不占栅格高度 */
|
||||||
|
breadcrumb?: ReactNode;
|
||||||
/** 窄屏下沉到正文之后的侧栏副本 */
|
/** 窄屏下沉到正文之后的侧栏副本 */
|
||||||
mobileExtras?: ReactNode;
|
mobileExtras?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
@@ -20,16 +23,19 @@ export default function SiteDocShell({
|
|||||||
<>
|
<>
|
||||||
<div className="grid grid-cols-1 items-start lg:grid-cols-[224px_minmax(0,1fr)_280px] xl:grid-cols-[240px_minmax(0,1fr)_300px] gap-x-6 xl:gap-x-7">
|
<div className="grid grid-cols-1 items-start lg:grid-cols-[224px_minmax(0,1fr)_280px] xl:grid-cols-[240px_minmax(0,1fr)_300px] gap-x-6 xl:gap-x-7">
|
||||||
<aside
|
<aside
|
||||||
className="hidden lg:flex w-56 xl:w-60 shrink-0 sticky top-24 flex-col gap-4 max-h-[calc(100vh_-_7rem)] overflow-hidden"
|
className="hidden lg:flex w-56 xl:w-60 shrink-0 sticky top-[var(--rail-top)] flex-col gap-4 max-h-[var(--rail-max-h)] overflow-hidden"
|
||||||
aria-label="站点导航"
|
aria-label="站点导航"
|
||||||
>
|
>
|
||||||
{left}
|
{left}
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div className="min-w-0">{children}</div>
|
<div className="min-w-0 relative">
|
||||||
|
{breadcrumb}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
|
||||||
<aside
|
<aside
|
||||||
className="hidden lg:flex w-[280px] xl:w-[300px] shrink-0 sticky top-24 flex-col gap-4 max-h-[calc(100vh_-_7rem)] overflow-hidden"
|
className="hidden lg:flex w-[280px] xl:w-[300px] shrink-0 sticky top-[var(--rail-top)] flex-col gap-4 max-h-[var(--rail-max-h)] overflow-hidden"
|
||||||
aria-label="阅读辅助"
|
aria-label="阅读辅助"
|
||||||
>
|
>
|
||||||
{right}
|
{right}
|
||||||
|
|||||||
Reference in New Issue
Block a user