react-okr-tree Logoreact-okr-tree
外观

主题与样式定制

6 套内置主题、27 个 --okr-* 外观变量一览、自定义主题、unstyled 无样式模式与打印样式。

组件所有可定制的外观取值都通过 CSS 变量暴露,并且只在消费点写内联回退值var(--okr-line-color, #ccc)),容器上从不声明默认值。这件事决定了三条能力:

  • 不传 theme 时外观与 vue3-okr-tree 逐像素一致——样式表是同一份,diff 仅限包名注释。
  • 变量可以写在组件根容器、任意祖先元素:root,甚至内联 style={{ '--okr-line-color': 'red' }}。类放哪儿都行,因为没有任何一处「先声明了默认值、后面就改不动」。
  • 卡片外观与主题选中态用 :where() 声明(特异度 0),所以你通过 labelClassName / currentLableClassName 传进去的单个类名始终能覆盖主题。

内置主题

'use client'

import { OkrTree, type TreeNodeData } from 'react-okr-tree'
import 'react-okr-tree/style.css'

export default function App({ data }: { data: TreeNodeData[] }) {
  return <OkrTree data={data} theme="feishu" showCollapsable />
}
主题定位覆写了什么
default与 vue-okr-tree 一致:灰线、白卡、直角、轻阴影不加任何类,也没有内置选中样式——选中态交给你用 currentLableClassName 自定义
feishu飞书 OKR 观感--okr-line-color: #dee0e3、圆角 8px、双层阴影、选中 #3370ff / #fff
dark暗色页面线 #4c4d4f、卡片 #1d1e1f + 1px solid #414243、文字 #cfd3dc、按钮与数字色 #a3a6ad、阴影加深
auto跟随系统选中态恒等于 dark;完整的 dark 变量包写在 @media (prefers-color-scheme: dark) 里,亮色模式下等同 default
minimal演示 / 打印无阴影 + 1px solid #dcdfe6 + 圆角 4px,选中 #ecf5ff / #409eff 且同色边框
colorful按层级着色,适合组织架构展示基础取值同 feishu,另按 .org-chart-node[data-level='1'..'5'] 逐级着色(#3370ff#eef3ff),6 级及以上回退透明

data-level 属性在所有主题下都输出,不只是 colorful;OKR 左右两棵树的根同为 level 1,所以两侧着色一致。colorful 是唯一含非变量规则的主题,其选中规则排在同级着色之后且特异度不低(.org-chart-node + .is-current),保证选中还能看出来。

传不在这六套里的名字是允许的(用于挂你自己的 .okr-theme-{name} 类),只是开发期会输出一条提示,避免拼错主题名时毫无视觉变化却查不到原因。

theme="auto" 跟随的是系统,不是站点主题类

auto@media (prefers-color-scheme: dark) 媒体查询。如果你的站点用 .dark 类切换明暗(Next + next-themes 的常见做法),站级切换不会改变媒体查询auto 就看起来「没生效」。这两种情况分开处理:

  • 想跟随系统:theme="auto"
  • 想跟随站点:在站级主题容器上自己覆盖 --okr-* 变量(.dark { --okr-node-bg: …; }),或按当前主题给组件传 theme="dark" / theme="default"。变量能写在任意祖先,所以这条路不需要组件配合。

下面这条切换条就是上面那句话的活样例:前六个按钮直接传 theme,最后一个「跟随站点主题」是 useTheme().resolvedTheme === 'dark' ? 'dark' : 'default'(next-themes 的站级主题), 点它的时候树会跟着站点的明暗走,而 auto 不会。

自定义主题 / 覆盖变量

/* 方式一:自定义主题名,配合 theme="brand" */
.okr-theme-brand {
  --okr-line-color: #409eff;
  --okr-node-radius: 8px;
  --okr-current-bg: #409eff;
  --okr-current-color: #fff;
}
/* 选中态用 :where() 保持零特异度,用户的 currentLableClassName 仍能覆盖 */
.okr-theme-brand :where(.org-chart-node-label-inner.is-current) {
  --okr-node-bg: var(--okr-current-bg);
  --okr-node-color: var(--okr-current-color);
}

/* 方式二:在任意祖先上直接覆盖若干变量(可叠加在内置主题之上) */
.my-page {
  --okr-gap-level: 32px;
  --okr-node-font-size: 14px;
}

React 侧写内联变量记得加引号:<div style={{ '--okr-line-color': '#409eff' } as CSSProperties}>

变量一览

「回退值」一列是 CSS 里 var() 的第二个参数。同一变量在不同使用点的回退值可以不同(组件不声明默认值,只在用它的地方兜底),下面逐项列全。

变量说明回退值
--okr-line-color连接线颜色#ccc(复选框边框 #c0c4cc、画布工具栏边框 #e0e0e0
--okr-line-width连接线宽度1px
--okr-line-radius兄弟连线拐角圆角5px
--okr-gap-level层级间距 / 连接线长度20px
--okr-gap-sibling兄弟节点交叉轴间距5px
--okr-gap-node-y水平模式下节点纵向间距10px
--okr-node-bg节点背景transparent(复选框填充 #fff
--okr-node-color节点文字颜色inherit(工具栏按钮 #333
--okr-node-border节点边框none
--okr-node-radius节点圆角0
--okr-node-padding节点内边距10px
--okr-node-font-size节点字号16px
--okr-node-shadow节点阴影0 1px 10px rgba(31, 35, 41, .08)
--okr-node-shadow-hover节点 hover 阴影0 1px 14px rgba(31, 35, 41, .12)
--okr-btn-size展开圆盘直径20px
--okr-btn-bg展开圆盘背景#fff
--okr-btn-shadow展开圆盘阴影0 0 2px rgba(0, 0, 0, .15)
--okr-btn-sign-color圆盘内 +/- 颜色--okr-line-color(再回退 #ccc
--okr-btn-text-color圆盘内子节点数字颜色#909090
--okr-current-bg选中背景(仅主题内生效)#3370ff
--okr-current-color选中文字(仅主题内生效)#fff
--okr-disabled-opacity禁用节点透明度0.6
--okr-drop-color拖拽放置指示线 / inner 描边颜色--okr-current-bg(再回退 #3370ff
--okr-focus-color键盘焦点环颜色#409eff
--okr-focus-width键盘焦点环宽度2px
--okr-anim-duration展开 / 收起过渡时长200ms,由 animateDuration prop 写成内联值
--okr-anim-easing过渡缓动cubic-bezier(.55, 0, .1, 1)okr-fade-in-linear 下为 linear

画布组件 OkrTreeViewport 另有一组:--okr-viewport-height420px)、--okr-viewport-bgtransparent)、--okr-viewport-bordernone)、--okr-viewport-radius4px)、--okr-viewport-toolbar-bgrgba(255, 255, 255, .94))、--okr-viewport-toolbar-shadow0 2px 8px rgba(0, 0, 0, .08))。

