@omnific/atelier 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +10 -1
  2. package/dist/box/Box.d.ts +7 -0
  3. package/dist/box/Box.js +28 -0
  4. package/dist/box/constants.js +8 -0
  5. package/dist/box/index.d.ts +3 -0
  6. package/dist/box/index.js +2 -0
  7. package/dist/box/types.d.ts +48 -0
  8. package/dist/button/Button.js +1 -1
  9. package/dist/button/LoadingIcon.js +1 -1
  10. package/dist/main.d.ts +4 -1
  11. package/dist/main.js +2 -1
  12. package/dist/styles.css +76 -80
  13. package/dist/system/create-system.d.ts +9 -0
  14. package/dist/system/create-system.js +21 -0
  15. package/dist/system/css-vars.d.ts +52 -0
  16. package/dist/system/css-vars.js +112 -0
  17. package/dist/system/default-system.d.ts +7 -0
  18. package/dist/system/default-system.js +9 -0
  19. package/dist/system/defaults/colors.d.ts +181 -0
  20. package/dist/system/defaults/colors.js +68 -0
  21. package/dist/system/defaults/control-heights.d.ts +16 -0
  22. package/dist/system/defaults/control-heights.js +11 -0
  23. package/dist/system/defaults/focus-rings.d.ts +10 -0
  24. package/dist/system/defaults/focus-rings.js +7 -0
  25. package/dist/system/defaults/font-sizes.d.ts +13 -0
  26. package/dist/system/defaults/font-sizes.js +10 -0
  27. package/dist/system/defaults/fonts.d.ts +10 -0
  28. package/dist/system/defaults/fonts.js +7 -0
  29. package/dist/system/defaults/index.d.ts +422 -0
  30. package/dist/system/defaults/index.js +28 -0
  31. package/dist/system/defaults/radii.d.ts +40 -0
  32. package/dist/system/defaults/radii.js +19 -0
  33. package/dist/system/defaults/shadows.d.ts +31 -0
  34. package/dist/system/defaults/shadows.js +16 -0
  35. package/dist/system/defaults/spacing.d.ts +112 -0
  36. package/dist/system/defaults/spacing.js +43 -0
  37. package/dist/system/define-tokens.d.ts +7 -0
  38. package/dist/system/define-tokens.js +7 -0
  39. package/dist/system/index.d.ts +17 -0
  40. package/dist/system/index.js +16 -0
  41. package/dist/system/merge-tokens.d.ts +20 -0
  42. package/dist/system/merge-tokens.js +27 -0
  43. package/dist/system/types.d.ts +73 -0
  44. package/dist/system/use-theme.d.ts +31 -0
  45. package/dist/system/use-theme.js +39 -0
  46. package/package.json +13 -3
package/README.md CHANGED
@@ -4,11 +4,20 @@ A refined React component library for Omnific.
4
4
 
5
5
  ```tsx
6
6
  import { Button } from '@omnific/atelier';
7
+ import { createSystem, defaultConfig, useTheme } from '@omnific/atelier/system';
7
8
  import '@omnific/atelier/styles.css';
8
9
 
9
- <Button variant='primary'>Continue</Button>;
10
+ const system = createSystem('app', defaultConfig);
11
+
12
+ function App() {
13
+ useTheme(system); // injects CSS variables onto :root
14
+ return <Button variant='primary'>Continue</Button>;
15
+ }
10
16
  ```
11
17
 
18
+ Token values come from `createSystem` and are injected at runtime by `useTheme`.
19
+ Component layout/theme CSS still ships via `@omnific/atelier/styles.css`.
20
+
12
21
  ## Storybook
13
22
 
14
23
  Curated component examples live under `packages/atelier/__stories__` and
