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 为骨架的前端 monorepo
Polar 的客户端代码集中在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 --check,CI 门禁;覆盖 Markdown。`pnpm exec oxfmt <file>` 可修复 pnpm test # turbo 并行跑 workspace 内测试(12 个包定义了 test) pnpm typecheck # turbo 并行跑 tsc(15 个包定义了 typecheck) pnpm generate # 重新生成 API 客户端(位于 packages/client)2.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/app(jest)以及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:e2e(Playwright)需要完整可用的环境。 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 上已有类型化 prop(padding、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>标签(responsiveCSS); - DOM props(如
onClick、htmlFor、action)原样透传给底层元素。
令牌使用 CSS 的light-dark()函数在亮/暗模式间自动切换,所以一次样式编写,暗色模式免费。
4.2display默认是flex
约 90% 的块级元素用法都是 flex,所以裸<Box>就是一行 flex row。span、label(内联元素)与li(list-item)会保留原生 display,不破坏语义——这正是源码中NON_FLEX_DEFAULT_ELEMENTS集合(['span', 'label', 'li'])的作用(见 Box.tsx)。传显式display(如display="block"、display="grid")可覆盖默认值:
<Box flexDirection="column" gap="m">…</Box> // 已是 flex <Box display="block">…</Box> // 显式退出 flex4.3 导入方式
import { Box } from '@polar-sh/orbit/Box'Box是深路径导入(@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' | 'li'<Box as="section" padding="xl">…</Box> <Box as="ul" flexDirection="column" rowGap="s"> <Box as="li">Item</Box> </Box> <Box as="nav" alignItems="center" columnGap="m">…</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 | 值 |
|---|---|
none | 0 |
xs | 4px |
s | 8px |
m | 12px |
l | 16px |
xl | 24px |
2xl | 32px |
3xl | 48px |
4xl | 64px |
5xl | 96px |
5.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 圆角 / 阴影 / 动效 / 断点
圆角(BorderRadiusToken):none、s(8)、m(12)、l(16)、xl(32)、full(9999)。
阴影(ShadowToken):none、s、m、l、xl(具体 shadow 定义见 value.stylex.ts)。
动效—— 时长(DurationToken)与缓动(EasingToken):
| 时长 | 值 | 缓动 | 曲线用途 |
|---|---|---|---|
instant | 0ms | standard | 通用、对称 |
fast | 120ms | decelerate | 进入(快 → 稳定) |
base | 200ms | accelerate | 退出(稳定 → 快) |
slow | 320ms | spring | 轻微过冲 |
slower | 480ms |
时长是 CSS 变量,因此动效可全局调节(或为 reduced-motion 归零);缓动是编译期常量(cubic-bezier曲线在 value.stylex.ts 中定义)。
断点(BreakpointKey):sm(640)、md(768)、lg(1024)、xl(1280)。作为响应式 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 / 9'Flex:
flex, 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')Grid:
gridTemplateColumns, gridTemplateRows, gridColumn, gridRow, gridAutoFlow ('row'|'column'|'dense'|'row-dense'|'column-dense'), gridAutoColumns, gridAutoRowsPosition:
position: '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-color;common→ colors + box-shadow + opacity + transform。未指定transitionProperty时,transitionDuration作用于all。
Visual:
opacity: 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'|'justify'7. 响应式与伪状态:一套语法搞定断点与交互
任何样式 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 flexDirection="column" rowGap="l"> … </Box>水平一行、居中、带间距:
<Box alignItems="center" columnGap="m"> … </Box>卡片表面:
<Box borderRadius="l" backgroundColor="background-card" borderWidth={1} borderStyle="solid" borderColor="border-primary" padding="xl" flexDirection="column" rowGap="m" > <Text variant="heading-xs" as="h3"> Title </Text> <Text color="muted">Description</Text> </Box>可交互卡片(平滑 hover)——伪状态 props 配合 transition,动画是平滑过渡而非跳变:
<Box borderRadius="l" backgroundColor={{ base: 'background-card', hover: 'background-secondary' }} boxShadow={{ base: 's', hover: 'm' }} transform={{ hover: 'translateY(-2px)' }} transitionProperty="common" transitionDuration="fast" ease="decelerate" cursor={{ hover: 'pointer' }} padding="xl" > … </Box>响应式网格——优先用Grid原语(见第 9 节),它默认display: grid且使用短 prop 名:
<Grid templateColumns={{ base: '1fr', md: 'repeat(2, 1fr)', xl: 'repeat(4, 1fr)' }} gap="l" > {items.map((item) => ( <Card key={item.id} {...item} /> ))} </Grid>吸顶工具栏:
<Box position="sticky" top={0} zIndex={10} backgroundColor="background-primary" borderBottomWidth={1} borderStyle="solid" borderColor="border-primary" paddingHorizontal="xl" paddingVertical="m" alignItems="center" justifyContent="between" > … </Box>加载骨架屏:
<Box height={128} borderRadius="m" backgroundColor="background-card" className="animate-pulse" />空状态:
<Box flexDirection="column" alignItems="center" justifyContent="center" paddingVertical="3xl" rowGap="l" > <Text color="muted">No items found</Text> <Button variant="secondary">Create First Item</Button> </Box>错误表面:
<Box borderRadius="m" backgroundColor="background-warning" borderWidth={1} borderStyle="solid" borderColor="border-warning" padding="l" > <Text>{error.message}</Text> </Box>9.className/style逃生舱与Grid原语
9.1 逃生舱:允许,但别滥用
Box 接受className和style,用于设计系统之外的东西:动画 keyframes、第三方工具类、CSS Grid template areas 等。不要用它重新实现类型化 prop 已覆盖的内容——这正是这个原语要杜绝的失败模式。如果你发现自己想写className="bg-…"或className="p-…",说明你用错了 Box。
当类型化 prop 缺失时:先打开 clients/packages/orbit/src/utils/types.ts 确认——大多数需求都有覆盖。如果确实缺少某个 CSS 属性,优先扩展BoxStyleProps(必要时带令牌支持),而不是走 Tailwind 逃生舱,并在 PR 中说明。
9.2Grid:Box 的 CSS grid 预设
Grid默认display: grid,把 grid 属性以短名称(Chakra 风格)重新暴露;其余所有 Box prop(gap、padding、颜色、响应式对象等)都被继承(实现见 packages/orbit/src/components/Grid.tsx,其本质是给 Box 传display={inline ? 'inline-grid' : 'grid'}并映射短 prop 名):
import { Grid } from '@polar-sh/orbit' <Grid templateColumns="repeat(3, 1fr)" gap="m"> … </Grid> // areas + 响应式 <Grid templateAreas={{ base: '"head" "main"', md: '"head head" "nav main"' }} templateColumns={{ base: '1fr', md: '200px 1fr' }} gap="l" />Prop 名:templateColumns、templateRows、templateAreas、autoFlow、autoRows、autoColumns、column、row,以及inline(渲染inline-grid)。
需要子元素显式跨列/放置时用GridItem:
import { Grid, GridItem } from '@polar-sh/orbit' ;<Grid templateColumns="repeat(4, 1fr)" gap="m"> <GridItem colSpan={2}>跨两列</GridItem> <GridItem colStart={3} colEnd={5} rowSpan={2}> 显式放置 </GridItem> <GridItem area="sidebar">按模板区域</GridItem> </Grid>GridItemprops:colSpan/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接受variant(info|warning|danger|success,默认info)、title和可选description;variant 抽象了图标与所有颜色。它还支持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-800; rounded-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 = create<AppState>((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 className="text-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} borderRadius="m" backgroundColor="background-card" className="animate-pulse" /> ) } // 空状态 if (!data?.length) { return ( <Box flexDirection="column" alignItems="center" justifyContent="center" paddingVertical="3xl" rowGap="l" textAlign="center" > <Text color="muted">No items found</Text> <Button variant="secondary">Create First Item</Button> </Box> ) } // 错误处理 if (error) { return ( <Box borderRadius="m" backgroundColor="background-warning" borderWidth={1} borderStyle="solid" borderColor="border-warning" padding="l" > <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/Banner'13. 国际化(i18n)约定
翻译文件位于packages/i18n/src/locales/。新增可翻译字符串时,只添加到en.ts(packages/i18n/src/locales/en.ts),不要手动编辑其他语言文件。CI 作业会自动把新增英文串翻译成所有支持语言并提交回分支。推送en.ts的改动后,等 CI 翻译作业完成再拉取分支即可。
14. 源码参考索引
以下路径是深入理解本指南各主题的第一手资料(均为仓库根目录相对路径):
- Box 组件实现:clients/packages/orbit/src/components/Box.tsx
- Box prop 类型(
BoxStyleProps):clients/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.ts
- Orbit 桶导出:clients/packages/orbit/src/index.ts
Grid/GridItem实现:clients/packages/orbit/src/components/Grid.tsx- 遗留 Card:clients/packages/ui/src/components/atoms/Card.tsx
- 全局样式:clients/apps/web/src/styles/globals.css
- Dashboard 布局: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),仅供参考