v0.10.5 首页 H2 产品线入口 ×6:ImageBoxWidget 重构为 Elementor 忠实 DOM + slim 白名单补齐 + H2 scoped CSS(post-18 对齐,桌面/移动/hover 实测一致)

This commit is contained in:
BigCode
2026-11-01 00:15:29 +08:00
commit 5b83e71ad9
128 changed files with 20008 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
/**
* # 组件 shadcn/ui NavigationMenu
* 基于 @radix-ui/react-navigation-menu,用于顶部主导航(含巨幕面板)。
* 样式对齐原站 Max Mega Menu(megamenu 内联 CSS 实测值):
* - 顶级项:高 68px、16px/400/#222,hover/展开仅文字变橙(背景不变);
* 指示器为实心三角 ▾(dashicons arrow-down 等价物),展开不旋转、颜色随文字。
* - 巨幕/飞出面板:#fcfbfa 底 + 顶部 2px #f5b279 橙线 + 0 10px 30px 阴影,直角。
* 切换动效保留 shadcn/ui(tw-animate-css 的 fade/zoom/slide + Radix motion 属性)。
*/
import * as React from "react";
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
import { cn } from "~/lib/utils";
/** # 组件 导航菜单根容器 */
function NavigationMenu({ className, children, viewportPosition = "inline", ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & { viewportPosition?: "inline" | "full" }) {
return (
<NavigationMenuPrimitive.Root data-viewport={viewportPosition} className={cn("relative z-[90] flex max-w-max items-center justify-center", className)} {...props}>
{children}
{viewportPosition === "full" ? <ViewportFullWidth /> : <ViewportCentered />}
</NavigationMenuPrimitive.Root>
);
}
/** # 组件 导航菜单列表(顶级项容器)
* p-0:本项目不引入 Tailwind preflight,需重置 UL 的浏览器默认左内边距。
*/
function NavigationMenuList({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
return <NavigationMenuPrimitive.List className={cn("m-0 flex flex-1 list-none items-center justify-center gap-0.5 p-0", className)} {...props} />;
}
/** # 组件 导航菜单项 */
function NavigationMenuItem({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
return <NavigationMenuPrimitive.Item className={cn("relative", className)} {...props} />;
}
/** # 类型 NavigationMenuTrigger 属性 */
const triggerStyle =
"inline-flex h-[68px] w-max cursor-pointer items-center justify-center gap-[6px] rounded-none border-0 bg-transparent py-0 pr-[6px] pl-2 text-[16px] font-normal whitespace-nowrap text-[#222] transition-colors hover:text-brand focus-visible:outline-2 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-brand";
/** # 组件 导航菜单触发器(顶级可展开项)
* chevron=false 时子元素原样透传(asChild 场景:Radix Slot 要求 children 是
* 单个 React 元素,不能被 JSX 双槽包装成数组)。
*/
function NavigationMenuTrigger({ className, children, chevron = true, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger> & { chevron?: boolean }) {
const content = chevron ? (
<>
{children}
<CaretDown />
</>
) : (
children
);
return (
<NavigationMenuPrimitive.Trigger className={cn(triggerStyle, "group", className)} {...props}>
{content}
</NavigationMenuPrimitive.Trigger>
);
}
/** # 组件 导航菜单内容面板(下拉/巨幕内容)
* data-[motion] 动画类依赖 tw-animate-css(app.css 已引入)。
*/
function NavigationMenuContent({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
return <NavigationMenuPrimitive.Content className={cn("data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion=from-end]:fade-in data-[motion=from-start]:fade-in data-[motion=to-end]:fade-out data-[motion=to-start]:fade-out", className)} {...props} />;
}
/** # 组件 导航菜单链接(无下级的普通链接项) */
function NavigationMenuLink({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
return <NavigationMenuPrimitive.Link className={cn(triggerStyle, className)} {...props} />;
}
/** # 组件 巨幕面板标题(面板内小标题/副标题) */
function NavigationMenuHeading({ className, ...props }: React.ComponentProps<"div">) {
return <div className={cn("text-[16px] leading-relaxed font-bold text-[#222]", className)} {...props} />;
}
/** # 组件 实心下三角指示器(等价原站 dashicons arrow-down,展开不旋转,颜色随文字) */
function CaretDown() {
return (
<svg width="10" height="10" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" className="shrink-0">
<path d="M4.5 7h11L10 13.5 4.5 7Z" />
</svg>
);
}
/** # 组件 居中式视口(小尺寸下拉用)
* 面板 chrome 对齐原站 flyout:#fcfbfa 底 + 顶部 2px 橙线 + 直角 + 大阴影。
*/
function ViewportCentered() {
return (
<div className="absolute top-full left-0 flex w-max justify-center">
<NavigationMenuPrimitive.Viewport className="relative mt-0 h-[var(--radix-navigation-menu-viewport-height)] w-[var(--radix-navigation-menu-viewport-width)] origin-top overflow-hidden rounded-none border-t-2 border-panel-line bg-panel shadow-[0_10px_30px_rgba(13,21,46,0.1)] transition-[width,height] duration-300 data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:zoom-in-95" />
</div>
);
}
/** # 组件 全宽视口(巨幕面板用:固定在导航条下方、水平居中)
* 导航条(topbar 40px + 主导航 68px)sticky 置顶,故固定定位 top-108px 与其底边对齐;
* 面板宽度按原站实测 1340px(1436 视口),窄视口收窄到留 16px 边距。
*/
function ViewportFullWidth() {
return (
<div className="fixed inset-x-0 top-[108px] z-[90] flex justify-center">
<NavigationMenuPrimitive.Viewport className="relative h-[var(--radix-navigation-menu-viewport-height)] w-[calc(100%-32px)] max-w-[1340px] origin-top overflow-visible rounded-none border-t-2 border-panel-line bg-panel shadow-[0_10px_30px_rgba(13,21,46,0.1)] transition-[height] duration-300 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:pointer-events-none data-[state=closed]:slide-out-to-top-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-top-2" />
</div>
);
}
export { NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuHeading, CaretDown };