ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Sim EMCN 组件库开发规范与 Chip 体系实践:从导入边界到可访问性与键盘交互的完整指南

Sim EMCN 组件库开发规范与 Chip 体系实践:从导入边界到可访问性与键盘交互的完整指南 Sim EMCN 组件库开发规范与 Chip 体系实践从导入边界到可访问性与键盘交互的完整指南【免费下载链接】simSim is the collaborative workspace to build, deploy, and monitor AI agents and workflows. Used by 100,000 builders.项目地址: https://gitcode.com/GitHub_Trending/sim16/sim本文以packages/emcn组件库的工程规范文档packages/emcn/src/AGENTS.md为骨架结合其组件实现源码与.claude/rules下的设计规则文件系统讲解 Sim 平台 UI 组件库EMCN的开发约定如何正确导入组件、何时使用 CVA 与cn()、Chip 家族的标准外观chip chrome如何构成单一事实来源、模态框的键盘默认行为如何设计以及公开组件如何用 TSDoc 记录。读完本文你将掌握在 Sim 仓库中为工作区、设置页与各类表单贡献 UI 组件时的全部边界规则与实操模式。EMCNsim/emcn是 Sim 项目中承载工作区、设置、表单等业务界面共用的 React 组件包。packages/emcn/src/AGENTS.md用短短十余条规则划定了这个包的全部工程边界——从只能从 barrel 导入的模块约束到chip-pill 标准外观的设计 token 约定。这些规则并非孤立清单它们背后都有具体源码与测试支撑chip-chrome.ts提供所有视觉 token 的单一事实来源chip.tsx的chipVariants定义药丸几何chip-modal.tsx实现声明式字段与键盘默认值而.claude/rules/emcn-components.md与.claude/rules/sim-styling.md则给出完整组件目录与样式细则。本文将逐条拆解这些规则并深入到源码层说明为什么。一、EMCN 组件包的全貌位置、依赖与脚本在开始讨论规则之前先明确这个包在仓库中的位置与工具链。包名与版本sim/emcnprivatev0.1.0位于 packages/emcn/package.json运行时依赖sim/utils、clsx、tailwind-mergev3.6.0对等依赖peerDependencies以 Radix UI 为主——radix-ui/react-avatar、react-dialog、react-dropdown-menu、react-popover、react-switch、react-tabs、react-collapsible、react-label、react-slot、react-dismissable-layer等另含class-variance-authorityCVA、framer-motion、input-otp、next15、react^19与prismjs常用脚本type-checktsc --noEmit、testvitest run、lint/lint:checkBiome、format/format:checkBiome。组件实现分布在 packages/emcn/src 下按components/、hooks/、icons/、lib/组织。components/下既有button、input、modal这类通用组件也有规模庞大的chip 家族chip、chip-input、chip-textarea、chip-dropdown、chip-select、chip-combobox、chip-modal、chip-switch、chip-tag、chip-date-picker、chip-time-picker、chip-copy-input、chip-emails-input等。二、导入边界只从 barrel 导入子路径仅限 CSSAGENTS.md 的第一条规则是整个包最重要的模块约束Import fromsim/emcn, never from subpaths except CSS files.即业务代码只能从sim/emcn这个 barrel 入口导入组件、cn工具与 token图标从sim/emcn/icons子路径导入CSS 模块按文件路径导入。严禁从其他组件子路径深度导入。这条约束由 packages/emcn/src/index.ts 的导出结构落实export * from ./components导出全部组件对Calendar、Code、Table这类组件与图标同名的符号做了显式再导出明确 barrel 解析到组件版本图标一律从sim/emcn/icons获取导出cnlib/cn.ts与键盘辅助函数handleKeyboardActivation、isKeyboardActivationhooksuseCopyToClipboard、usePrefersReducedMotion也在 barrel 中导出。同名符号的歧义处理是个值得注意的细节index.ts注释明确指出Table图标若被当作组件渲染会绘制一个挤占兄弟节点的空表曾以表格页头 T… 闪烁 bug 的形式上线过因此 barrel 必须显式消歧。.claude/rules/emcn-components.md进一步补充了导入的具体形态Import components,cn, and tokens from thesim/emcnbarrel; icons come from thesim/emcn/iconssubpath, and CSS modules from their file path. Never deep-import other component subpaths.对应到package.json的exports映射.指向./src/index.ts./icons指向./src/icons/index.ts./*通配子路径用于 CSS 模块等。三、可访问性基座优先使用 Radix UI 原语Use Radix UI primitives for accessibility where applicable.package.json中一长串radix-ui/*对等依赖印证了这一点对话框react-dialog、下拉菜单react-dropdown-menu、弹出层react-popover、开关react-switch、标签页react-tabs、折叠面板react-collapsible、头像react-avatar等均以 Radix 为底。以 chip-dropdown.tsx 为例它直接组合DropdownMenu/DropdownMenuContent/DropdownMenuItem/DropdownMenuTrigger并额外处理了模态场景通过useContext(InsideModalContext)判断自身是否位于对话框内位于对话框内时将菜单切换为 modal 模式避免非模态菜单被对话框的pointer-events: none与焦点陷阱锁死。在自定义实现层面可访问性同样被显式设计chip-dropdown.tsx的触发器支持aria-label与aria-labelledby且文档明确提醒aria-labelledby会替换由内容推导的可用名称引用外部标签时必须把触发器自身 id 一并拼入${labelId} ${triggerId}否则选中值会从无障碍名称中丢失chip-modal.tsx为字段推导aria-required、aria-invalid、aria-describedby并交给自定义字段通过函数式 children 传递。四、CVA 与 cn()变体选型的判据AGENTS.md 对类名组合策略给出明确判据Use CVA when a component has 2 variants; use directclassNamecomposition for single-style components. Export both the component and its variants helper when using CVA.即两个及以上变体 → CVAclass-variance-authority单一布尔状态 → 直接用cn()拼接。使用 CVA 时必须同时导出组件与其variants辅助函数。chip.tsx是 CVA 的典型范例chipVariants用cva()定义variantdefault/filled/primary/destructive/border-shadow/border、shapedefault/round、active、fullWidth四个维度并在文件末尾导出Chip、ChipLink与chipVariants。而chip-input.tsx的error只是单个布尔值因此直接用cn(..., error border-[var(--text-error)])处理不引入 CVA。cn()本身并非普通 clsx见 packages/emcn/src/lib/cn.tsimport { extendTailwindMerge } from tailwind-merge const twMerge extendTailwindMerge({ extend: { classGroups: { font-size: [{ text: [micro, caption, small, md] }], }, }, }) export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) }关键点tailwind-merge 采用 v3对应 Tailwind v4 工具面并扩展了font-size分组把 Sim 自有的字号 tokentext-micro/text-caption/text-small/text-md注册为字号而非颜色——否则text-small与text-sm不视为冲突后传入的类无法覆盖前者最终由 CSS 源码顺序而非调用方决定样式。五、Chip 家族平台首选 chrome 与单一事实来源AGENTS.md 的核心主张是Chip 家族是平台的主交互外观primary chrome正在渐进取代旧原语Input→ChipInput、Textarea→ChipTextarea、Modal→ChipModal、Select/Combobox→ChipSelect/ChipCombobox/ChipDropdown、Switch→ChipSwitch、日期字段→ChipDatePicker。.claude/rules/emcn-components.md中列出了完整的替换映射。5.1 视觉 token 的单一事实来源chip-chrome.tsAGENTS.md 与组件目录文档都强调绝不要手写药丸pill的裸类名——字符串会过时。必须从 packages/emcn/src/components/chip/chip-chrome.ts 组合该文件是 chip chrome 的单一事实来源所有 token 均从sim/emcnbarrel 再导出。核心 token 一览附源码行号Token语义源码位置chipFilledFillTokens无边框填充面--surface-5亮 /--surface-4暗chip-chrome.ts#L2chipFilledSurfaceTokens带--border-1边框的填充面供 chip 字段ChipInput/ChipTextarea使用chip-chrome.ts#L9chipPrimaryFillTokens主反相填充深底反白字暗色模式镜像chip-chrome.ts#L16-L17chipRadiusClass默认圆角rounded-lgchip-chrome.ts#L19chipFieldSurfaceClass字段共用填充面圆角 填充 边框 transition-colorschip-chrome.ts#L21chipFieldTextClass字段排版常规字重、--text-body、placeholder用--text-muted、无焦点轮廓chip-chrome.ts#L41-L42chipContentGap图标↔标签间距gap-1.5chip-chrome.ts#L50chipGeometryUnroundedClass/chipGeometryClass药丸几何30px 高、居中、px-2、text-sm、rounded-lgchip-chrome.ts#L58、L66chipContentIconClass内容图标16px、shrink-0、--text-iconchip-chrome.ts#L68chipContentLabelClass内容标签min-w-0、whitespace-nowrap、--text-body、text-smchip-chrome.ts#L70-L71chipHoverSurfaceClass/chipActiveSurfaceClass行状态对hover 与选中互斥选中行在 hover 时保持自身表面chip-chrome.ts#L83、L85chipDropTargetSurfaceClass第三态拖放目标填充--surface-active 1px 轮廓环chip-chrome.ts#L99disclosureChevronClass侧栏/树行折叠 chevron14px、--text-icon、150ms 过渡chip-chrome.ts#L110-L111chipIconSlotClass16px 图标槽保证行标签基线对齐chip-chrome.ts#L113cellIconNodeClass强制预渲染图标节点到 14px 资源行标准表行用 14px非药丸的 16pxchip-chrome.ts#L128其中chipFieldTextClass的[letter-spacing:inherit]是一个反直觉但重要的细节UA 样式表会把表单控件钉在letter-spacing: normal若不撤销chip 字段文本与周围标签的字距不一致且任何透明字段覆盖镜像的叠层都会逐字符偏离导致光标随长度增长而漂移。5.2 标准外观canonical look综合 AGENTS.md、emcn-components.md与sim-styling.mdchip 药丸的标准外观为常规字重400font-normal永不使用font-medium/font-semibold值文本用--text-body图标用--text-iconsize-[14px]是应用图标默认值chipContentIconClass当前在源码中定义为 16px而cellIconNodeClass将资源表行图标强制到 14px两者按表面类型区分占位符用--text-mutedtransition-colors用于交互态无焦点环——以插入符caret标记焦点填充面为亮色--surface-5/ 暗色--surface-4外加--border-1边框。菜单表面与药丸刻意区分dropdown-menu.tsx的菜单项使用text-small与gap-2菜单约定不是 chip 药丸二者不得混用。5.3 Chip / ChipLink药丸按钮与变体语义chip.tsx 是 30px 药丸的实现Chip渲染buttonChipLink渲染 Next.jsLinkchipVariants({...})可供任意其他元素div rolebutton、DropdownMenuTrigger asChild内层复用。值得注意的变体语义隐式默认变体是裸药丸透明底、hover 时--surface-hover省略variant即可禁止写variantdefaultfilled故意不是Chip的变体——它保留给 chip 字段/触发器ChipInput/ChipDropdown/ChipSelect/ChipDatePicker这些触发器再通过TRIGGER_BORDER_CLASS自行加--border-1轮廓primary反相面、destructive错误 token 面、border-shadow类卡片浮起面、border纯 box-shadow 描边无 CSS border、无填充选中/切换态用activeprop而非变体支持leftIcon/rightIcon、leftAdornment/rightAdornment、fullWidth、shaperounded-fullChip 不携带任何外边距——芯片间距是父容器的gap责任。曾经的mx-0.5默认与flush退出开关已被移除不得复活也不得通过className给 chip 加 margin。hover 类放在active键控的 compoundVariants 中而非 base 串里是为了保证 rest/hover 类互斥——一个 chip 至多渲染一个hover-hover:bg-*使裸chipVariants(...)消费者与cn(chipVariants(...))消费者行为一致。5.4 字段家族ChipInput、ChipTextarea、ChipCopyInputChipInputchip-input.tsx单行文本字段chrome 挂在 wrapper 上内部input透明icon14px、--text-icon、endAdornment尾部 reveal/copy 按钮、error边框换错误 token、inputClassName作用于内层 input如font-mono与className作用于 wrapper职责分明。ref 转发到内层input。ChipTextarea多行兄弟error、resizable默认关、viewOnly全不透明度只读 默认光标。ChipCopyInput只读字段标准形态——全不透明度只读 尾部复制按钮。view-only 是展示模式而非禁用态用户不可编辑的值应优先用它或ChipModalField typecopy而不是灰掉的disabled输入框。六、选择器与输入类组件ChipDropdown、ChipSelect、ChipCombobox、ChipSwitch、ChipTagChipDropdown药丸触发器 菜单。通过判别联合multipleprop 在一个组件内支持单选与多选不是两个组件。多选模式菜单保持打开、每个选项带尾部勾选、可选all重置行与搜索框单值模式下matchTriggerWidth默认 true使菜单宽度对齐触发器宽度。触发器通过chipVariants复用Chip的视觉尾部 chevron 归组件所有没有rightIconprop渲染在 16px 槽内以匹配leftIcon的包围盒。多选时value为空数组读作全部/无过滤通过showAllOption{false}可切到真·未选择语义。ChipSelect/ChipCombobox基于Combobox的选择器支持搜索、分组、多选用于比ChipDropdown更复杂的列表。ChipSwitch分段药丸控件由chipVariants构建如chip-modal.tsx的ChipModalTabs用它渲染标签页切换。ChipTag20px 内联标签/徽章mono/gray/invite不是药丸触发器。ChipDatePicker/ChipTimePickerchip 风格日期字段时间字段是分钟粒度宽松解析输入9:47、947、2:05pm、14:30Enter/blur 提交并以9:47 AM规范标签重渲染。ChipEmailsInput多邮箱 chip 列表输入内部化 chip 渲染、去重、格式校验、粘贴与 Backspace 处理。DropdownMenu标准上下文/动作菜单Radix 支撑不是 chip但却是命令/动作列表的标准菜单——优先于手写 popover。七、ChipModal声明式紧凑弹窗与字段所有权ChipModalchip-modal.tsx面向 invite / share / connect 式紧凑流程。组合方式与Modal/ModalHeader/ModalBody/ModalFooter镜像ChipModalHeader、ChipModalBody、ChipModalField、ChipModalFooter作为 children 拼装。7.1 ChipModalFieldtype 判别联合字段拥有全部 chrome核心原则字段的type决定控件并拥有全部 chrome消费者只描述意图绝不向内部控制传递variant/className/id。ChipModalFieldProps是八个分支的判别联合chip-modal.tsx#L720-L728type控件备注inputChipInput支持inputTypetext/password/url/tel/search/number、inputMode、mono、maxLength、autoCompletepassword走密码专用控件失焦掩码、聚焦揭示、eye 切换emailChipInput typeemail单行 Enter 提交语义与input一致textareaChipTextarearows、minHeight、resizable、viewOnly、monocopyChipCopyInput只读值 复制按钮dropdownChipDropdownfullWidth选项别名ChipDropdownOptionfile文件拖放区accept、multiple、label、description、loadingemailsChipEmailsInput恒用block高表面与 textarea 字段对齐custom任意 JSX 或函数逃生舱函数形式接收字段推导的 ARIA字段渲染Labeltext-small、--text-muted、常规字重、控件、hint/errortext-caption。每个 body 字段都必须是ChipModalField——对齐数学是硬性的ChipModalBody施加px-2gap-4ChipModalField再叠加px-2每个字段落在有效px-4与px-4的 header/footer 精确对齐手写行会跳过字段 gutter 而错位在px-2该 bug 曾真实上线于 scheduled-tasks 的 Create new scheduled task 弹窗。inputTypenumber的一个实践忠告数字字段通常应使用inputTypetextinputModenumeric因为原生 number 类型会渲染浏览器步进器在扁平 chip 表面上画自己的 chrome且对任何非法值返回调用方无法区分空字段与被拒绝的按键。八、模态框键盘默认行为把 Enter 意图声明在动作承载原语上.claude/rules/emcn-components.md的 Modal keyboard defaults 一节与chip-modal.tsx的实现共同定义了这套交互契约。总原则在承载动作的原语上声明键盘意图绝不添加 document 级或调用点的 Enter 监听器。ChipModalFooter默认defaultActionprimary在规范单行字段或自定义纯输入框中按普通 Enter 触发启用的主操作。用none要求必须显式点击如不可逆破坏性操作或嵌套控件自拥 Enter 的编辑器dismiss仅在关闭确实是该弹窗的默认决策时使用。ChipConfirmModal默认defaultActiondismissfail-safe。只有经过审计、影响低、可逆或非破坏性的决策才 opt in 到confirm。删除工作流、表格、知识库、文件夹等聚合资源即使可恢复也保持dismiss——因为该动作会令庞大的依赖图离线。none用于键入确认与严重的账户/所有权/访问变更。按钮颜色永远不决定键盘行为。Textarea、原生表单、按钮、链接、combobox、菜单、listbox、tag/email 输入、IME 组合、修饰 Enter、禁用或进行中的动作保持原生行为原生表单仍是唯一提交路径浏览器校验不被绕过。自定义字段若包含搜索框、token 编辑器或自拥 Enter 的输入必须在ChipModalField上设submitOnEnter{false}不得附加重复onKeyDown去调 footer 动作。初始焦点优先第一个可见可编辑文本控件无文本控件时聚焦声明的真实按钮none聚焦对话框表面。实现侧chip-modal.tsx#L100-L191isPlainEnter严格排除修饰键、重复、IME 组合与defaultPreventedfocusChipModalDefaultAction处理onOpenAutoFocus先找文本输入再按 default → dismiss → dialog 表面依次回退handleChipModalEnter作为内容级兜底刻意窄化到非表单、未禁用、无自有 Enter 属性list/aria-autocomplete/aria-haspopup/combobox/listbox/menu 内的单行 input并对CHIP_MODAL_INPUT_TYPES_WITH_OWN_ENTER集合内的原生类型放行。九、文本溢出OverflowText 与 fade-only 裁剪AGENTS.md 与样式规则都对单行只读标签的溢出给出明确方案overflow-text.tsx 的OverflowText是只读人类标签/标题的标准溢出处理。它拥有min-w-0、fade-only 裁剪绝不使用省略号、条件 18px 边缘遮罩以及全值浮动 tooltip消费者只能通过className传布局与排版overflowTextClipClass无 fade 的完整裁剪与overflowTextFadeClass完整 fade 处理用于罕见的、必须自持测量的组件两者都不得与truncate/text-ellipsis或 hover 时移除遮罩组合菜单标签用DropdownMenuItemLabel图标、勾选、快捷键旁的标签非可编辑Combobox通过overlayLabel传递完整视觉值普通truncate仅保留给可编辑值、代码/日志/路径内容、密集或虚拟化网格以及无法提供纯文本 tooltip 的富复合内容多行副本使用有意的line-clamp-*。OverflowText的实现细节useIsOverflowing检测裁剪useFloatingTooltip管理 tooltip 状态focusTargetnearest-interactive时把键盘焦点让给最近的交互祖先a[href]、button、[rolebutton]、[role^menuitem]、非 -1 的tabindex。十、样式细则token、字重、行宽与 CSS 变量校验.claude/rules/sim-styling.md是颜色 token 与图标尺寸约定的规范来源packages/emcn/src/AGENTS.md 明确要求遵循它而非复述。颜色 token值文本--text-body占位符/标签--text-muted图标--text-icon中性边框--border--border-1/--border-muted是解析到它的历史别名--divider已退役表面--surface-5亮/--surface-4暗选中行--surface-active错误--text-error。chip 表面无焦点环。字重三档font-normal400文档默认正文/chip 标签/侧栏/标题一律继承不写类、font-medium500、font-semibold600。禁止任意权重font-[380]等与内联fontWeightTailwind preflight 将h1–h6重置为继承权重400 的标题是预期外观而非 bug。精确值 vs 命名 token几何用精确值h-[26px]而非h-6字号永远是命名 tokentext-sm、text-caption绝不用text-[14px]只设 font-size 却继承不同 line-height。字号刻度apps/sim/app/_styles/globals.css的themetext-micro10px、text-xs11px、text-caption12px、text-small13px、text-base15px、text-sm14pxTailwind 默认。字段标题用text-smallhint/error 用text-caption。图标默认size-[14px]等宽高用size-*。行宽中性边框几何来自--border-width默认 1px2dppx 下 0.5px 真发丝线调整观感权重在--border上调色绝不动--border-width画线用真实border-*工具禁止shadow-[inset_0_-1px_0_…]手写线。条件类组合统一走cn()isActive active-classes。AGENTS.md 还专门警告必须验证 CSS 变量存在——未定义的 var 解析为currentColor曾引发真实的黑边框 bug。十一、写作原则与工程质量11.1 组件编写守则综合 AGENTS.md 与emcn-components.md的 Authoring principles共享 chrome 单一事实来源从chip-chrome.ts/chipVariants组合绝不复制 chrome 字符串单个状态切换用cn()真正的多变体用 CVA孤立error布尔是cn()不是 CVA 变体模式用判别联合 props如multiple、模态字段type而非近重复组件迁移后删除旧变体不留死路径该范式已移除Input variantchip与ChipMultiSelect公开组件用 TSDoc 使用示例记录chip.tsx中每个导出都有example如ChipLink href/integrations active{isCurrent} leftIcon{ArrowLeft}Integrations/ChipLink交互 hover/active 态用transition-colors。11.2 测试与验证组件包内配备 vitest 测试覆盖行为与可访问性细节chip-dropdown.test.tsx、chip-select.test.ts、chip-select.dom.test.tsx选择器的单/多选、搜索与 DOM 行为chip-modal.test.tsx 与 modal-interactions.test.tsx对话框交互与键盘语义chip-switch.test.tsx、chip-emails-input.test.tsx、chip-tag 等overflow-text.test.tsx、code.test.tsx、calendar.test.tshooks 与工具use-copy-to-clipboard.test.ts、cn.test.ts。运行方式在仓库根执行bun run test --filter emcn或进入包目录执行bun test/bunx vitest run类型检查bun run type-checkBiome 检查bun run lint:checklint会写入格式化。十二、迁移路线图与常见陷阱速查作为收尾将实践中反复出现、且被规则明文禁止的陷阱汇总为速查表陷阱正确做法从sim/emcn/components/chip/chip深导入从sim/emcnbarrel 导入手写h-[30px] rounded-lg bg-[var(--surface-5)] border px-2 text-sm组合chip-chrome.tstoken 或chipVariantsvariantfilled用在Chip上filled保留给字段/触发器Chip用默认/primary/destructive/border-shadow/border给 chip 加 margin或复活mx-0.5间距交给父容器gap弹窗 body 里手写字段行divp标题 裸ChipInput全部改用ChipModalField含typecustom在不可编辑值上用disabled灰掉输入框用ChipCopyInput/ChipModalField typecopy/viewOnly只读标签用truncate/text-ellipsis用OverflowTextfade-only给自定义字段附加onKeyDown调 footer 动作设submitOnEnter{false}或onSubmit给 chip 表面加焦点环保持无环靠 caret 标记焦点手写font-[380]等任意权重只用 400/500/600 三档这套规则之所以有效是因为每个禁止背后都有一个真实的线上 bug 或一致性事故错位的px-2字段 gutter、导致双选感的 hover/active 表面、letter-spacing 漂移的光标、解析为currentColor的黑边框、表头 T… 闪烁的图标歧义。理解AGENTS.md就等于拿到了避免在 Sim 平台上重蹈这些覆辙的完整检查清单。【免费下载链接】simSim is the collaborative workspace to build, deploy, and monitor AI agents and workflows. Used by 100,000 builders.项目地址: https://gitcode.com/GitHub_Trending/sim16/sim创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表