@animus-ui/系统
React的设计系统构建器. 由零运行时间驱动的CSS-in-JS——样式提取到建设时间的静态CSS.
安装
npm install @animus-ui/system
与提取驱动程序对等 : @animus-ui/vite-plugin, (中文(简体) ),
@animus-ui/next-plugin, (中文(简体) ), @animus-ui/unplugin,或 @animus-ui/cli。 。 。 .
快速启动
1. 确定主题
import { createTheme } from '@animus-ui/system';
const theme = createTheme()
.addBreakpoints({ sm: 480, md: 768, lg: 1024 })
.addColors({
gray: { 100: '#f0f0f0', 800: '#1a1a1a' },
blue: { 400: '#3d94ff', 700: '#003d99' },
red: { 500: '#e63946' },
})
.addColorModes('dark', {
dark: {
primary: 'blue.400',
danger: 'red.500',
bg: 'gray.800',
text: 'gray.100',
},
light: {
primary: 'blue.700',
danger: 'red.500',
bg: 'gray.100',
text: 'gray.800',
},
})
.addScale({
name: 'space',
values: { sm: '0.5rem', md: '1rem', lg: '1.5rem' },
})
.build();
type AppTheme = typeof theme;
declare module '@animus-ui/system' {
interface Theme extends AppTheme {}
}
2. 与道具组创建系统
预先建造的团体船 与包裹。 把它们编成自己的语义组:
import { createSystem } from '@animus-ui/system';
import {
background,
border,
color,
flex,
layout,
shadows,
space,
typography,
} from '@animus-ui/system/groups';
const bundle = createSystem()
.addGroup('surface', { ...color, ...border, ...shadows, ...background })
.addGroup('space', space)
.addGroup('text', typography)
.addGroup('arrange', { ...flex, ...layout })
.build();
export const { createGlobalStyles, createKeyframes } = bundle;
build() 返回捆绑,而不是系统。 关键框架收集和
全局风格的块在模块镜下的捆绑上注册
导出名称,以及 seal() 生产系统组件的构建方式如下:
export const motion = createKeyframes({
pulse: { '0%, 100%': { opacity: 1 }, '50%': { opacity: 0.6 } },
});
export const globalStyles = createGlobalStyles({
body: { margin: 0, bg: 'bg', color: 'text' },
});
export const ds = bundle
.registerKeyframes({ motion })
.registerGlobalStyles({ globalStyles })
.seal();
注册密钥必须等于导出名称, 因为提取符
决定 motion.pulse 引用该名称。 关键框架和全球风格
块共享一个词汇名空间.
要利用已公布的设计系统工具包,请从其中任一环节开始
.extend() 它将工具包的注册和信物合并到你的(kit as
基地,你后来的呼声赢得了冲突, 和提取管道
在同一边上发现工具包 :
import { system as kitSystem, theme as kitTheme } from '@acme/kit';
const theme = createTheme().extend(kitTheme).build();
const bundle = createSystem()
.extend(kitSystem)
.addProps({ cursor: { property: 'cursor' } })
.build();
createSystem({ includes: [...] }) 财务报告和审定财务报表 .from() 已贬值:它们添加
在不合并登记册的情况下加入调查组.
每个组成为一组可选入的道具,组件可以使用 .system()数字 :
const Box = ds
.styles({})
.system({ surface: true, space: true })
.asElement('div');
// Box now accepts: color, bg, border, shadow, p, m, gap, etc.
<Box bg="bg" p="md" borderBottom="1px solid" />;
3. 构筑组件
export const Alert = ds
.styles({
display: 'flex',
alignItems: 'flex-start',
p: 'md',
borderRadius: '4px',
fontSize: '14px',
lineHeight: '1.5',
})
.variant({
prop: 'variant',
variants: {
filled: { color: 'bg' },
outline: { bg: 'transparent', borderWidth: '1px', borderStyle: 'solid' },
},
})
.variant({
prop: 'intent',
variants: {
info: { bg: 'primary' },
danger: { bg: 'danger' },
},
})
.compound(
{ variant: 'outline', intent: 'info' },
{ borderColor: 'primary', color: 'primary' }
)
.compound(
{ variant: 'outline', intent: 'danger' },
{ borderColor: 'danger', color: 'danger' }
)
.states({
disabled: { opacity: 0.5, pointerEvents: 'none' },
})
.system({ space: true })
.asElement('div');
<Alert variant="filled" intent="info" m="sm" disabled />;
构建链
链条执行级联命令——每个方法映射到CSS @layer数字 :
ds.styles() → @layer anm-base
.variant() → @layer anm-variants
.compound() → @layer anm-compounds
.states() → @layer anm-states
.system() → @layer anm-system
.props() → @layer anm-custom
.asElement() → typed React component
类型系统防止调用方法出轨.
扩展组件
.extend() 在组件上启动一个继承所有
组件声明、自定义道具及其召回。 其方法
可以在任意顺序下调用; 自定义道克重新声明在 .props() 替换
仅用于此扩展及其自身的扩展.
const Card = ds
.props({
inset: { property: 'padding', transform: (v) => `${Number(v) * 4}px` },
})
.asElement('div');
export const Panel = Card.extend()
.styles({ display: 'grid' })
.asElement('section');
<Panel inset={3} />; // padding: 12px, computed by Card's callback
扩展读取从父组件继承的回调, 当其
模块运行,就像作者一样 Card.extend() 已经 有两个后果:
- 父模块必须在扩展前完成初始化 模块:无法在导入周期内宣布扩展 首先评价扩展模块.
- 客户端模块中定义的自定义道具召回的父程序
(单位:千美元)
'use client'无法从服务器模块中扩展; 声明 客户端模块中的扩展名.
倒计时值
firstThatWorks() 列出一个属性的值,优先.
提取器声明为倒置, 所以浏览器支持的最后一个
胜出,折出一跑赤裸的 var() 值变为一个嵌入式倒计时 :
import { firstThatWorks } from '@animus-ui/system';
const Header = ds
.styles({
position: firstThatWorks('sticky', '-webkit-sticky', 'fixed'),
scrollMarginTop: firstThatWorks(
'var(--header-height)',
'var(--bar-height)',
'4rem'
),
})
.asElement('header');
.animus-Header-… {
position: fixed;
position: -webkit-sticky;
position: sticky;
scroll-margin-top: var(--header-height, var(--bar-height, 4rem));
}
每个成员按财产打字,按其规模解决。 这个
以组件样式、全局样式和键框来工作。 写成
调用站点道具它只在运行时读取,运行时应用首选值
独自一人,且加以警告animus.first-that-works.unextracted) (中文(简体) ). 降低修饰符
用于浏览器目标, 如在Vite 或 Animus 自己的闪电 CSS
targets,将每个已经支持的目标降回倒计时.
锁定位置后退
createPositionTry,在已建的捆绑上
锚定位. 以出口名称登记收藏,截至
键,并使用 positionTryFallbacks数字 :
export const { createPositionTry } = bundle;
export const placements = createPositionTry({
above: { positionArea: 'top', marginBottom: '{space.8}' },
flipStart: { top: 'anchor(bottom)', insetInlineStart: 'anchor(start)' },
});
export const ds = bundle.registerPositionTry({ placements }).seal();
const Tooltip = ds
.styles({
positionAnchor: '--trigger',
positionTryFallbacks: `${placements.above}, flip-block`,
})
.asElement('div');
@layer anm-global {
@position-try --animus-pt-… {
position-area: top;
margin-bottom: 0.5rem;
}
}
.animus-Tooltip-… {
position-anchor: --trigger;
position-try-fallbacks: --animus-pt-…, flip-block;
}
每个职位都取自其描述词。 只不过什么 @position-try
接受的属性包括:设置、边距、大小和自配对属性,
positionAnchor 财务报告和审定财务报表 positionArea; 其他任何东西都是类型错误,以及
掷出. 由于数组是
反应性价值。 每个注册位置都发出,是否使用.
查看过渡
createViewTransitions,在已建的捆绑上,名称视图-过渡
课程。 每个过渡 group, (中文(简体) ), imagePair, (中文(简体) ), old 财务报告和审定财务报表 new 槽样式
其伪要素,并将关键框架的参考、标志和条件作为
一个全局风格块。 以出口名称登记收藏:
export const { createKeyframes, createViewTransitions } = bundle;
export const motion = createKeyframes({
slideIn: { from: { translate: '100% 0' } },
slideOut: { to: { translate: '-100% 0' } },
});
export const transitions = createViewTransitions({
slide: {
group: { animationDuration: '250ms', _motionReduce: { animation: 'none' } },
old: { animationName: motion.slideOut },
new: { animationName: motion.slideIn },
},
});
export const ds = bundle
.registerKeyframes({ motion })
.registerViewTransitions({ transitions })
.seal();
const Card = ds
.styles({
viewTransitionName: 'match-element',
viewTransitionClass: transitions.slide,
})
.asElement('article');
@layer anm-global {
::view-transition-group(*.animus-vt-…) {
animation-duration: 250ms;
}
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*.animus-vt-…) {
animation: none;
}
}
::view-transition-old(*.animus-vt-…) {
animation-name: animus-kf-…;
}
::view-transition-new(*.animus-vt-…) {
animation-name: animus-kf-…;
}
}
校对的指纹是其类,所以 String(transitions.slide) 还有名字
如果一个框架需要一个类,例如React's <ViewTransition>每一个
已注册的转换输出,是否使用.
相对状态选择器
marker(name) 命名要设置一个状态样式的元素的类
另一种:先祖,后人或兄弟姐妹. 将其关系登记为
选择者别名 :
import { createSystem, marker } from '@animus-ui/system';
export const row = marker('row'); // row.className === 'animus-marker-row'
const bundle = createSystem()
.addSelectors({
_rowHover: row.ancestor(':hover'),
_rowHasFocus: row.descendant(':focus-visible'),
})
.build();
<tr className={row.className}>
<Cell _rowHover={{ opacity: 1 }} />
</tr>;
| 关系 | 选择器 |
|---|---|
ancestor(':hover') | &:where(.m:hover *) |
descendant(':hover') | &:where(:has(.m:hover)) |
siblingBefore(...) | &:where(.m:hover ~ *) |
siblingAfter(...) | &:where(:has(~ .m:hover)) |
anySibling(...) | &:where(.m:hover ~ *, :has(~ .m:hover)) |
状态是伪类或属性选择器([data-open]) (中文(简体) ). 这个
标记坐在里面 :where(),因此,关系除了
组件的类,和元素本身的状态,例如 _hover, (中文(简体) ),
胜于相亲. 没有 _group* 或者说 _peer* 化名由内建而来.
颜色模式
addColorModes(initialMode, modeConfig) 输出 a [data-color-mode="…"] 块
每个模式。 可选的第三个参数选择主题为 OS 参与 :
const theme = createTheme()
.addColors({ gray: { 100: '#f0f0f0', 800: '#1a1a1a' } })
.addColorModes(
'dark',
{
dark: { bg: 'gray.800', text: 'gray.100' },
light: { bg: 'gray.100', text: 'gray.800' },
},
{
// OS preference → declared mode name.
systemPreference: { light: 'light', dark: 'dark' },
// CSS `color-scheme` per mode. The two mapped modes default to
// 'light'/'dark', so `{}` is the whole opt-in here; classify any
// additional modes explicitly.
browserColorScheme: {},
}
)
.build();
systemPreference 允许注意后退排放:
@media (prefers-color-scheme: dark) {
:root:not([data-color-mode]) {
/* the dark mode's declarations */
}
}
这个 :not([data-color-mode]) 守护是使一个明确的属性获胜的
CSS独自一人,没有剧本. 这两个值必须命名已声明的模式 .
browserColorScheme 增加中央支助系统 color-scheme 土产
滚动条,窗体控件,以及 UA 样式跟踪活动模式. 当供应时
必须分类每个声明模式,否则模式将默默继承
先前的本地方案——有一个例外: 两种模式由
systemPreference 强迫 light编号 :dark 由验证,所以他们
默认并可能被省略。 在地图绘制模式上有一个明确条目被尊重
仍然有冲突.
选择两个字节都无法给出它之前释放的字节的主题 .
"系统"是没有属性
没有 data-color-mode="system" ——OS跟随状态是属性
状态 缺席,这是上面的媒体卫士唯一能通过的价值.
system 是一个保留模式名称并被拒绝.
外表记录
模糊的外表生活在一个版本的密钥下, animus:appearance数字 :
{ "v": 1, "mode": "system" | "<mode name>", "theme": "default" }
这个 theme 轴被保留,目前被忽略——只拥有
模式轴必须保存它不拥有的字段。 包裹船
写纪律,以免手卷: @animus-ui/system/appearance 是一个
微小, 只存储的运行时间子路径( 没有反应, 没有听众, 没有 DOM —— 应用)
data-color-mode 留在你的身边:
import {
SYSTEM_MODE,
migrateLegacyModeKey,
persistColorMode,
} from '@animus-ui/system/appearance';
persistColorMode('midnight'); // read-modify-write; unowned fields survive
persistColorMode(SYSTEM_MODE); // "follow the OS" — bootstrap restores absence
// One-shot: move YOUR app's old key into the record, then delete it.
migrateLegacyModeKey('my-app-color-mode', ['midnight', 'paper']);
它拒绝降级一个新版本的唱片,并拒绝
移动共享 color-mode 密钥(一个可能属于另一个应用程序)
来源; 拖曳已读取, 只读)。 一个失败的写入被吞噬
而不是抛出,所以一个已经应用的呼叫者 data-color-mode 保留
即使存储拒绝写入,用户对该会话的选择.
打电话 migrateLegacyModeKey 后油漆,从应用程序条目中返回
迁移模式名称以申请一次访问,或 null 当什么都没有的时候
迁移。 之前在OS解析模式下
修正自己——一个接受的闪光,一次——以后的每个负载都恢复
模式预绘通过生成的靴子.
为了在第一次涂料之前恢复它, 生成一个内线片段 已建成 主题:
import { createAppearanceBootstrap } from '@animus-ui/system/bootstrap';
const { code, cspHash } = createAppearanceBootstrap(theme, {
storageKey: 'animus:appearance', // default
});
code 是文档头的无依赖性IIFE:它读了记录,
验证 mode 针对主题宣布的名称, 集 data-color-mode (单位:千美元)
有效的明确模式,以及 删除 属性 "system",一个缺失
记录,或未识别值——将控件交回媒体查询. 这个
从不写存储,从不打电话 matchMedia (实现操作系统答案)
输入属性后,它会被冻结,以适应以后的操作系统变化。 预录
纯字符串 color-mode 键被读取一次,仅在记录缺失时
从来没有写.
cspHash 授权准确的脚本。 从文物中取出信头
构建时间和 单引号 数值 - script-src 'sha256-…'。没有引用它
解析为主机源并默默地屏蔽脚本;手抄取后,便去
暂停已宣布的模式名称或存储密钥更改的时刻,并暂停
散列是一个被屏蔽的脚本和错误模式的闪光.
这个子路径正在构建工具. 它从未被组件或运行时间导入
条目,因此无法到达提取的应用程序捆绑 —— 生成
装入您捆绑配置的文物并将其交给插件
(单位:千美元)@animus-ui/vite-plugin
接受 appearanceBootstrap; 在 Next.js 中,应用程序的位置 code 本身,
这样它就可以控制 CSP 不ce和订购.
融合合同
出版自定义别名. 从已建成的系统中增加两个登记册:
import type { ConditionsOf, SelectorsOf } from '@animus-ui/system';
declare module '@animus-ui/system' {
interface Selectors extends Record<SelectorsOf<typeof ds>, true> {}
interface Conditions extends Record<ConditionsOf<typeof ds>, true> {}
}
虽然两个接口都是空的, _- 前置的块键保持允许.
任何一种增量都会使整个 _ 命名空间验证, 所以另一个
登记处的别名被拒绝,直到其扩大。 已注册的选择器
别名也作为组件调用道具类型(<Box _hoverChild={{ p: 8 }} />(三);
条件化名只是样式-块键.
关键框架. 框架接受 CSS 属性名称( mamlCase,转换为
排放时的kebab-case),原始CSS值,以及 {scale.key} 符号引用
作为 {shadows.glow-text},主题解析器可替换。 赤度键
没有牙套是逐字发射的,所以总是写出被划定的表格.
className 点菜. 在正常的路由 消费者提供
className 在生成的类后合并, 道具转发跳过
className 因为它的合并拥有它。 就是这样 className="group"
因此,祖先模式,如 .group:hover &
工作时.
出口
| 路径 | 里面有什么东西 |
|---|---|
@animus-ui/system | Full API — 构建器,主题,运行时间,类型 |
@animus-ui/system/groups | 预建道具组: space, (中文(简体) ), color, (中文(简体) ), typography, (中文(简体) ), layout, (中文(简体) ), flex, (中文(简体) ), grid, (中文(简体) ), border, (中文(简体) ), shadows, (中文(简体) ), background, (中文(简体) ), positioning, (中文(简体) ), transitions, (中文(简体) ), mode, (中文(简体) ), vars |
@animus-ui/system/compose | compose - 共用变种传播的插槽家庭 |
@animus-ui/system/compose-with-context | composeWithContext 共用道具的家庭通过反应环境旅行 requireRoot: true,其根外的成员投向发展 |
@animus-ui/system/runtime | createComponent 和组成家庭的运行时间 |
@animus-ui/system/class-resolver | createClassResolver ——无反应运行时间的类解析,面向非反应消费者 |
@animus-ui/system/bootstrap | createAppearanceBootstrap ——仅构建时间,从未通过应用程序代码导入 |
@animus-ui/system/appearance | persistColorMode, (中文(简体) ), migrateLegacyModeKey, (中文(简体) ), SYSTEM_MODE ——仅存储外观记录写入路径(运行时间,客户端安全) |
许可证
麻省理工学院