@omnific/atelier 0.1.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 +20 -0
- package/dist/button/Button.d.ts +7 -0
- package/dist/button/Button.js +43 -0
- package/dist/button/LoadingIcon.js +45 -0
- package/dist/button/constants.js +8 -0
- package/dist/button/index.d.ts +3 -0
- package/dist/button/index.js +2 -0
- package/dist/button/types.d.ts +43 -0
- package/dist/main.d.ts +7 -0
- package/dist/main.js +4 -0
- package/dist/styles.css +175 -0
- package/dist/transition/index.d.ts +3 -0
- package/dist/transition/index.js +3 -0
- package/dist/transition/types.d.ts +128 -0
- package/dist/transition/types.js +23 -0
- package/dist/transition/useTransitionStatus.d.ts +7 -0
- package/dist/transition/useTransitionStatus.js +54 -0
- package/dist/types.d.ts +6 -0
- package/dist/utils/getPrefixCls.js +11 -0
- package/package.json +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @omnific/atelier
|
|
2
|
+
|
|
3
|
+
A refined React component library for Omnific.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Button } from '@omnific/atelier';
|
|
7
|
+
import '@omnific/atelier/styles.css';
|
|
8
|
+
|
|
9
|
+
<Button variant='primary'>Continue</Button>;
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Storybook
|
|
13
|
+
|
|
14
|
+
Curated component examples live under `packages/atelier/__stories__` and
|
|
15
|
+
`packages/*/examples`. Run Storybook from the repository root:
|
|
16
|
+
|
|
17
|
+
- Local dev: `pnpm storybook`
|
|
18
|
+
- Production build: `pnpm build:storybook`
|
|
19
|
+
- Static output: `packages/atelier/storybook-static`
|
|
20
|
+
- Online preview: https://monhan-97.github.io/omnific/atelier/
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { buttonPrefixCls } from "./constants.js";
|
|
2
|
+
import LoadingIcon from "./LoadingIcon.js";
|
|
3
|
+
import { hasValue, isNil } from "@omnific/utils";
|
|
4
|
+
import { clsx } from "clsx";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region button/Button.tsx
|
|
7
|
+
/**
|
|
8
|
+
* 通用按钮。
|
|
9
|
+
*/
|
|
10
|
+
const Button = (props) => {
|
|
11
|
+
const { block = false, children, className, disabled, icon, loading = false, onClick, ref, shape = "default", size = "default", type = "button", variant = "secondary", ...rest } = props;
|
|
12
|
+
const hasChildren = hasValue(children);
|
|
13
|
+
const isIconOnly = isNil(children) && (hasValue(icon) || loading);
|
|
14
|
+
const classes = clsx(buttonPrefixCls, `${buttonPrefixCls}-${variant}`, `${buttonPrefixCls}-size-${size}`, {
|
|
15
|
+
[`${buttonPrefixCls}-shape-${shape}`]: shape !== "default",
|
|
16
|
+
[`${buttonPrefixCls}-icon-only`]: isIconOnly,
|
|
17
|
+
[`${buttonPrefixCls}-block`]: block,
|
|
18
|
+
[`${buttonPrefixCls}-loading`]: loading
|
|
19
|
+
}, className);
|
|
20
|
+
const handleClick = (event) => {
|
|
21
|
+
if (loading || disabled) {
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
onClick?.(event);
|
|
26
|
+
};
|
|
27
|
+
const iconNode = loading || isNil(icon) ? /* @__PURE__ */ jsx(LoadingIcon, {
|
|
28
|
+
iconOnly: isIconOnly,
|
|
29
|
+
loading
|
|
30
|
+
}) : icon;
|
|
31
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
32
|
+
...rest,
|
|
33
|
+
"aria-busy": loading || void 0,
|
|
34
|
+
className: classes,
|
|
35
|
+
disabled,
|
|
36
|
+
onClick: handleClick,
|
|
37
|
+
ref,
|
|
38
|
+
type,
|
|
39
|
+
children: [iconNode, hasChildren && /* @__PURE__ */ jsx("span", { children })]
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
//#endregion
|
|
43
|
+
export { Button };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { buttonPrefixCls } from "./constants.js";
|
|
2
|
+
import "../transition/types.js";
|
|
3
|
+
import { useTransitionStatus } from "../transition/useTransitionStatus.js";
|
|
4
|
+
import { clsx } from "clsx";
|
|
5
|
+
import { LoadingIcon } from "@omnific/icons";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
//#region button/LoadingIcon.tsx
|
|
8
|
+
/**
|
|
9
|
+
* 渲染按钮内部的加载图标。
|
|
10
|
+
*/
|
|
11
|
+
const LoadingIcon$1 = (props) => {
|
|
12
|
+
const { className, iconOnly = false, loading = false, style } = props;
|
|
13
|
+
const { handleRef, isMounted, status } = useTransitionStatus({
|
|
14
|
+
appear: true,
|
|
15
|
+
enter: {
|
|
16
|
+
opacity: 1,
|
|
17
|
+
scale: 1,
|
|
18
|
+
width: "auto",
|
|
19
|
+
marginRight: iconOnly ? 0 : 8
|
|
20
|
+
},
|
|
21
|
+
exit: {
|
|
22
|
+
opacity: 0,
|
|
23
|
+
scale: 0,
|
|
24
|
+
width: 0,
|
|
25
|
+
marginRight: 0
|
|
26
|
+
},
|
|
27
|
+
in: loading,
|
|
28
|
+
unmountOnExit: true
|
|
29
|
+
});
|
|
30
|
+
if (!isMounted) return null;
|
|
31
|
+
return /* @__PURE__ */ jsx("span", {
|
|
32
|
+
"aria-hidden": "true",
|
|
33
|
+
className: clsx(`${buttonPrefixCls}-loading-icon`, className),
|
|
34
|
+
ref: handleRef,
|
|
35
|
+
style: {
|
|
36
|
+
visibility: status === "exited" && !loading ? "hidden" : void 0,
|
|
37
|
+
opacity: 0,
|
|
38
|
+
width: 0,
|
|
39
|
+
...style
|
|
40
|
+
},
|
|
41
|
+
children: /* @__PURE__ */ jsx(LoadingIcon, { size: "1em" })
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
export { LoadingIcon$1 as default };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ComponentSize } from "../types.js";
|
|
2
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
|
|
3
|
+
//#region button/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* 按钮形状。
|
|
6
|
+
*/
|
|
7
|
+
export type ButtonShape = 'default' | 'circle' | 'round';
|
|
8
|
+
/**
|
|
9
|
+
* 按钮视觉变体。危险操作用 `danger`,不再与其他变体叠加。
|
|
10
|
+
*/
|
|
11
|
+
export type ButtonVariant = 'primary' | 'secondary' | 'dashed' | 'outline' | 'danger';
|
|
12
|
+
/**
|
|
13
|
+
* 按钮属性。
|
|
14
|
+
*
|
|
15
|
+
* 使用 `interface` 扩展原生 button props,便于 Storybook Docgen 推断 Controls。
|
|
16
|
+
*/
|
|
17
|
+
export interface ButtonProps extends ComponentPropsWithRef<'button'> {
|
|
18
|
+
/**
|
|
19
|
+
* 将按钮宽度调整为父容器宽度。
|
|
20
|
+
*/
|
|
21
|
+
block?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* 设置按钮图标。
|
|
24
|
+
*/
|
|
25
|
+
icon?: ReactNode;
|
|
26
|
+
/**
|
|
27
|
+
* 设置按钮加载状态。
|
|
28
|
+
*/
|
|
29
|
+
loading?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* 设置按钮形状。
|
|
32
|
+
*/
|
|
33
|
+
shape?: ButtonShape;
|
|
34
|
+
/**
|
|
35
|
+
* 设置按钮大小。
|
|
36
|
+
*/
|
|
37
|
+
size?: ComponentSize;
|
|
38
|
+
/**
|
|
39
|
+
* 设置按钮视觉变体。
|
|
40
|
+
*/
|
|
41
|
+
variant?: ButtonVariant;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ButtonProps } from "./button/types.js";
|
|
2
|
+
import { Button } from "./button/Button.js";
|
|
3
|
+
import "./button/index.js";
|
|
4
|
+
import { ENTERED, ENTERING, EXITED, EXITING, TransitionOptions, UNMOUNTED } from "./transition/types.js";
|
|
5
|
+
import { useTransitionStatus } from "./transition/useTransitionStatus.js";
|
|
6
|
+
import "./transition/index.js";
|
|
7
|
+
export { Button, type ButtonProps, ENTERED, ENTERING, EXITED, EXITING, type TransitionOptions, UNMOUNTED, useTransitionStatus };
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ENTERED, ENTERING, EXITED, EXITING, UNMOUNTED } from "./transition/types.js";
|
|
2
|
+
import { useTransitionStatus } from "./transition/useTransitionStatus.js";
|
|
3
|
+
import { Button } from "./button/Button.js";
|
|
4
|
+
export { Button, ENTERED, ENTERING, EXITED, EXITING, UNMOUNTED, useTransitionStatus };
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
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%);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.atelier-button {
|
|
32
|
+
display: inline-flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
height: var(--btn-size, var(--control-height-md));
|
|
36
|
+
padding: 0 15px;
|
|
37
|
+
line-height: 1.5715;
|
|
38
|
+
white-space: nowrap;
|
|
39
|
+
}
|
|
40
|
+
.atelier-button > :first-child:not(:only-child) {
|
|
41
|
+
margin-right: 8px;
|
|
42
|
+
}
|
|
43
|
+
.atelier-button-size-small {
|
|
44
|
+
--btn-size: var(--control-height-sm);
|
|
45
|
+
padding: 0 7px;
|
|
46
|
+
}
|
|
47
|
+
.atelier-button-size-large {
|
|
48
|
+
--btn-size: var(--control-height-lg);
|
|
49
|
+
}
|
|
50
|
+
.atelier-button-shape-circle, .atelier-button-icon-only {
|
|
51
|
+
width: var(--btn-size, var(--control-height-md));
|
|
52
|
+
padding: 0;
|
|
53
|
+
}
|
|
54
|
+
.atelier-button-block {
|
|
55
|
+
display: flex;
|
|
56
|
+
width: 100%;
|
|
57
|
+
}
|
|
58
|
+
.atelier-button-loading-icon {
|
|
59
|
+
flex: 0 0 auto;
|
|
60
|
+
height: 1em;
|
|
61
|
+
overflow: hidden;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.atelier-button {
|
|
65
|
+
appearance: none;
|
|
66
|
+
font: inherit;
|
|
67
|
+
font-family: var(--font-sans);
|
|
68
|
+
font-weight: 400;
|
|
69
|
+
color: var(--btn-color, var(--color-text));
|
|
70
|
+
user-select: none;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
background: var(--btn-background, var(--color-bg));
|
|
73
|
+
border: 1px solid var(--btn-border, var(--color-border));
|
|
74
|
+
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;
|
|
77
|
+
transition-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1);
|
|
78
|
+
}
|
|
79
|
+
.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));
|
|
83
|
+
}
|
|
84
|
+
.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));
|
|
88
|
+
}
|
|
89
|
+
.atelier-button:focus-visible {
|
|
90
|
+
outline: 3px solid var(--focus-ring);
|
|
91
|
+
outline-offset: 2px;
|
|
92
|
+
}
|
|
93
|
+
.atelier-button:disabled, .atelier-button-loading {
|
|
94
|
+
box-shadow: none;
|
|
95
|
+
}
|
|
96
|
+
.atelier-button:disabled {
|
|
97
|
+
color: var(--color-disabled-text);
|
|
98
|
+
cursor: not-allowed;
|
|
99
|
+
background: var(--color-disabled-bg);
|
|
100
|
+
border-color: var(--color-border);
|
|
101
|
+
}
|
|
102
|
+
.atelier-button-loading {
|
|
103
|
+
cursor: default;
|
|
104
|
+
opacity: 0.65;
|
|
105
|
+
pointer-events: none;
|
|
106
|
+
}
|
|
107
|
+
.atelier-button-size-small {
|
|
108
|
+
font-size: var(--font-size-md);
|
|
109
|
+
border-radius: var(--radius-sm);
|
|
110
|
+
}
|
|
111
|
+
.atelier-button-size-default {
|
|
112
|
+
font-size: var(--font-size-md);
|
|
113
|
+
border-radius: var(--radius-md);
|
|
114
|
+
}
|
|
115
|
+
.atelier-button-size-large {
|
|
116
|
+
font-size: var(--font-size-lg);
|
|
117
|
+
border-radius: var(--radius-lg);
|
|
118
|
+
}
|
|
119
|
+
.atelier-button-shape-round {
|
|
120
|
+
border-radius: var(--btn-size, var(--control-height-md));
|
|
121
|
+
}
|
|
122
|
+
.atelier-button-shape-circle, .atelier-button-icon-only, .atelier-button-loading-icon {
|
|
123
|
+
border-radius: 50%;
|
|
124
|
+
}
|
|
125
|
+
.atelier-button > span {
|
|
126
|
+
display: inline-block;
|
|
127
|
+
}
|
|
128
|
+
.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);
|
|
139
|
+
}
|
|
140
|
+
.atelier-button-dashed {
|
|
141
|
+
border-style: dashed;
|
|
142
|
+
}
|
|
143
|
+
.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;
|
|
154
|
+
}
|
|
155
|
+
.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);
|
|
166
|
+
}
|
|
167
|
+
.atelier-button-loading-icon > svg {
|
|
168
|
+
animation: atelier-button-spin 0.7s linear infinite;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@keyframes atelier-button-spin {
|
|
172
|
+
to {
|
|
173
|
+
transform: rotate(360deg);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ENTERED, ENTERING, EXITED, EXITING, TransitionChildren, TransitionCycle, TransitionOptions, TransitionResult, TransitionStatus, TransitionStyles, UNMOUNTED, Variant, Variants } from "./types.js";
|
|
2
|
+
import { useTransitionStatus } from "./useTransitionStatus.js";
|
|
3
|
+
export { ENTERED, ENTERING, EXITED, EXITING, TransitionChildren, TransitionCycle, TransitionOptions, TransitionResult, TransitionStatus, TransitionStyles, UNMOUNTED, Variant, Variants, useTransitionStatus };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { CSSProperties, ReactElement, Ref, RefCallback } from "react";
|
|
2
|
+
import { DOMKeyframesDefinition } from "motion";
|
|
3
|
+
//#region transition/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* 过渡已退出。
|
|
6
|
+
*/
|
|
7
|
+
export declare const EXITED = "exited";
|
|
8
|
+
/**
|
|
9
|
+
* 过渡正在进入。
|
|
10
|
+
*/
|
|
11
|
+
export declare const ENTERING = "entering";
|
|
12
|
+
/**
|
|
13
|
+
* 过渡已进入。
|
|
14
|
+
*/
|
|
15
|
+
export declare const ENTERED = "entered";
|
|
16
|
+
/**
|
|
17
|
+
* 过渡正在退出。
|
|
18
|
+
*/
|
|
19
|
+
export declare const EXITING = "exiting";
|
|
20
|
+
/**
|
|
21
|
+
* 过渡元素未挂载。
|
|
22
|
+
*/
|
|
23
|
+
export declare const UNMOUNTED = "unmounted";
|
|
24
|
+
/**
|
|
25
|
+
* 过渡动画当前状态。
|
|
26
|
+
*/
|
|
27
|
+
export type TransitionStatus = typeof UNMOUNTED | typeof EXITED | typeof ENTERING | typeof ENTERED | typeof EXITING;
|
|
28
|
+
/**
|
|
29
|
+
* 过渡动画生命周期回调。
|
|
30
|
+
*/
|
|
31
|
+
export type TransitionCycle = {
|
|
32
|
+
/**
|
|
33
|
+
* 进入动画开始时触发。
|
|
34
|
+
*/
|
|
35
|
+
onEnter?: (node: HTMLElement) => void;
|
|
36
|
+
/**
|
|
37
|
+
* 进入动画完成时触发。
|
|
38
|
+
*/
|
|
39
|
+
onEntered?: (node: HTMLElement) => void;
|
|
40
|
+
/**
|
|
41
|
+
* 退出动画开始时触发。
|
|
42
|
+
*/
|
|
43
|
+
onExit?: (node: HTMLElement) => void;
|
|
44
|
+
/**
|
|
45
|
+
* 退出动画完成时触发。
|
|
46
|
+
*/
|
|
47
|
+
onExited?: (node: HTMLElement) => void;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* 动画关键帧或根据自定义参数生成关键帧的函数。
|
|
51
|
+
*/
|
|
52
|
+
export type Variant<Custom = unknown> = DOMKeyframesDefinition | ((custom: Custom | undefined, node: HTMLElement) => DOMKeyframesDefinition);
|
|
53
|
+
/**
|
|
54
|
+
* 进入与退出动画变体集合。
|
|
55
|
+
*/
|
|
56
|
+
export type Variants<Custom = unknown> = Record<'enter' | 'exit', Variant<Custom>>;
|
|
57
|
+
/**
|
|
58
|
+
* 各过渡状态对应的行内样式。
|
|
59
|
+
*/
|
|
60
|
+
export type TransitionStyles = Partial<Record<TransitionStatus, CSSProperties>>;
|
|
61
|
+
/**
|
|
62
|
+
* useTransitionStatus Hook 返回的过渡状态。
|
|
63
|
+
*/
|
|
64
|
+
export type TransitionResult = {
|
|
65
|
+
/**
|
|
66
|
+
* 同步 Motion 与外部元素引用的回调。
|
|
67
|
+
*/
|
|
68
|
+
handleRef: RefCallback<HTMLElement> | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* 过渡元素当前是否应挂载。
|
|
71
|
+
*/
|
|
72
|
+
isMounted: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* 过渡动画当前状态。
|
|
75
|
+
*/
|
|
76
|
+
status: TransitionStatus;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* useTransitionStatus Hook 配置。
|
|
80
|
+
*/
|
|
81
|
+
export type TransitionOptions<Custom = unknown> = TransitionCycle & {
|
|
82
|
+
/**
|
|
83
|
+
* 组件首次挂载时是否执行进入动画。
|
|
84
|
+
*/
|
|
85
|
+
appear?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* 传递给动画变体生成函数的自定义参数。
|
|
88
|
+
*/
|
|
89
|
+
custom?: Custom;
|
|
90
|
+
/**
|
|
91
|
+
* 动画持续时间,单位为秒。
|
|
92
|
+
*/
|
|
93
|
+
duration?: number;
|
|
94
|
+
/**
|
|
95
|
+
* 进入动画。
|
|
96
|
+
*/
|
|
97
|
+
enter?: Variant<Custom>;
|
|
98
|
+
/**
|
|
99
|
+
* 退出动画。
|
|
100
|
+
*/
|
|
101
|
+
exit?: Variant<Custom>;
|
|
102
|
+
/**
|
|
103
|
+
* 是否显示元素并触发进入或退出状态。
|
|
104
|
+
*/
|
|
105
|
+
in?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* 过渡元素引用。
|
|
108
|
+
*/
|
|
109
|
+
ref?: Ref<HTMLElement>;
|
|
110
|
+
/**
|
|
111
|
+
* 退出动画完成后是否卸载元素。
|
|
112
|
+
*/
|
|
113
|
+
unmountOnExit?: boolean;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* 可由 Transition 控制的 React 元素。
|
|
117
|
+
*/
|
|
118
|
+
export type TransitionChildren = ReactElement<{
|
|
119
|
+
/**
|
|
120
|
+
* 过渡元素引用。
|
|
121
|
+
*/
|
|
122
|
+
ref: Ref<HTMLElement>;
|
|
123
|
+
/**
|
|
124
|
+
* 过渡元素行内样式。
|
|
125
|
+
*/
|
|
126
|
+
style?: CSSProperties;
|
|
127
|
+
}>;
|
|
128
|
+
//#endregion
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region transition/types.ts
|
|
2
|
+
/**
|
|
3
|
+
* 过渡已退出。
|
|
4
|
+
*/
|
|
5
|
+
const EXITED = "exited";
|
|
6
|
+
/**
|
|
7
|
+
* 过渡正在进入。
|
|
8
|
+
*/
|
|
9
|
+
const ENTERING = "entering";
|
|
10
|
+
/**
|
|
11
|
+
* 过渡已进入。
|
|
12
|
+
*/
|
|
13
|
+
const ENTERED = "entered";
|
|
14
|
+
/**
|
|
15
|
+
* 过渡正在退出。
|
|
16
|
+
*/
|
|
17
|
+
const EXITING = "exiting";
|
|
18
|
+
/**
|
|
19
|
+
* 过渡元素未挂载。
|
|
20
|
+
*/
|
|
21
|
+
const UNMOUNTED = "unmounted";
|
|
22
|
+
//#endregion
|
|
23
|
+
export { ENTERED, ENTERING, EXITED, EXITING, UNMOUNTED };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TransitionOptions, TransitionResult } from "./types.js";
|
|
2
|
+
//#region transition/useTransitionStatus.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* 管理元素进入、退出、挂载状态及对应 Motion 动画。
|
|
5
|
+
*/
|
|
6
|
+
export declare const useTransitionStatus: <Custom = unknown>(options: TransitionOptions<Custom>) => TransitionResult;
|
|
7
|
+
//#endregion
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ENTERED, ENTERING, EXITED, EXITING, UNMOUNTED } from "./types.js";
|
|
2
|
+
import { isNil, resolveValue } from "@omnific/utils";
|
|
3
|
+
import { useForkRef } from "@omnific/hooks";
|
|
4
|
+
import { useAnimate } from "motion/react";
|
|
5
|
+
import { useEffectEvent, useLayoutEffect, useState } from "react";
|
|
6
|
+
//#region transition/useTransitionStatus.ts
|
|
7
|
+
/**
|
|
8
|
+
* 管理元素进入、退出、挂载状态及对应 Motion 动画。
|
|
9
|
+
*/
|
|
10
|
+
const useTransitionStatus = (options) => {
|
|
11
|
+
const { appear = false, custom, duration = .3, enter, exit, in: isOpen = false, onEnter, onEntered, onExit, onExited, ref, unmountOnExit = false } = options;
|
|
12
|
+
const [scope, animate] = useAnimate();
|
|
13
|
+
const [status, setStatus] = useState(() => {
|
|
14
|
+
if (isOpen) return appear ? EXITED : ENTERED;
|
|
15
|
+
return unmountOnExit ? UNMOUNTED : EXITED;
|
|
16
|
+
});
|
|
17
|
+
const handleRef = useForkRef(scope, ref);
|
|
18
|
+
const handleEnterMotion = useEffectEvent(async () => {
|
|
19
|
+
if (status === "unmounted") {
|
|
20
|
+
setStatus(EXITED);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (isNil(enter) || isNil(scope.current)) return;
|
|
24
|
+
setStatus(ENTERING);
|
|
25
|
+
onEnter?.(scope.current);
|
|
26
|
+
await animate(scope.current, resolveValue(enter, custom, scope.current), { duration });
|
|
27
|
+
setStatus(ENTERED);
|
|
28
|
+
onEntered?.(scope.current);
|
|
29
|
+
});
|
|
30
|
+
const handleExitMotion = useEffectEvent(async () => {
|
|
31
|
+
if (isNil(exit) || isNil(scope.current)) return;
|
|
32
|
+
setStatus(EXITING);
|
|
33
|
+
onExit?.(scope.current);
|
|
34
|
+
await animate(scope.current, resolveValue(exit, custom, scope.current), { duration });
|
|
35
|
+
onExited?.(scope.current);
|
|
36
|
+
setStatus(unmountOnExit ? UNMOUNTED : EXITED);
|
|
37
|
+
});
|
|
38
|
+
useLayoutEffect(() => {
|
|
39
|
+
const canEnter = status === "exited" || status === "unmounted";
|
|
40
|
+
const canExit = status === "entering" || status === "entered";
|
|
41
|
+
if (isOpen) {
|
|
42
|
+
if (canEnter) handleEnterMotion();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (canExit) handleExitMotion();
|
|
46
|
+
}, [isOpen, status]);
|
|
47
|
+
return {
|
|
48
|
+
handleRef,
|
|
49
|
+
isMounted: status !== UNMOUNTED,
|
|
50
|
+
status
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
export { useTransitionStatus };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region utils/getPrefixCls.ts
|
|
2
|
+
/**
|
|
3
|
+
* Atelier 样式的根类名前缀。
|
|
4
|
+
*/
|
|
5
|
+
const rootPrefixCls = "atelier";
|
|
6
|
+
/**
|
|
7
|
+
* 获取 Atelier 组件的 CSS 类名前缀。
|
|
8
|
+
*/
|
|
9
|
+
const getPrefixCls = (suffixCls) => `${rootPrefixCls}-${suffixCls}`;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { getPrefixCls, rootPrefixCls };
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@omnific/atelier",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A refined React component library for Omnific",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"react",
|
|
9
|
+
"components",
|
|
10
|
+
"ui"
|
|
11
|
+
],
|
|
12
|
+
"author": "monhan-97",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/monhan-97/omnific.git",
|
|
16
|
+
"directory": "packages/atelier"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/monhan-97/omnific.git/issues"
|
|
20
|
+
},
|
|
21
|
+
"main": "./dist/main.js",
|
|
22
|
+
"module": "./dist/main.js",
|
|
23
|
+
"types": "./dist/main.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/main.d.ts",
|
|
27
|
+
"import": "./dist/main.js",
|
|
28
|
+
"default": "./dist/main.js"
|
|
29
|
+
},
|
|
30
|
+
"./button": {
|
|
31
|
+
"types": "./dist/button/index.d.ts",
|
|
32
|
+
"import": "./dist/button/index.js",
|
|
33
|
+
"default": "./dist/button/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./transition": {
|
|
36
|
+
"types": "./dist/transition/index.d.ts",
|
|
37
|
+
"import": "./dist/transition/index.js",
|
|
38
|
+
"default": "./dist/transition/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./styles.css": {
|
|
41
|
+
"default": "./dist/styles.css"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"sideEffects": [
|
|
45
|
+
"**/*.css"
|
|
46
|
+
],
|
|
47
|
+
"files": [
|
|
48
|
+
"dist",
|
|
49
|
+
"README.md"
|
|
50
|
+
],
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@omnific/hooks": "0.2.1",
|
|
53
|
+
"@omnific/icons": "0.1.0",
|
|
54
|
+
"@omnific/types": "0.0.8",
|
|
55
|
+
"@omnific/utils": "0.3.1",
|
|
56
|
+
"clsx": "^2.1.1"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"motion": "^13.1.0",
|
|
60
|
+
"react": "^19.2.8",
|
|
61
|
+
"react-dom": "^19.2.8"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@omnific/tsconfig": "0.2.0",
|
|
65
|
+
"@types/react": "^19.3.0",
|
|
66
|
+
"@types/react-dom": "^19.3.0",
|
|
67
|
+
"motion": "^13.4.0",
|
|
68
|
+
"react": "^19.3.0",
|
|
69
|
+
"react-dom": "^19.3.0",
|
|
70
|
+
"sass-embedded": "1.104.1"
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"dev": "pnpm --dir ../.. storybook",
|
|
74
|
+
"build": "tsdown -c tsdown.config.mjs && pnpm build:styles",
|
|
75
|
+
"build:styles": "sass --style=expanded --no-source-map styles/index.scss dist/styles.css",
|
|
76
|
+
"test": "vitest run",
|
|
77
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
78
|
+
}
|
|
79
|
+
}
|