dashboard: - Add comparison-badge, dashboard-notification-widget, dashboard-responsive-layout, dashboard-time-range-filter - Add parent-dashboard components directory - Add config, hooks, and services directories diagnostic: - Add role-config and services directory elective: - Add elective-course-detail, elective-stats-cards, parent-selection-view components - Add data-access-settings and data-access-stats
92 lines
2.1 KiB
TypeScript
92 lines
2.1 KiB
TypeScript
import type { ReactNode } from "react"
|
||
import { cn } from "@/shared/lib/utils"
|
||
|
||
/**
|
||
* 仪表盘移动端响应式布局(L8)。
|
||
*
|
||
* 根据屏幕尺寸自动调整 Widget 布局:
|
||
* - 移动端(< sm):单列堆叠,重要 Widget 优先
|
||
* - 平板(sm - lg):双列网格
|
||
* - 桌面(>= lg):按配置的 layoutClassName 渲染
|
||
*
|
||
* 通过 CSS Grid 的 order 属性实现移动端优先级排序,
|
||
* 避免重复渲染(P2-9 教训)。
|
||
*/
|
||
export function DashboardResponsiveLayout({
|
||
children,
|
||
className,
|
||
mobileFirstSlot,
|
||
}: {
|
||
children: ReactNode
|
||
className?: string
|
||
/** 移动端置顶的 Widget(如今日课表/待办) */
|
||
mobileFirstSlot?: ReactNode
|
||
}) {
|
||
return (
|
||
<div className={cn("flex flex-col gap-4 lg:grid", className)}>
|
||
{mobileFirstSlot && (
|
||
<div className="order-first lg:hidden">{mobileFirstSlot}</div>
|
||
)}
|
||
{children}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
/**
|
||
* 移动端水平滑动卡片容器(L8)。
|
||
*
|
||
* - snap-x snap-mandatory 提供卡片吸附效果
|
||
* - 隐藏滚动条但保留滚动功能
|
||
* - 仅在移动端生效,桌面端转为网格
|
||
*/
|
||
export function MobileSwipeContainer({
|
||
children,
|
||
className,
|
||
ariaLabel,
|
||
}: {
|
||
children: ReactNode
|
||
className?: string
|
||
ariaLabel?: string
|
||
}) {
|
||
return (
|
||
<div
|
||
className={cn(
|
||
"flex gap-4 overflow-x-auto pb-2 snap-x snap-mandatory scrollbar-hide sm:hidden",
|
||
className,
|
||
)}
|
||
aria-label={ariaLabel}
|
||
role="region"
|
||
>
|
||
{children}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
/**
|
||
* 桌面端网格容器(L8)。
|
||
*
|
||
* 与 MobileSwipeContainer 配对使用,
|
||
* 桌面端显示网格,移动端隐藏(由 MobileSwipeContainer 接管)。
|
||
*/
|
||
export function DesktopGrid({
|
||
children,
|
||
className,
|
||
columns = 3,
|
||
}: {
|
||
children: ReactNode
|
||
className?: string
|
||
columns?: 2 | 3 | 4
|
||
}) {
|
||
const gridCols = {
|
||
2: "sm:grid-cols-2",
|
||
3: "sm:grid-cols-2 lg:grid-cols-3",
|
||
4: "sm:grid-cols-2 lg:grid-cols-4",
|
||
}[columns]
|
||
|
||
return (
|
||
<div className={cn("hidden sm:grid gap-4", gridCols, className)}>
|
||
{children}
|
||
</div>
|
||
)
|
||
}
|