
Polar 前端开发指南从 Orbit Box 设计系统到 monorepo 工作流的完整实战【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar导读本文是 Polar 开源仓库clients/AGENTS.md沉淀的Frontend Development Guide的完整展开版。它面向两类读者一是需要在clients/monorepo 中新增页面、组件或修复缺陷的贡献者二是想了解如何用设计令牌驱动的类型安全原语取代 Tailwind 手写类的 React 前端工程师。读完本文你将掌握 Polar 客户端的日常命令工作流、以Box /为核心的 Orbit 设计系统用法含设计令牌、响应式与伪状态语法、常见布局配方以及 TanStack Query / Zustand / react-hook-form 的数据与表单模式并看到这些约定在源码中的落点。1. 总览一个以 Next.js 为骨架的前端 monorepoPolar 的客户端代码集中在clients/目录技术栈为Next.js TypeScript TanStack Query Tailwind CSS正在被 Orbit 取代。整体结构如下与文档中的 Project Structure 对应clients/ ├── adapters/ # 已发布的框架与鉴权适配器nextjs、nuxt、tanstack-start、better-auth 等 ├── apps/ │ ├── web/ # 主 Next.js 应用 │ │ └── src/ │ │ ├── app/ # App Router 页面 │ │ │ ├── (main)/ # 主导航布局dashboard、组织页 │ │ │ └── (public)/ # 公开页面 │ │ └── hooks/ # React hooks │ ├── app/ # iOS / Android 应用Expo / React Native │ └── orbit/ # Orbit 设计系统的文档与展示站点 ├── packages/ │ ├── ui/ # 共享 UI 组件含遗留的 Card、Banner │ ├── client/ # 自动生成的 API 客户端src/v1.ts 已提交 │ ├── checkout/ # 结账流程包 │ └── orbit/ # Polar 设计系统组件 设计令牌apps/web/src/app/下的路由分组清晰(main)/承载 dashboard 与组织页面(public)/承载公开页面实际源码中还包括(checkout)/、(embed)/等分组见 apps/web/src/app。新页面开发时应放入正确的路由分组并在apps/web/src/app/(main)/dashboard/的现有布局基础上扩展。2. 快速命令Polar 客户端的日常操作手册所有命令都是clients/下的根脚本由 clients/package.json 定义统一用 pnpm 执行pnpm dev # 启动开发服务器默认 http://127.0.0.1:3000 pnpm build # 生产构建 pnpm lint # 全仓库 oxlint 检查 —— 干净时无任何输出 pnpm format:check # oxfmt --checkCI 门禁覆盖 Markdown。pnpm exec oxfmt file 可修复 pnpm test # turbo 并行跑 workspace 内测试12 个包定义了 test pnpm typecheck # turbo 并行跑 tsc15 个包定义了 typecheck pnpm generate # 重新生成 API 客户端位于 packages/client2.1 命令背后的几个省时间细节冷安装很慢是正常的pnpm install会通过prepare钩子执行一次完整的turbo run build --filter./packages/*见 clients/package.json 中的prepare脚本所以冷安装约需 2.5 分钟而非 1 分钟。turbo.json中test依赖^build这是包测试前必须先构建的原因。务必用--filter缩小范围pnpm test --filter web、pnpm typecheck --filter polar-sh/orbit。不加 filter 的pnpm test会跑满 4 核容器并引发 5 秒的 vitest 超时误报。pnpm test的隐藏依赖它包含packages/cli其测试需要bun运行时、apps/appjest以及adapters/nuxt会构建一个 Nuxt fixture。单测不需要后端单元测试既不需要运行中的后端也不需要.env.local。apps/web/vitest.config.ts 在env中直接注入NEXT_PUBLIC_API_URL、NEXT_PUBLIC_FRONTEND_BASE_URL、NEXT_PUBLIC_SANDBOX_FRONTEND_BASE_URL等NEXT_PUBLIC_*变量并引入 StyleX 的 Babel 插件支持测试 JSX。只有test:e2ePlaywright需要完整可用的环境。pnpm generate的副作用它会进入 server 的 Python 环境运行scripts.generate_openapi因此需要server/.jwks.json和 email-renderer 二进制这两个会阻塞 import 的后端产物。日常开发很少需要它因为生成的 packages/client/src/v1.ts 已经提交到仓库。2.2 功能完成后清单每完成一个功能务必运行pnpm lint检查你的改动是否引入了新的错误或警告修复后再算完成。3. UI 编写铁律一切新 UI 用Box /所有新 UI 必须使用polar-sh/orbit/Box导出的Box /编写。div Tailwind 类名在布局、间距、颜色、边框、圆角、阴影、flex、grid、position 等视觉关注点上已被弃用。Box 是一个多态polymorphic、完全类型安全的基础原语把 Orbit 设计令牌作为一等 props 使用——没有 className 猜测没有亮/暗模式样板代码令牌自动解析设计系统已定义的东西不允许再用任意值。严格执行的规则新组件一律写 Box不用div做视觉容器。修改既有 Tailwind 组件时优先在同一次改动中迁移到 Box而不是继续堆 Tailwind。绝不使用裸色值 hex/oklch、裸 px 间距或dark:变体——一律用令牌。Box 上已有类型化 proppadding、background、radius 等的属性绝不退回用className。排版使用polar-sh/orbit的Text /而不是 Tailwind 的文本类。Tailwind 仅限三种场景第三方组件覆盖className 是唯一 API、Orbit 尚不支持的临时动画、迁移遗留文件时的临时胶水。4.Box /组件深度解析4.1 它到底做了什么Box 在构建期把你的类型化 props 编译为StyleX 样式 作用域 CSS。从 Box.tsx 源码 可以看到实现要点组件内部用useId()生成一个ds...作用域类名把传入 props 按BOX_STYLE_PROP_KEYS集合拆分为样式 props 与 DOM props样式 props 交给resolveBoxStyles()解析为 StyleX 结果、内联样式与响应式 CSS响应式场景会额外渲染一段style标签responsiveCSSDOM props如onClick、htmlFor、action原样透传给底层元素。令牌使用 CSS 的light-dark()函数在亮/暗模式间自动切换所以一次样式编写暗色模式免费。4.2display默认是flex约 90% 的块级元素用法都是 flex所以裸Box就是一行 flex row。span、label内联元素与lilist-item会保留原生 display不破坏语义——这正是源码中NON_FLEX_DEFAULT_ELEMENTS集合[span, label, li]的作用见 Box.tsx。传显式display如displayblock、displaygrid可覆盖默认值Box flexDirectioncolumn gapm…/Box // 已是 flex Box displayblock…/Box // 显式退出 flex4.3 导入方式import { Box } from polar-sh/orbit/BoxBox是深路径导入polar-sh/orbit/Box不从包根导出——这与Grid、Text、Button等从polar-sh/orbit根导出不同见 packages/orbit/src/index.ts。4.4 通过as实现多态Box 默认渲染div但底层元素可通过as选择以照顾语义与无障碍。允许的值;div | span | section | article | aside | main | nav | header | footer | form | fieldset | label | ul | ol | liBox assection paddingxl…/Box Box asul flexDirectioncolumn rowGaps Box asliItem/Box /Box Box asnav alignItemscenter columnGapm…/Box所选元素的 DOM props 都有类型并会被转发如label上的htmlFor、form上的action、任意元素上的onClick。5. 设计令牌两层级架构与完整取值表令牌分为两层原始值层与语义层。原始值层在 packages/orbit/src/tokens/value.stylex.ts定义字面颜色、字号、间距、圆角、阴影、断点、时长与缓动曲线。源码注释明确指出这是唯一允许出现字面色值与字号的层级gray500只是一个颜色不是 secondary text。语义层在 packages/orbit/src/tokens/semantics.stylex.ts定义带意图的名字background-primary、text-secondary等每一个值都引用原始层而非持有字面量通过light-dark(lightValue, darkValue)成对解析见 semantics.stylex.ts 源码。Box 只接受令牌名不接受裸值。注意修改这两份令牌文件需要删除clients/apps/web/.next缓存并重启 dev server源码注释中明确提示。5.1 间距令牌SpacingToken用于 padding、margin、gap。刻度会镜像出负数令牌-xs…-5xl供 margin 与偏移使用padding 和 gap 只接受正数令牌PositiveSpacingToken因为负数在 CSS 中非法这一约束在 value.stylex.ts 类型定义中强制Token值none0xs4pxs8pxm12pxl16pxxl24px2xl32px3xl48px4xl64px5xl96px5.2 颜色令牌ColorToken用于backgroundColor、color、borderColor。每个令牌自动解析亮/暗两套取值Token用途background-primary页面背景background-secondary分区/凸起表面background-card卡片/内嵌面板表面background-warning警告表面background-success成功表面background-danger危险表面text-primary主要文字text-secondary次要文字text-tertiary提示、注释、占位符text-success成功文字text-danger危险文字text-warning警告文字border-primary默认边框与分隔线border-secondary微妙/次级分隔线border-warning警告边框补充语义层还定义了background-inverse、background-accent、text-disabled、text-accent等令牌见 semantics.stylex.ts可在需要反色、强调或禁用态时直接使用。5.3 圆角 / 阴影 / 动效 / 断点圆角BorderRadiusTokennone、s8、m12、l16、xl32、full9999。阴影ShadowTokennone、s、m、l、xl具体 shadow 定义见 value.stylex.ts。动效—— 时长DurationToken与缓动EasingToken时长值缓动曲线用途instant0msstandard通用、对称fast120msdecelerate进入快 → 稳定base200msaccelerate退出稳定 → 快slow320msspring轻微过冲slower480ms时长是 CSS 变量因此动效可全局调节或为 reduced-motion 归零缓动是编译期常量cubic-bezier曲线在 value.stylex.ts 中定义。断点BreakpointKeysm640、md768、lg1024、xl1280。作为响应式 prop 对象的键使用。6. Prop 参考完整速查表每个 prop 都接受单个令牌/值或一个响应式对象见下一节。以下是文档中的完整 prop 清单可直接作为编码时的速查表。间距令牌驱动括号内为别名padding (p), paddingTop (pt), paddingRight (pr), paddingBottom (pb), paddingLeft (pl), paddingHorizontal (px), paddingVertical (py) margin (m), marginTop (mt), marginRight (mr), marginBottom (mb), marginLeft (ml), marginHorizontal (mx), marginVertical (my) // margin 令牌还接受 auto 与 // 负令牌-xs … -5xl gap (g), rowGap, columnGap颜色令牌驱动backgroundColor、color、borderColor。边框borderRadius, borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius, borderBottomRightRadius // BorderRadiusToken borderWidth, borderTopWidth, borderRightWidth, borderBottomWidth, borderLeftWidth // 数值px borderStyle: solid | dashed | dotted | none阴影boxShadow— ShadowToken。布局display: flex | grid | block | inline | inline-flex | inline-block | none | contents // 块级元素默认 flex见 4.2 overflow / overflowX / overflowY: hidden | auto | scroll | visible width, height, minWidth, maxWidth, minHeight, maxHeight: string | number // 数字 → px aspectRatio: string // 如 16 / 9Flexflex, flexDirection (row|column|row-reverse|column-reverse), flexWrap (wrap|nowrap|wrap-reverse), flexGrow, flexShrink, flexBasis, alignItems / alignSelf (start|end|center|baseline|stretch [ auto 仅 alignSelf]), justifyContent (start|end|center|between|around|evenly), alignContent (start|end|center|between|around|evenly|stretch)GridgridTemplateColumns, gridTemplateRows, gridColumn, gridRow, gridAutoFlow (row|column|dense|row-dense|column-dense), gridAutoColumns, gridAutoRowsPositionposition: relative|absolute|fixed|sticky|static top, right, bottom, left, inset: SpacingToken | string | number // 令牌可为负-l zIndex: number | stringMotion过渡配合伪状态 props 实现 hover/focus/active 动画transitionProperty: none|all|common|colors|opacity|shadow|transform transitionDuration: DurationToken // instant|fast|base|slow|slower transitionTimingFunction (别名: ease): EasingToken // standard|decelerate|accelerate|spring transitionDelay: DurationToken transform: string // 如 translateY(-2px)、scale(1.02) transformOrigin: string willChange: stringtransitionProperty关键字会展开为真实属性列表——colors→ color background-color border-colorcommon→ colors box-shadow opacity transform。未指定transitionProperty时transitionDuration作用于all。Visualopacity: number cursor: pointer|default|not-allowed|grab|grabbing|text|move|wait pointerEvents: none|auto visibility: visible|hidden userSelect: none|text|all|auto textAlign: left|center|right|justify7. 响应式与伪状态一套语法搞定断点与交互任何样式 prop 都可以接受一个对象键为base移动优先的默认值无条件生效断点键sm/md/lg/xl编译为 min-width 媒体查询伪状态键hover、focus、active、focusVisible、focusWithin生成作用域化的伪类规则。伪状态到选择器的映射:hover、:focus、:active、:focus-visible、:focus-within定义在 packages/orbit/src/utils/resolvers.ts 的PSEUDO_SELECTOR_MAP中响应式值类型则见 packages/orbit/src/utils/types.ts。Box flexDirection{{ base: column, md: row }} padding{{ base: l, lg: 2xl }} gridTemplateColumns{{ base: 1fr, md: repeat(2, 1fr), xl: repeat(4, 1fr), }} backgroundColor{{ base: background-card, hover: background-secondary }} cursor{{ hover: pointer }} /可以自由混用——base是无条件值断点键是最小宽度媒体查询伪状态键生成作用域伪类规则。8. 常见布局配方可直接复制垂直堆叠Box flexDirectioncolumn rowGapl … /Box水平一行、居中、带间距Box alignItemscenter columnGapm … /Box卡片表面Box borderRadiusl backgroundColorbackground-card borderWidth{1} borderStylesolid borderColorborder-primary paddingxl flexDirectioncolumn rowGapm Text variantheading-xs ash3 Title /Text Text colormutedDescription/Text /Box可交互卡片平滑 hover——伪状态 props 配合 transition动画是平滑过渡而非跳变Box borderRadiusl backgroundColor{{ base: background-card, hover: background-secondary }} boxShadow{{ base: s, hover: m }} transform{{ hover: translateY(-2px) }} transitionPropertycommon transitionDurationfast easedecelerate cursor{{ hover: pointer }} paddingxl … /Box响应式网格——优先用Grid原语见第 9 节它默认display: grid且使用短 prop 名Grid templateColumns{{ base: 1fr, md: repeat(2, 1fr), xl: repeat(4, 1fr) }} gapl {items.map((item) ( Card key{item.id} {...item} / ))} /Grid吸顶工具栏Box positionsticky top{0} zIndex{10} backgroundColorbackground-primary borderBottomWidth{1} borderStylesolid borderColorborder-primary paddingHorizontalxl paddingVerticalm alignItemscenter justifyContentbetween … /Box加载骨架屏Box height{128} borderRadiusm backgroundColorbackground-card classNameanimate-pulse /空状态Box flexDirectioncolumn alignItemscenter justifyContentcenter paddingVertical3xl rowGapl Text colormutedNo items found/Text Button variantsecondaryCreate First Item/Button /Box错误表面Box borderRadiusm backgroundColorbackground-warning borderWidth{1} borderStylesolid borderColorborder-warning paddingl Text{error.message}/Text /Box9.className/style逃生舱与Grid原语9.1 逃生舱允许但别滥用Box 接受className和style用于设计系统之外的东西动画 keyframes、第三方工具类、CSS Grid template areas 等。不要用它重新实现类型化 prop 已覆盖的内容——这正是这个原语要杜绝的失败模式。如果你发现自己想写classNamebg-…或classNamep-…说明你用错了 Box。当类型化 prop 缺失时先打开 clients/packages/orbit/src/utils/types.ts 确认——大多数需求都有覆盖。如果确实缺少某个 CSS 属性优先扩展BoxStyleProps必要时带令牌支持而不是走 Tailwind 逃生舱并在 PR 中说明。9.2GridBox 的 CSS grid 预设Grid默认display: grid把 grid 属性以短名称Chakra 风格重新暴露其余所有 Box propgap、padding、颜色、响应式对象等都被继承实现见 packages/orbit/src/components/Grid.tsx其本质是给 Box 传display{inline ? inline-grid : grid}并映射短 prop 名import { Grid } from polar-sh/orbit Grid templateColumnsrepeat(3, 1fr) gapm … /Grid // areas 响应式 Grid templateAreas{{ base: head main, md: head head nav main }} templateColumns{{ base: 1fr, md: 200px 1fr }} gapl /Prop 名templateColumns、templateRows、templateAreas、autoFlow、autoRows、autoColumns、column、row以及inline渲染inline-grid。需要子元素显式跨列/放置时用GridItemimport { Grid, GridItem } from polar-sh/orbit ;Grid templateColumnsrepeat(4, 1fr) gapm GridItem colSpan{2}跨两列/GridItem GridItem colStart{3} colEnd{5} rowSpan{2} 显式放置 /GridItem GridItem areasidebar按模板区域/GridItem /GridGridItempropscolSpan/rowSpan数字或auto、colStart/colEnd/rowStart/rowEnd、area——全部支持响应式外加所有 Box prop。9.3 其他 Orbit 原语有现成原语时优先使用不要手搓 Tailwind 组件import { Text } from polar-sh/orbit // 排版variant 驱动 import { Button, Grid } from polar-sh/orbit import { Avatar, SegmentedControl } from polar-sh/orbit import { Alert } from polar-sh/orbit // 着色提示info/warning/danger/success import { ButtonGroup } from polar-sh/orbit // 一或两个 primary/ghost 操作Alert接受variantinfo|warning|danger|success默认info、title和可选descriptionvariant 抽象了图标与所有颜色。它还支持loading图标换成 spinner、onDismiss渲染关闭按钮和actions一或两个ButtonGroupCTA渲染在右下角。ButtonGroup接受最多两个{ text, onClick, loading?, disabled? }组成的actions元组——第一个渲染为 primary 按钮第二个为 ghost 按钮。原则需要完全控制时用Box当某个用例已有命名原语时优先用它任何文本节点用Text操作用Button网格布局用Grid。10. 遗留 Tailwind已弃用的模式这些模式遍布旧代码但新代码中严禁使用div className…做布局/间距/颜色dark:变体——Orbit 颜色令牌自动解析硬编码颜色名如bg-blue-500、text-gray-500、dark:bg-polar-800rounded-xl、shadow-lg、p-4、gap-2等——请用 Box props 令牌。编辑遗留文件时把该文件或紧邻的组件迁移到 Box而不是扩大 Tailwind 的使用面。11. 数据获取与状态管理模式11.1 TanStack Query查询模式import { useQuery } from tanstack/react-query import { api } from /utils/api const useProducts (organizationId: string) { return useQuery({ queryKey: [products, organizationId], queryFn: () api.products.list({ organizationId }), enabled: !!organizationId, }) } // 组件内使用 const { data: products, isLoading, error } useProducts(orgId)api来自/utils/api底层是对 packages/client/src/v1.ts 生成的类型安全 API 客户端的封装。enabled: !!organizationId保证在组织 ID 就绪前不发请求。11.2 变更模式import { useMutation, useQueryClient } from tanstack/react-query const useCreateProduct () { const queryClient useQueryClient() return useMutation({ mutationFn: (data: ProductCreate) api.products.create(data), onSuccess: () { queryClient.invalidateQueries({ queryKey: [products] }) }, }) }变更成功后使相关查询键失效让列表自动刷新——这是 TanStack Query 缓存一致性的标准做法。11.3 Zustand 状态管理import { create } from zustand interface AppState { sidebarOpen: boolean toggleSidebar: () void } const useAppStore createAppState((set) ({ sidebarOpen: true, toggleSidebar: () set((state) ({ sidebarOpen: !state.sidebarOpen })), }))11.4 react-hook-form 表单处理import { useForm } from react-hook-form const MyForm () { const form useForm({ defaultValues: { name: , email: }, }) return ( form onSubmit{form.handleSubmit(onSubmit)} Input {...form.register(name)} / {form.formState.errors.name ( span classNametext-sm text-red-500 {form.formState.errors.name.message} /span )} /form ) }注意表单错误提示中的text-sm text-red-500属于遗留 Tailwind 写法新代码应改用Text /与颜色令牌。11.5 常见状态模式加载 / 空 / 错误// 加载中 if (isLoading) { return ( Box height{128} borderRadiusm backgroundColorbackground-card classNameanimate-pulse / ) } // 空状态 if (!data?.length) { return ( Box flexDirectioncolumn alignItemscenter justifyContentcenter paddingVertical3xl rowGapl textAligncenter Text colormutedNo items found/Text Button variantsecondaryCreate First Item/Button /Box ) } // 错误处理 if (error) { return ( Box borderRadiusm backgroundColorbackground-warning borderWidth{1} borderStylesolid borderColorborder-warning paddingl Text{error.message}/Text /Box ) }12. 导入规范Orbit 优先polar-sh/ui兜底// Orbit优先——设计系统原语 import { Box } from polar-sh/orbit/Box import { Text, Button, Avatar, SegmentedControl, Input, TextArea, } from polar-sh/orbit import { DataTable, Select } from polar-sh/orbit // 遗留 polar-sh/ui仅在 Orbit 无对应物时使用 import { Card } from polar-sh/ui/components/atoms/Card import { Banner } from polar-sh/ui/components/molecules/Banner13. 国际化i18n约定翻译文件位于packages/i18n/src/locales/。新增可翻译字符串时只添加到en.tspackages/i18n/src/locales/en.ts不要手动编辑其他语言文件。CI 作业会自动把新增英文串翻译成所有支持语言并提交回分支。推送en.ts的改动后等 CI 翻译作业完成再拉取分支即可。14. 源码参考索引以下路径是深入理解本指南各主题的第一手资料均为仓库根目录相对路径Box 组件实现clients/packages/orbit/src/components/Box.tsxBox prop 类型BoxStylePropsclients/packages/orbit/src/utils/types.ts样式解析器含伪状态映射clients/packages/orbit/src/utils/resolvers.ts原始值令牌间距/颜色/圆角/阴影/断点/动效clients/packages/orbit/src/tokens/value.stylex.ts语义令牌语义色与排版角色clients/packages/orbit/src/tokens/semantics.stylex.tsOrbit 桶导出clients/packages/orbit/src/index.tsGrid/GridItem实现clients/packages/orbit/src/components/Grid.tsx遗留 Cardclients/packages/ui/src/components/atoms/Card.tsx全局样式clients/apps/web/src/styles/globals.cssDashboard 布局clients/apps/web/src/app/(main)/dashboard//dashboard/)生成的 API 客户端clients/packages/client/src/v1.ts根脚本定义clients/package.json结语Polar 客户端的这套前端规范核心思想可以概括为三点用设计令牌消灭魔法值裸色值、裸 px、dark:变体一律禁用、用类型安全原语取代 className 拼接Box /的多态、响应式与伪状态语法把布局和交互写成可验证的 props、用 monorepo 工具链保证一致性turbo 并行、oxlint 门禁、生成式 API 客户端与自动化的 i18n 流水线。对于任何正在把 Tailwind 项目演进为设计系统驱动的前端团队这份指南的取舍思路都值得借鉴对于 Polar 的贡献者它则是开工前的必读手册。【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考