Skyroc Web Kit

@skyroc/materials

管理后台 UI 材料库 — 提供完整的布局框架 AdminLayout 与多风格标签页 PageTab,支持响应式、暗色模式与 CSS 变量驱动的动态主题

概述

@skyroc/materials 是专为管理后台设计的 React UI 材料库,提供两个核心组件:

  • AdminLayout — 完整的后台布局框架,通过插槽(Header / Tab / Sider / Footer / children)组合区域,内部用 CSS 变量驱动所有尺寸和层叠,支持两种布局模式、两种滚动模式与移动端适配
  • PageTab — 多风格标签页组件,支持 Button / Chrome / Slider 三种样式,配合路由标签系统使用

架构

src/
├── types/
│   └── index.ts          AdminLayoutProps、PageTabProps 及所有子配置类型
├── libs/
│   ├── admin-layout/
│   │   ├── index.tsx     AdminLayout 组件(布局逻辑 + 插槽渲染)
│   │   ├── shared.ts     createLayoutCssVars、LAYOUT_SCROLL_EL_ID、LAYOUT_MAX_Z_INDEX
│   │   └── index.module.css  布局 CSS 变量消费(--soy-* 变量)
│   └── page-tab/
│       ├── index.tsx     PageTab 入口(模式分发、memo 包装)
│       ├── ButtonTab.tsx  按钮风格标签
│       ├── ChromeTab.tsx  Chrome 风格标签
│       ├── SliderTab.tsx  滑块下划线风格标签
│       ├── SvgClose.tsx   关闭图标(支持触摸事件)
│       ├── ChromeTabBg.tsx Chrome 弧形 SVG 背景
│       ├── hook.ts        useTap(触摸事件处理)
│       ├── shared.ts      createTabCssVars、ACTIVE_COLOR
│       └── index.module.css  标签页 CSS 变量消费
└── index.ts              公共导出

安装

pnpm add @skyroc/materials

依赖@skyroc/colorclsx

AdminLayout

概念

AdminLayout 是一个插槽式布局容器,不内置任何业务逻辑,只负责将传入的区域按照配置规则排列、确定层叠顺序和控制滚动行为。

┌─────────────────────────────────────┐
│  Header(固定顶部,z-index: 97)      │
├─────────────────────────────────────┤
│  Tab(固定,top = header-height)     │
├──────────┬──────────────────────────┤
│          │                          │
│  Sider   │  Main Content(children) │
│(固定左侧)│                          │
│          │                          │
├──────────┴──────────────────────────┤
│  Footer(可固定底部)                 │
└─────────────────────────────────────┘

快速上手

import { AdminLayout, LAYOUT_SCROLL_EL_ID } from '@skyroc/materials';

const App = () => {
  const [siderCollapse, setSiderCollapse] = useState(false);

  return (
    <AdminLayout
      mode="vertical"
      fixedTop
      headerHeight={56}
      tabHeight={44}
      siderWidth={220}
      siderCollapsedWidth={64}
      siderCollapse={siderCollapse}
      footerHeight={48}
      isMobile={false}
      scrollMode="content"
      updateSiderCollapse={() => setSiderCollapse(v => !v)}
      Header={<AppHeader />}
      Tab={<AppTab />}
      Sider={<AppSider />}
      Footer={<AppFooter />}
    >
      <RouterOutlet />
    </AdminLayout>
  );
};

Props 完整说明

布局与滚动