@@ -0,0 +1,7 @@
1
+ import { BoxProps } from "./types.js";
2
+ //#region box/Box.d.ts
3
+ /**
4
+ * 通用样式容器,通过 token 直接写入 CSS 属性应用 padding / 背景 / 圆角 / 边框色 / 阴影。
5
+ */
6
+ export declare const Box: (props: BoxProps) => import("react").JSX.Element;
7
+ //#endregion
@@ -0,0 +1,28 @@
1
+ import { boxPrefixCls } from "./constants.js";
2
+ import { styleFromTokens } from "../system/css-vars.js";
3
+ import { clsx } from "clsx";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region box/Box.tsx
6
+ /**
7
+ * 通用样式容器,通过 token 直接写入 CSS 属性应用 padding / 背景 / 圆角 / 边框色 / 阴影。
8
+ */
9
+ const Box = (props) => {
10
+ const { bg, border = false, borderColor, children, className, p, px, py, ref, rounded, shadow, style, ...rest } = props;
11
+ return /* @__PURE__ */ jsx("div", {
12
+ ...rest,
13
+ className: clsx(boxPrefixCls, border && `${boxPrefixCls}-border`, className),
14
+ ref,
15
+ style: styleFromTokens([
16
+ ["padding", p],
17
+ ["paddingInline", px],
18
+ ["paddingBlock", py],
19
+ ["background", bg],
20
+ ["borderColor", borderColor],
21
+ ["borderRadius", rounded],
22
+ ["boxShadow", shadow]
23
+ ], style),
24
+ children
25
+ });
26
+ };
27
+ //#endregion
28
+ export { Box };
@@ -0,0 +1,8 @@
1
+ import { getPrefixCls } from "../utils/getPrefixCls.js";
2
+ //#region box/constants.ts
3
+ /**
4
+ * Box 组件的 CSS 类名前缀。
5
+ */
6
+ const boxPrefixCls = getPrefixCls("box");
7
+ //#endregion
8
+ export { boxPrefixCls };
@@ -0,0 +1,3 @@
1
+ import { BoxProps } from "./types.js";
2
+ import { Box } from "./Box.js";
3
+ export { Box, type BoxProps };
@@ -0,0 +1,2 @@
1
+ import { Box } from "./Box.js";
2
+ export { Box };
@@ -0,0 +1,48 @@
1
+ import { ColorToken, RadiiToken, ShadowToken, SpacingToken } from "../system/defaults/index.js";
2
+ import "../system/index.js";
3
+ import { ComponentPropsWithRef } from "react";
4
+ //#region box/types.d.ts
5
+ /**
6
+ * Box 自有样式属性。
7
+ */
8
+ export type BoxOwnProps = {
9
+ /**
10
+ * 内边距,对应 `padding`。
11
+ */
12
+ p?: SpacingToken;
13
+ /**
14
+ * 水平内边距,对应 `padding-inline`。
15
+ */
16
+ px?: SpacingToken;
17
+ /**
18
+ * 垂直内边距,对应 `padding-block`。
19
+ */
20
+ py?: SpacingToken;
21
+ /**
22
+ * 背景色语义 token。
23
+ */
24
+ bg?: ColorToken;
25
+ /**
26
+ * 圆角预设。
27
+ */
28
+ rounded?: RadiiToken;
29
+ /**
30
+ * 是否显示默认 1px solid 边框。
31
+ */
32
+ border?: boolean;
33
+ /**
34
+ * 边框颜色语义 token。
35
+ */
36
+ borderColor?: ColorToken;
37
+ /**
38
+ * 阴影预设。
39
+ */
40
+ shadow?: ShadowToken;
41
+ };
42
+ /**
43
+ * 通用样式容器属性。
44
+ *
45
+ * 使用 `interface` 扩展原生 div props,便于 Storybook Docgen 推断 Controls。
46
+ */
47
+ export interface BoxProps extends ComponentPropsWithRef<'div'>, BoxOwnProps {}
48
+ //#endregion
@@ -1,7 +1,7 @@
1
1
  import { buttonPrefixCls } from "./constants.js";
2
2
  import LoadingIcon from "./LoadingIcon.js";
3
- import { hasValue, isNil } from "@omnific/utils";
4
3
  import { clsx } from "clsx";