--okr-group-left-widthOkrTreeGroup 运行时测量写入,不是给用户改的,见组对齐

几个改不动的东西:连接线的部分几何值是硬编码且相互咬合的(如 OKR 左子树根 stub 的 width: 12px / left: calc(100% - 11px) / height: 10px 三元组、水平模式折叠指示短线 10px)。它们被参数化的任何尝试都会产生亚像素漂移,源码注释里也明确写了不要动。层级与间距请用 --okr-gap-* 表达。

无样式模式

unstyled 只去掉卡片外观(背景 / 边框 / 圆角 / 阴影,含 hover 态),布局与连接线原样保留,供 Tailwind 或自有设计系统接管。它刻意不动 paddingfont-sizecolor:改 padding 会移动节点盒、牵动连接线的伪元素几何。这三项继续用 --okr-node-padding / --okr-node-font-size / --okr-node-color,或直接用 labelClassName

<OkrTree data={data} unstyled labelClassName="rounded-lg border bg-white px-4 py-2 shadow-sm" />

为什么样式是手写 CSS 而不是接 Tailwind

连接线是伪元素上的像素级几何(::before / ::after 的边框与偏移量彼此咬合),工具类表达不了;而 Preflight 会重新引入全局样式污染——那正是原版 * { margin: 0; padding: 0 } 被诟病的地方。组件本体没有全局 reset:只有 .org-chart-container 自身与 5 个指定类的 margin/padding: 0,加容器内的 box-sizing: border-box。文档站这类消费方可以随意用 Tailwind,组件不行。

.okr-unstyled 那组规则用了 5 个类才压过后面的 4 类方向专属规则(同特异度时后者胜),这是照抄源项目的结果,别「优化」。

打印

@media print 下自动隐藏 +/- 圆盘与画布工具栏(纸上点不动的交互件),并去掉卡片与画布的 box-shadow(部分打印引擎会把阴影渲染成灰块、也费墨),两处都是 !important

折叠的子树按屏幕原样输出——想让整棵树都印出来,先调 handle.expandAll()。需要图片版请用画布组件的 exportImage()

减弱动效

prefers-reduced-motion: reduce 时,两份 CSS 各自按类名枚举清零 duration / delay,JS 侧同时去掉动画类与内联时长。细节见键盘导航与可访问性

本页目录