Prop类型默认值说明
mode'vertical' | 'horizontal''vertical'布局方向,影响 Sider 位置和 Footer 行为
scrollMode'content' | 'wrapper''content'滚动区域位置,见下方说明
scrollElIdstring'__SCROLL_EL_ID__'滚动容器的 DOM id,用于编程式滚动
scrollElClassstring滚动元素额外 class
scrollWrapperClassstring外层包裹 section 的 class
fixedTopbooleantrueHeader 和 Tab 是否固定到顶部(position: absolute
isMobileboolean移动端模式:桌面 Sider 隐藏,启用 Mobile Sider + 遮罩
maxZIndexnumber100所有区域层叠的上限基准值
commonClassstring'transition-all-300'所有区域公共 class,用于控制过渡动画
fullContentboolean全屏内容模式:Header、Sider、Footer 全部 display: none
Prop类型默认值说明
HeaderReactNodeHeader 插槽内容
headerHeightnumber56Header 高度(px),同步写入 CSS 变量
headerVisiblebooleantruefalse 时不渲染 Header 区域
headerClassstringHeader 元素额外 class

Tab

Prop类型默认值说明
TabReactNode标签页插槽内容
tabHeightnumber44Tab 高度(px)
tabVisiblebooleantruefalse 时不渲染 Tab 区域
tabClassstringTab 元素额外 class

Sider

Prop类型默认值说明
SiderReactNode侧边栏插槽内容
siderWidthnumber220展开时宽度(px)
siderCollapsedWidthnumber64折叠时宽度(px)
siderCollapsebooleanfalse是否处于折叠状态
siderVisiblebooleantruefalse 时不渲染 Sider
siderClassstringSider 元素额外 class
mobileSiderClassstring移动端 Sider 额外 class
updateSiderCollapse() => void必填点击移动端遮罩时的折叠回调
Prop类型默认值说明
FooterReactNodeFooter 插槽内容
footerHeightnumber48Footer 高度(px)
footerVisiblebooleantruefalse 时不渲染 Footer
fixedFooterbooleanFooter 固定到底部(position: absolute
footerClassstringFooter 元素额外 class
rightFooterbooleanfalse垂直/水平布局中 Footer 是否跟随 Sider 右移

两种布局模式

Vertical 模式(默认):

  • Sider 位于最左侧,覆盖 Header / Tab / Footer 的左侧空间(通过 padding-left 让出位置)
  • Header、Footer 在垂直模式下各自独立 left-gap,随 sider 展开/折叠动态调整
  • 适合大多数管理后台场景

Horizontal 模式

  • Sider 层叠关系降低(z-index = maxZIndex - 4),通常用作顶部菜单下的内容侧边
  • Footer 的 rightFooter 在此模式下影响是否跟随内容区对齐

两种滚动模式

content 模式(默认):

  • scrollElId 挂在 <main> 元素上
  • 只有内容区域可滚动
  • Header / Tab / Sider / Footer 始终可见,不会随内容滚动消失

wrapper 模式

  • scrollElId 挂在外层 <section>
  • 整个布局作为一个滚动区
  • fixedTop 或水平布局的 wrapper 滚动模式下,Header/Tab 自动固定(position: absolute

CSS 变量系统

AdminLayout 将所有尺寸和层叠值写入 style inline CSS 变量,CSS 模块通过变量消费,无需硬编码:

--soy-header-height       Header 高度(如 "56px")
--soy-header-z-index      Header 层叠(maxZIndex - 3,默认 97)
--soy-tab-height          Tab 高度
--soy-tab-z-index         Tab 层叠(maxZIndex - 5,默认 95)
--soy-sider-width         Sider 展开宽度
--soy-sider-collapsed-width Sider 折叠宽度
--soy-sider-z-index       Sider 层叠(垂直: maxZIndex - 1 = 99;水平: maxZIndex - 4 = 96)
--soy-mobile-sider-z-index 移动端 Sider 层叠(移动端: maxZIndex - 2 = 98;否则 0)
--soy-footer-height       Footer 高度
--soy-footer-z-index      Footer 层叠(maxZIndex - 5,默认 95)

Z-Index 计算规则maxZIndex = 100):

Sider(垂直)  99  ← 最高,覆盖 Header/Footer 左边缘
Header         97
Mobile Sider   98  ← 移动端特有
Tab            95
Footer         95
Mobile Mask    98  ← 移动端遮罩

移动端行为

isMobile = true

  • 桌面 Sider(<aside>)不渲染
  • 渲染 Mobile Sider + 半透明黑色遮罩(rgba(0,0,0,0.2)
  • 点击遮罩触发 updateSiderCollapse()
  • siderCollapse = true 时 Mobile Sider overflow: hidden,遮罩 display: none

编程式滚动

import { LAYOUT_SCROLL_EL_ID } from '@skyroc/materials';

// 获取滚动容器并滚动到顶部
const scrollEl = document.querySelector(`#${LAYOUT_SCROLL_EL_ID}`);
scrollEl?.scrollTo({ top: 0, behavior: 'smooth' });

PageTab

概念

PageTab 是一个单个标签页单元,通常配合路由标签列表使用。它本身不管理列表状态,只负责渲染一个标签并处理点击与关闭。

快速上手

import { PageTab } from '@skyroc/materials';

// Chrome 风格(默认)
<PageTab
  mode="chrome"
  active={currentRoute === '/dashboard'}
  activeColor="#6366F1"
  prefix={<DashboardIcon />}
  darkMode={isDark}
  closable
  onClick={() => navigate('/dashboard')}
  handleClose={() => closeTab('/dashboard')}
>
  仪表盘
</PageTab>

// 渲染一组标签
{tabs.map(tab => (
  <PageTab
    key={tab.path}
    mode="button"
    active={tab.path === currentPath}
    activeColor={themeColor}
    prefix={tab.icon}
    onClick={() => navigate(tab.path)}
    handleClose={() => closeTab(tab.path)}
  >
    {tab.title}
  </PageTab>
))}

Props

Prop类型默认值说明
mode'chrome' | 'button' | 'slider''chrome'标签外观风格
activeboolean是否为当前激活标签
activeColorstring'#1890ff'激活色,用于生成 CSS 变量色彩系列
prefixReactNode必填前缀内容(通常为图标)
suffixReactNode自定义后缀(默认为关闭按钮)
childrenReactNode标签文字
closablebooleantrue是否显示关闭按钮
handleClose() => void关闭按钮回调,点击时阻止冒泡
onClick() => void必填标签点击回调
darkModeboolean暗色模式开关
commonClassstring'transition-all-300'过渡动画 class
classNamestring外层额外 class
buttonClassstringButton 风格专用额外 class
chromeClassstringChrome 风格专用额外 class
sliderClassstringSlider 风格专用额外 class
styleCSSProperties内联样式(会与 CSS 变量合并)

三种风格详解

Chrome 模式

仿浏览器标签栏风格,使用 SVG 弧形背景(ChromeTabBg):

 ╭────────────────╮
 │ ⬡  标签名   ✕  │  ← 激活(填充主色 10% 透明)
─╯                ╰─
   标签名   ✕           ← 未激活(透明背景)
  • 激活状态:--soy-primary-color1(主色 + 白底 10% 混合)为背景
  • 暗色激活:--soy-primary-color2(主色 + 黑底 30% 混合)为背景
  • 相邻标签间通过右侧分隔线分隔,激活标签的右侧分隔线隐藏

Button 模式

按钮框线风格:

┌─────────────┐   ← 激活(主色边框 + 主色 10% 透明背景)
│ ⬡  标签名  ✕│
└─────────────┘

  ⬡  标签名  ✕    ← 未激活(灰色边框)
  • 激活状态:border 变为主色,背景 --soy-primary-color-opacity1(10% alpha)
  • 暗色模式下边框色适配深色背景

Slider 模式

轻量下划线风格:

⬡  标签名  ✕
────────────    ← 激活(主色下划线 + 浅背景)

⬡  标签名  ✕   ← 未激活(无下划线)
  • 激活状态:底部 2px 主色边框 + --soy-primary-color-opacity1 背景
  • hover:文字变主色

CSS 变量系统

createTabCssVars(activeColor) 根据一个颜色生成完整的色彩变量组,注入 PageTab 的 style 属性:

CSS 变量生成方式用途
--soy-primary-color原色文字激活色、分隔线色
--soy-primary-color1主色 + 白底 10% 混合Chrome 激活背景(亮色)
--soy-primary-color2主色 + 黑底 30% 混合Chrome 激活背景(暗色)
--soy-primary-color-opacity1主色 alpha 10%Button/Slider 激活背景
--soy-primary-color-opacity2主色 alpha 15%关闭按钮 hover 背景
--soy-primary-color-opacity3主色 alpha 30%关闭按钮 hover 激活态

颜色混合来自 @skyroc/colortransformColorWithOpacityaddColorAlpha

关闭按钮(SvgClose)

未提供 suffixclosable = true 时,自动渲染内置 SVG 关闭图标:

  • 16×16 SVG,圆形 hover 区域
  • hover 时:背景变 --soy-primary-color-opacity2,图标变主色
  • 激活标签 hover 关闭时:背景变 --soy-primary-color-opacity3
  • 支持触摸事件(onTouchStart / onTouchEnd),与 useTap 配合防止误触

触摸支持

内置 useTap hook 区分滑动和点击,避免移动端滚动时误触发标签切换:

function useTap(onClick: () => void) {
  // touchstart → 记录触摸状态
  // touchmove  → 标记为滑动(取消点击)
  // touchend   → 仅未发生滑动时执行 onClick
  return { onTouchStart, onTouchMove, onTouchEnd }
}

常量

import { LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX } from '@skyroc/materials';

LAYOUT_SCROLL_EL_ID  // '__SCROLL_EL_ID__' — 滚动容器的默认 DOM id
LAYOUT_MAX_Z_INDEX   // 100 — 布局层叠的默认最大值

类型参考

LayoutMode

type LayoutMode = 'horizontal' | 'vertical';

LayoutScrollMode

type LayoutScrollMode = 'content' | 'wrapper';

PageTabMode

type PageTabMode = 'button' | 'chrome' | 'slider';

LayoutCssVars

通过 TypeScript 模板字面量类型生成,键名前缀 --soy-,字段来自 LayoutCssVarsProps

type LayoutCssVars = {
  '--soy-footer-height': string | number;
  '--soy-footer-z-index': string | number;
  '--soy-header-height': string | number;
  '--soy-header-z-index': string | number;
  '--soy-mobile-sider-z-index': string | number;
  '--soy-sider-collapsed-width': string | number;
  '--soy-sider-width': string | number;
  '--soy-sider-z-index': string | number;
  '--soy-tab-height': string | number;
  '--soy-tab-z-index': string | number;
};

PageTabCssVars

type PageTabCssVars = {
  '--soy-primary-color': string | number;
  '--soy-primary-color1': string | number;
  '--soy-primary-color2': string | number;
  '--soy-primary-color-opacity1': string | number;
  '--soy-primary-color-opacity2': string | number;
  '--soy-primary-color-opacity3': string | number;
};

设计说明

为什么用 CSS 变量而非内联 style

所有尺寸(height / width / z-index)都写入 CSS 变量,CSS 模块通过 var() 引用。这样:

  • 组件内部样式改动不需要 JS 参与
  • 过渡动画(transition: all 300ms)能覆盖 CSS 变量驱动的属性变化
  • 调试时在浏览器 DevTools 修改 CSS 变量即可预览效果

为什么 AdminLayout 不内置区域组件

AdminLayout 是纯布局容器,不耦合任何业务组件(菜单树、标签列表、用户信息等)。应用通过插槽自由组合,便于在不同项目中复用布局骨架,替换插槽内容。

PageTab memo 包装

PageTabReact.memo 包裹,标签列表中只有发生属性变化的标签会重新渲染。ChromeTabBgSvgClose 同样 memo,避免父组件更新时的无效渲染。

On this page