4
+ import { hasValue, isNil } from "@omnific/utils";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  //#region button/Button.tsx
7
7
  /**
@@ -2,8 +2,8 @@ import { buttonPrefixCls } from "./constants.js";
2
2
  import "../transition/types.js";
3
3
  import { useTransitionStatus } from "../transition/useTransitionStatus.js";
4
4
  import { clsx } from "clsx";
5
- import { LoadingIcon } from "@omnific/icons";
6
5
  import { jsx } from "react/jsx-runtime";
6
+ import { LoadingIcon } from "@omnific/icons";
7
7
  //#region button/LoadingIcon.tsx
8
8
  /**
9
9
  * 渲染按钮内部的加载图标。
package/dist/main.d.ts CHANGED
@@ -1,7 +1,10 @@
1
+ import { BoxProps } from "./box/types.js";
2
+ import { Box } from "./box/Box.js";
3
+ import "./box/index.js";
1
4
  import { ButtonProps } from "./button/types.js";
2
5
  import { Button } from "./button/Button.js";
3
6
  import "./button/index.js";
4
7
  import { ENTERED, ENTERING, EXITED, EXITING, TransitionOptions, UNMOUNTED } from "./transition/types.js";
5
8
  import { useTransitionStatus } from "./transition/useTransitionStatus.js";
6
9
  import "./transition/index.js";
7
- export { Button, type ButtonProps, ENTERED, ENTERING, EXITED, EXITING, type TransitionOptions, UNMOUNTED, useTransitionStatus };
10
+ export { Box, type BoxProps, Button, type ButtonProps, ENTERED, ENTERING, EXITED, EXITING, type TransitionOptions, UNMOUNTED, useTransitionStatus };
package/dist/main.js CHANGED
@@ -1,4 +1,5 @@
1
+ import { Box } from "./box/Box.js";
1
2
  import { ENTERED, ENTERING, EXITED, EXITING, UNMOUNTED } from "./transition/types.js";
2
3
  import { useTransitionStatus } from "./transition/useTransitionStatus.js";
3
4
  import { Button } from "./button/Button.js";
4
- export { Button, ENTERED, ENTERING, EXITED, EXITING, UNMOUNTED, useTransitionStatus };
5
+ export { Box, Button, ENTERED, ENTERING, EXITED, EXITING, UNMOUNTED, useTransitionStatus };
package/dist/styles.css CHANGED
@@ -1,38 +1,17 @@
1
- :root {
2
- --color-text: rgb(0 0 0 / 88%);
3
- --color-bg: #ffffff;
4
- --color-border: #d9d9d9;
5
- --color-primary: #1677ff;
6
- --color-primary-hover: #4096ff;
7
- --color-primary-active: #0958d9;
8
- --color-primary-fg: #ffffff;
9
- --color-danger: #ff4d4f;
10
- --color-danger-hover: #ff7875;
11
- --color-danger-active: #d9363e;
12
- --color-danger-fg: #ffffff;
13
- --color-disabled-text: rgb(0 0 0 / 25%);
14
- --color-disabled-bg: rgb(239 239 239);
15
- --focus-ring: rgb(21 112 239 / 25%);
16
- --font-sans:
17
- -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
18
- sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
19
- --font-size-md: 14px;
20
- --font-size-lg: 16px;
21
- --radius-sm: 4px;
22
- --radius-md: 6px;
23
- --radius-lg: 8px;
24
- --control-height-sm: 24px;
25
- --control-height-md: 32px;
26
- --control-height-lg: 40px;
27
- --shadow-control: 0 2px 0 rgb(0 0 0 / 2%);
28
- --shadow-primary: 0 2px 0 rgb(5 145 255 / 10%);
1
+ .atelier-box {
2
+ box-sizing: border-box;
3
+ min-width: 0;
4
+ }
5
+
6
+ .atelier-box-border {
7
+ border: 1px solid var(--color-border);
29
8
  }
30
9
 
31
10
  .atelier-button {
32
11
  display: inline-flex;
33
12
  align-items: center;
34
13
  justify-content: center;
35
- height: var(--btn-size, var(--control-height-md));
14
+ height: var(--control-height-md);
36
15
  padding: 0 15px;
37
16
  line-height: 1.5715;
38
17
  white-space: nowrap;
@@ -41,16 +20,22 @@
41
20
  margin-right: 8px;
42
21
  }
43
22
  .atelier-button-size-small {
44
- --btn-size: var(--control-height-sm);
23
+ height: var(--control-height-sm);
45
24
  padding: 0 7px;
46
25
  }
47
26
  .atelier-button-size-large {
48
- --btn-size: var(--control-height-lg);
27
+ height: var(--control-height-lg);
49
28
  }
50
29
  .atelier-button-shape-circle, .atelier-button-icon-only {
51
- width: var(--btn-size, var(--control-height-md));
30
+ width: var(--control-height-md);
52
31
  padding: 0;
53
32
  }
33
+ .atelier-button-size-small.atelier-button-shape-circle, .atelier-button-size-small.atelier-button-icon-only {
34
+ width: var(--control-height-sm);
35
+ }
36
+ .atelier-button-size-large.atelier-button-shape-circle, .atelier-button-size-large.atelier-button-icon-only {
37
+ width: var(--control-height-lg);
38
+ }
54
39
  .atelier-button-block {
55
40
  display: flex;
56
41
  width: 100%;
@@ -66,37 +51,33 @@
66
51
  font: inherit;
67
52
  font-family: var(--font-sans);
68
53
  font-weight: 400;
69
- color: var(--btn-color, var(--color-text));
54
+ color: var(--color-fg);
70
55
  user-select: none;
71
56
  cursor: pointer;
72
- background: var(--btn-background, var(--color-bg));
73
- border: 1px solid var(--btn-border, var(--color-border));
57
+ background: var(--color-bg);
58
+ border: 1px solid var(--color-border);
74
59
  border-radius: var(--radius-md);
75
- box-shadow: var(--btn-shadow, var(--shadow-control));
76
- transition: background-color 0.2s, border-color 0.2s, box-shadow 0.2s, color 0.2s;
60
+ transition: background-color 0.2s, border-color 0.2s, color 0.2s;
77
61
  transition-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1);
78
62
  }
79
63
  .atelier-button:not(.atelier-button-loading):hover:not(:disabled) {
80
- color: var(--btn-hover-color, var(--color-primary-hover));
81
- background: var(--btn-hover, var(--color-bg));
82
- border-color: var(--btn-hover-border, var(--color-primary-hover));
64
+ color: var(--color-blue-hover);
65
+ background: var(--color-bg);
66
+ border-color: var(--color-blue-hover);
83
67
  }
84
68
  .atelier-button:not(.atelier-button-loading):active:not(:disabled) {
85
- color: var(--btn-active-color, var(--color-primary-active));
86
- background: var(--btn-active, var(--color-bg));
87
- border-color: var(--btn-active-border, var(--color-primary-active));
69
+ color: var(--color-blue-active);
70
+ background: var(--color-bg);
71
+ border-color: var(--color-blue-active);
88
72
  }
89
73
  .atelier-button:focus-visible {
90
- outline: 3px solid var(--focus-ring);
74
+ outline: 3px solid var(--focus-ring-default);
91
75
  outline-offset: 2px;
92
76
  }
93
- .atelier-button:disabled, .atelier-button-loading {
94
- box-shadow: none;
95
- }
96
77
  .atelier-button:disabled {
97
- color: var(--color-disabled-text);
78
+ color: var(--color-fg-disabled);
98
79
  cursor: not-allowed;
99
- background: var(--color-disabled-bg);
80
+ background: var(--color-bg-disabled);
100
81
  border-color: var(--color-border);
101
82
  }
102
83
  .atelier-button-loading {
@@ -117,7 +98,13 @@
117
98
  border-radius: var(--radius-lg);
118
99
  }
119
100
  .atelier-button-shape-round {
120
- border-radius: var(--btn-size, var(--control-height-md));
101
+ border-radius: var(--control-height-md);
102
+ }
103
+ .atelier-button-size-small.atelier-button-shape-round {
104
+ border-radius: var(--control-height-sm);
105
+ }
106
+ .atelier-button-size-large.atelier-button-shape-round {
107
+ border-radius: var(--control-height-lg);
121
108
  }
122
109
  .atelier-button-shape-circle, .atelier-button-icon-only, .atelier-button-loading-icon {
123
110
  border-radius: 50%;
@@ -126,43 +113,52 @@
126
113
  display: inline-block;
127
114
  }
128
115
  .atelier-button-primary {
129
- --btn-color: var(--color-primary-fg);
130
- --btn-background: var(--color-primary);
131
- --btn-border: transparent;
132
- --btn-hover: var(--color-primary-hover);
133
- --btn-active: var(--color-primary-active);
134
- --btn-hover-color: var(--color-primary-fg);
135
- --btn-active-color: var(--color-primary-fg);
136
- --btn-hover-border: transparent;
137
- --btn-active-border: transparent;
138
- --btn-shadow: var(--shadow-primary);
116
+ color: var(--color-blue-contrast);
117
+ background: var(--color-blue-solid);
118
+ border-color: transparent;
119
+ }
120
+ .atelier-button-primary:not(.atelier-button-loading):hover:not(:disabled) {
121
+ color: var(--color-blue-contrast);
122
+ background: var(--color-blue-hover);
123
+ border-color: transparent;
124
+ }
125
+ .atelier-button-primary:not(.atelier-button-loading):active:not(:disabled) {
126
+ color: var(--color-blue-contrast);
127
+ background: var(--color-blue-active);
128
+ border-color: transparent;
139
129
  }
140
130
  .atelier-button-dashed {
141
131
  border-style: dashed;
142
132
  }
143
133
  .atelier-button-outline {
144
- --btn-color: var(--color-primary);
145
- --btn-background: transparent;
146
- --btn-border: var(--color-primary);
147
- --btn-hover: transparent;
148
- --btn-active: transparent;
149
- --btn-hover-color: var(--color-primary-hover);
150
- --btn-active-color: var(--color-primary-active);
151
- --btn-hover-border: var(--color-primary-hover);
152
- --btn-active-border: var(--color-primary-active);
153
- --btn-shadow: none;
134
+ color: var(--color-blue-solid);
135
+ background: transparent;
136
+ border-color: var(--color-blue-solid);
137
+ }
138
+ .atelier-button-outline:not(.atelier-button-loading):hover:not(:disabled) {
139
+ color: var(--color-blue-hover);
140
+ background: transparent;
141
+ border-color: var(--color-blue-hover);
142
+ }
143
+ .atelier-button-outline:not(.atelier-button-loading):active:not(:disabled) {
144
+ color: var(--color-blue-active);
145
+ background: transparent;
146
+ border-color: var(--color-blue-active);
154
147
  }
155
148
  .atelier-button-danger {
156
- --btn-color: var(--color-danger-fg);
157
- --btn-background: var(--color-danger);
158
- --btn-border: transparent;
159
- --btn-hover: var(--color-danger-hover);
160
- --btn-active: var(--color-danger-active);
161
- --btn-hover-color: var(--color-danger-fg);
162
- --btn-active-color: var(--color-danger-fg);
163
- --btn-hover-border: transparent;
164
- --btn-active-border: transparent;
165
- --btn-shadow: var(--shadow-primary);
149
+ color: var(--color-red-contrast);
150
+ background: var(--color-red-solid);
151
+ border-color: transparent;
152
+ }
153
+ .atelier-button-danger:not(.atelier-button-loading):hover:not(:disabled) {
154
+ color: var(--color-red-contrast);
155
+ background: var(--color-red-hover);
156
+ border-color: transparent;
157
+ }
158
+ .atelier-button-danger:not(.atelier-button-loading):active:not(:disabled) {
159
+ color: var(--color-red-contrast);
160
+ background: var(--color-red-active);
161
+ border-color: transparent;
166
162
  }
167
163
  .atelier-button-loading-icon > svg {
168
164
  animation: atelier-button-spin 0.7s linear infinite;
@@ -0,0 +1,9 @@
1
+ import { SystemContext, ThemeTokens } from "./types.js";
2
+ //#region system/create-system.d.ts
3
+ /**
4
+ * 合并 tokens 并返回精简版 SystemContext。
5
+ *
6
+ * 第一个参数可以是主题名称,也可以直接传入 ThemeTokens;省略主题名称时使用 `default`。
7
+ */
8
+ export declare function createSystem(nameOrTokenSet: string | ThemeTokens, ...tokenSets: ThemeTokens[]): SystemContext;
9
+ //#endregion
@@ -0,0 +1,21 @@
1
+ import { mergeTokens } from "./merge-tokens.js";
2
+ import { isString } from "@omnific/utils";
3
+ //#region system/create-system.ts
4
+ const DEFAULT_SYSTEM_NAME = "default";
5
+ /**
6
+ * 合并 tokens 并返回精简版 SystemContext。
7
+ *
8
+ * 第一个参数可以是主题名称,也可以直接传入 ThemeTokens;省略主题名称时使用 `default`。
9
+ */
10
+ function createSystem(nameOrTokenSet, ...tokenSets) {
11
+ const name = isString(nameOrTokenSet) ? nameOrTokenSet : DEFAULT_SYSTEM_NAME;
12
+ const mergedTokenSets = isString(nameOrTokenSet) ? tokenSets : [nameOrTokenSet, ...tokenSets];
13
+ const { tokens, cssVars } = mergeTokens(...mergedTokenSets);
14
+ return {
15
+ name,
16
+ _config: tokens,
17
+ cssVars
18
+ };
19
+ }
20
+ //#endregion
21
+ export { createSystem };
@@ -0,0 +1,52 @@
1
+ import { SystemContext, ThemeTokens } from "./types.js";
2
+ import { CSSProperties } from "react";
3
+ //#region system/css-vars.d.ts
4
+ /**
5
+ * CSS 属性 → ThemeTokens 分类。
6
+ */
7
+ declare const STYLE_TOKEN: {
8
+ readonly padding: "spacing";
9
+ readonly paddingInline: "spacing";
10
+ readonly paddingBlock: "spacing";
11
+ readonly background: "colors";
12
+ readonly borderColor: "colors";
13
+ readonly borderRadius: "radii";
14
+ readonly boxShadow: "shadows";
15
+ };
16
+ /**
17
+ * 将 token 分类与键转为扁平 CSS 变量名。
18
+ *
19
+ * @example
20
+ * toCssVarName('colors', 'bg.muted') // '--color-bg-muted'
21
+ * toCssVarName('focusRings', 'default') // '--focus-ring-default'
22
+ */
23
+ export declare const toCssVarName: (category: keyof ThemeTokens, key: string) => string;
24
+ type StyleProperty = keyof typeof STYLE_TOKEN;
25
+ /**
26
+ * `styleFromTokens` 单条:`[CSS 属性, token 键]`。
27
+ */
28
+ export type TokenStyleEntry = readonly [StyleProperty, string | number | null | undefined];
29
+ /**
30
+ * 将 token 取值写成可合并进 React `style` 的 CSS 属性对象;空值跳过。
31
+ * ThemeTokens 分类由 `STYLE_TOKEN` 按 CSS 属性映射。
32
+ * 传入的 `style` 后合并,可覆盖同名属性。
33
+ *
34
+ * @example
35
+ * styleFromTokens(
36
+ * [
37
+ * ['padding', 4],
38
+ * ['background', 'bg.muted'],
39
+ * ],
40
+ * { color: 'red' },
41
+ * )
42
+ * // { padding: 'var(--space-4)', background: 'var(--color-bg-muted)', color: 'red' }
43
+ */
44
+ export declare const styleFromTokens: (entries: readonly TokenStyleEntry[], style?: CSSProperties) => CSSProperties;
45
+ /**
46
+ * 将 `system.cssVars` 写入文档级 `<style>`(挂在 `document.head` 下)。
47
+ *
48
+ * 以 `system.name` 为 key 复用节点。返回卸载函数。
49
+ * 目标节点设置 `data-atelier-theme={system.name}`。
50
+ */
51
+ export declare const applyCssVars: (system: SystemContext, target: HTMLElement) => (() => void);
52
+ //#endregion
@@ -0,0 +1,112 @@
1
+ import { hasValue, isNil } from "@omnific/utils";
2
+ //#region system/css-vars.ts
3
+ /**
4
+ * Token 分类 → CSS 变量名前缀。
5
+ */
6
+ const CATEGORY_PREFIX = {
7
+ colors: "color",
8
+ spacing: "space",
9
+ radii: "radius",
10
+ shadows: "shadow",
11
+ fonts: "font",
12
+ fontSizes: "font-size",
13
+ controlHeights: "control-height",
14
+ focusRings: "focus-ring"
15
+ };
16
+ /**
17
+ * CSS 属性 → ThemeTokens 分类。
18
+ */
19
+ const STYLE_TOKEN = {
20
+ padding: "spacing",
21
+ paddingInline: "spacing",
22
+ paddingBlock: "spacing",
23
+ background: "colors",
24
+ borderColor: "colors",
25
+ borderRadius: "radii",
26
+ boxShadow: "shadows"
27
+ };
28
+ /**
29
+ * 主题作用域属性;值为 `system.name`(含挂在 `documentElement` 上时)。
30
+ */
31
+ const THEME_ATTR = "data-atelier-theme";
32
+ /**
33
+ * `system.name` → 样式节点与引用次数。
34
+ */
35
+ const stylesByName = /* @__PURE__ */ new Map();
36
+ /**
37
+ * 将 token 分类与键转为扁平 CSS 变量名。
38
+ *
39
+ * @example
40
+ * toCssVarName('colors', 'bg.muted') // '--color-bg-muted'
41
+ * toCssVarName('focusRings', 'default') // '--focus-ring-default'
42
+ */
43
+ const toCssVarName = (category, key) => {
44
+ const suffix = key.replaceAll(".", "-");
45
+ return `--${CATEGORY_PREFIX[category]}-${suffix}`;
46
+ };
47
+ /**
48
+ * 将 token 取值写成可合并进 React `style` 的 CSS 属性对象;空值跳过。
49
+ * ThemeTokens 分类由 `STYLE_TOKEN` 按 CSS 属性映射。
50
+ * 传入的 `style` 后合并,可覆盖同名属性。
51
+ *
52
+ * @example
53
+ * styleFromTokens(
54
+ * [
55
+ * ['padding', 4],
56
+ * ['background', 'bg.muted'],
57
+ * ],
58
+ * { color: 'red' },
59
+ * )
60
+ * // { padding: 'var(--space-4)', background: 'var(--color-bg-muted)', color: 'red' }
61
+ */
62
+ const styleFromTokens = (entries, style) => {
63
+ const tokenStyle = {};
64
+ for (const [property, token] of entries) {
65
+ if (isNil(token)) continue;
66
+ tokenStyle[property] = `var(${toCssVarName(STYLE_TOKEN[property], String(token))})`;
67
+ }
68
+ return isNil(style) ? tokenStyle : {
69
+ ...tokenStyle,
70
+ ...style
71
+ };
72
+ };
73
+ const toCssText = (system) => {
74
+ const lines = [];
75
+ for (const [name, value] of Object.entries(system.cssVars)) lines.push(` ${name}: ${value};`);
76
+ return `${`[${THEME_ATTR}="${CSS.escape(system.name)}"]`} {\n${lines.join("\n")}\n}`;
77
+ };
78
+ /**
79
+ * 将 `system.cssVars` 写入文档级 `<style>`(挂在 `document.head` 下)。
80
+ *
81
+ * 以 `system.name` 为 key 复用节点。返回卸载函数。
82
+ * 目标节点设置 `data-atelier-theme={system.name}`。
83
+ */
84
+ const applyCssVars = (system, target) => {
85
+ const { document: doc } = globalThis;
86
+ if (isNil(doc) || isNil(doc.head)) return () => {};
87
+ const { name } = system;
88
+ target.setAttribute(THEME_ATTR, name);
89
+ let entry = stylesByName.get(name);
90
+ if (hasValue(entry) && entry.style.isConnected) entry.count += 1;
91
+ else {
92
+ const style = doc.createElement("style");
93
+ style.dataset.atelierCssVars = "";
94
+ style.dataset.atelierSystem = name;
95
+ style.textContent = toCssText(system);
96
+ doc.head.append(style);
97
+ entry = {
98
+ style,
99
+ count: 1
100
+ };
101
+ stylesByName.set(name, entry);
102
+ }
103
+ return () => {
104
+ target.removeAttribute(THEME_ATTR);
105
+ entry.count -= 1;
106
+ if (entry.count > 0) return;
107
+ entry.style.remove();
108
+ stylesByName.delete(name);
109
+ };
110
+ };
111
+ //#endregion
112
+ export { applyCssVars, styleFromTokens, toCssVarName };
@@ -0,0 +1,7 @@
1
+ import { SystemContext } from "./types.js";
2
+ //#region system/default-system.d.ts
3
+ /**
4
+ * 基于 `defaultConfig` 的共享默认 system(单例,可直接 `useTheme` / `applyCssVars`)。
5
+ */
6
+ export declare const defaultSystem: SystemContext;
7
+ //#endregion
@@ -0,0 +1,9 @@
1
+ import { createSystem } from "./create-system.js";
2
+ import { defaultConfig } from "./defaults/index.js";
3
+ //#region system/default-system.ts
4
+ /**
5
+ * 基于 `defaultConfig` 的共享默认 system(单例,可直接 `useTheme` / `applyCssVars`)。
6
+ */
7
+ const defaultSystem = createSystem(defaultConfig);
8
+ //#endregion
9
+ export { defaultSystem };