@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.
- package/README.md +10 -1
- package/dist/box/Box.d.ts +7 -0
- package/dist/box/Box.js +28 -0
- package/dist/box/constants.js +8 -0
- package/dist/box/index.d.ts +3 -0
- package/dist/box/index.js +2 -0
- package/dist/box/types.d.ts +48 -0
- package/dist/button/Button.js +1 -1
- package/dist/button/LoadingIcon.js +1 -1
- package/dist/main.d.ts +4 -1
- package/dist/main.js +2 -1
- package/dist/styles.css +76 -80
- package/dist/system/create-system.d.ts +9 -0
- package/dist/system/create-system.js +21 -0
- package/dist/system/css-vars.d.ts +52 -0
- package/dist/system/css-vars.js +112 -0
- package/dist/system/default-system.d.ts +7 -0
- package/dist/system/default-system.js +9 -0
- package/dist/system/defaults/colors.d.ts +181 -0
- package/dist/system/defaults/colors.js +68 -0
- package/dist/system/defaults/control-heights.d.ts +16 -0
- package/dist/system/defaults/control-heights.js +11 -0
- package/dist/system/defaults/focus-rings.d.ts +10 -0
- package/dist/system/defaults/focus-rings.js +7 -0
- package/dist/system/defaults/font-sizes.d.ts +13 -0
- package/dist/system/defaults/font-sizes.js +10 -0
- package/dist/system/defaults/fonts.d.ts +10 -0
- package/dist/system/defaults/fonts.js +7 -0
- package/dist/system/defaults/index.d.ts +422 -0
- package/dist/system/defaults/index.js +28 -0
- package/dist/system/defaults/radii.d.ts +40 -0
- package/dist/system/defaults/radii.js +19 -0
- package/dist/system/defaults/shadows.d.ts +31 -0
- package/dist/system/defaults/shadows.js +16 -0
- package/dist/system/defaults/spacing.d.ts +112 -0
- package/dist/system/defaults/spacing.js +43 -0
- package/dist/system/define-tokens.d.ts +7 -0
- package/dist/system/define-tokens.js +7 -0
- package/dist/system/index.d.ts +17 -0
- package/dist/system/index.js +16 -0
- package/dist/system/merge-tokens.d.ts +20 -0
- package/dist/system/merge-tokens.js +27 -0
- package/dist/system/types.d.ts +73 -0
- package/dist/system/use-theme.d.ts +31 -0
- package/dist/system/use-theme.js +39 -0
- 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
|
-
|
|
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
|
package/dist/box/Box.js
ADDED
|
@@ -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,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
|
package/dist/button/Button.js
CHANGED
|
@@ -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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
--color-
|
|
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(--
|
|
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
|
-
|
|
23
|
+
height: var(--control-height-sm);
|
|
45
24
|
padding: 0 7px;
|
|
46
25
|
}
|
|
47
26
|
.atelier-button-size-large {
|
|
48
|
-
|
|
27
|
+
height: var(--control-height-lg);
|
|
49
28
|
}
|
|
50
29
|
.atelier-button-shape-circle, .atelier-button-icon-only {
|
|
51
|
-
width: var(--
|
|
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(--
|
|
54
|
+
color: var(--color-fg);
|
|
70
55
|
user-select: none;
|
|
71
56
|
cursor: pointer;
|
|
72
|
-
background: var(--
|
|
73
|
-
border: 1px solid var(--
|
|
57
|
+
background: var(--color-bg);
|
|
58
|
+
border: 1px solid var(--color-border);
|
|
74
59
|
border-radius: var(--radius-md);
|
|
75
|
-
|
|
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(--
|
|
81
|
-
background: var(--
|
|
82
|
-
border-color: var(--
|
|
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(--
|
|
86
|
-
background: var(--
|
|
87
|
-
border-color: var(--
|
|
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
|
|
78
|
+
color: var(--color-fg-disabled);
|
|
98
79
|
cursor: not-allowed;
|
|
99
|
-
background: var(--color-disabled
|
|
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(--
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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,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 };
|