@seed-design/stackflow 0.0.0-alpha-20260414104312
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/lib/components/AppBar/AppBar.cjs +115 -0
- package/lib/components/AppBar/AppBar.d.ts +34 -0
- package/lib/components/AppBar/AppBar.js +103 -0
- package/lib/components/AppBar/AppBar.namespace.cjs +16 -0
- package/lib/components/AppBar/AppBar.namespace.d.ts +2 -0
- package/lib/components/AppBar/AppBar.namespace.js +1 -0
- package/lib/components/AppBar/index.cjs +18 -0
- package/lib/components/AppBar/index.d.ts +3 -0
- package/lib/components/AppBar/index.js +3 -0
- package/lib/components/AppScreen/AppScreen.cjs +68 -0
- package/lib/components/AppScreen/AppScreen.d.ts +23 -0
- package/lib/components/AppScreen/AppScreen.js +60 -0
- package/lib/components/AppScreen/AppScreen.namespace.cjs +13 -0
- package/lib/components/AppScreen/AppScreen.namespace.d.ts +2 -0
- package/lib/components/AppScreen/AppScreen.namespace.js +1 -0
- package/lib/components/AppScreen/index.cjs +15 -0
- package/lib/components/AppScreen/index.d.ts +3 -0
- package/lib/components/AppScreen/index.js +3 -0
- package/lib/components/index.cjs +26 -0
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.js +6 -0
- package/lib/hooks/index.cjs +9 -0
- package/lib/hooks/index.d.ts +1 -0
- package/lib/hooks/index.js +1 -0
- package/lib/hooks/useActivityZIndexBase.cjs +14 -0
- package/lib/hooks/useActivityZIndexBase.d.ts +18 -0
- package/lib/hooks/useActivityZIndexBase.js +10 -0
- package/lib/index.cjs +32 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +10 -0
- package/lib/plugin.cjs +18 -0
- package/lib/plugin.d.ts +7 -0
- package/lib/plugin.js +14 -0
- package/lib/primitive/AppBar/AppBar.cjs +44 -0
- package/lib/primitive/AppBar/AppBar.d.ts +15 -0
- package/lib/primitive/AppBar/AppBar.js +37 -0
- package/lib/primitive/AppBar/AppBar.namespace.cjs +12 -0
- package/lib/primitive/AppBar/AppBar.namespace.d.ts +1 -0
- package/lib/primitive/AppBar/AppBar.namespace.js +1 -0
- package/lib/primitive/AppBar/index.cjs +14 -0
- package/lib/primitive/AppBar/index.d.ts +2 -0
- package/lib/primitive/AppBar/index.js +3 -0
- package/lib/primitive/AppBar/useAppBar.cjs +39 -0
- package/lib/primitive/AppBar/useAppBar.d.ts +294 -0
- package/lib/primitive/AppBar/useAppBar.js +35 -0
- package/lib/primitive/AppBar/useAppBarContext.cjs +21 -0
- package/lib/primitive/AppBar/useAppBarContext.d.ts +8 -0
- package/lib/primitive/AppBar/useAppBarContext.js +16 -0
- package/lib/primitive/AppScreen/AppScreen.cjs +64 -0
- package/lib/primitive/AppScreen/AppScreen.d.ts +15 -0
- package/lib/primitive/AppScreen/AppScreen.js +57 -0
- package/lib/primitive/AppScreen/AppScreen.namespace.cjs +12 -0
- package/lib/primitive/AppScreen/AppScreen.namespace.d.ts +1 -0
- package/lib/primitive/AppScreen/AppScreen.namespace.js +1 -0
- package/lib/primitive/AppScreen/index.cjs +16 -0
- package/lib/primitive/AppScreen/index.d.ts +3 -0
- package/lib/primitive/AppScreen/index.js +4 -0
- package/lib/primitive/AppScreen/useAppScreen.cjs +66 -0
- package/lib/primitive/AppScreen/useAppScreen.d.ts +1138 -0
- package/lib/primitive/AppScreen/useAppScreen.js +62 -0
- package/lib/primitive/AppScreen/useAppScreenContext.cjs +21 -0
- package/lib/primitive/AppScreen/useAppScreenContext.d.ts +8 -0
- package/lib/primitive/AppScreen/useAppScreenContext.js +16 -0
- package/lib/primitive/GlobalInteraction/GlobalInteraction.cjs +28 -0
- package/lib/primitive/GlobalInteraction/GlobalInteraction.d.ts +5 -0
- package/lib/primitive/GlobalInteraction/GlobalInteraction.js +24 -0
- package/lib/primitive/GlobalInteraction/index.cjs +11 -0
- package/lib/primitive/GlobalInteraction/index.d.ts +2 -0
- package/lib/primitive/GlobalInteraction/index.js +2 -0
- package/lib/primitive/GlobalInteraction/useGlobalInteraction.cjs +136 -0
- package/lib/primitive/GlobalInteraction/useGlobalInteraction.d.ts +54 -0
- package/lib/primitive/GlobalInteraction/useGlobalInteraction.js +132 -0
- package/lib/primitive/GlobalInteraction/useGlobalInteractionContext.cjs +21 -0
- package/lib/primitive/GlobalInteraction/useGlobalInteractionContext.d.ts +8 -0
- package/lib/primitive/GlobalInteraction/useGlobalInteractionContext.js +16 -0
- package/lib/primitive/GlobalInteraction/useSwipeBack.cjs +58 -0
- package/lib/primitive/GlobalInteraction/useSwipeBack.d.ts +8 -0
- package/lib/primitive/GlobalInteraction/useSwipeBack.js +54 -0
- package/lib/primitive/index.cjs +27 -0
- package/lib/primitive/index.d.ts +3 -0
- package/lib/primitive/index.js +9 -0
- package/lib/primitive/private/useElementOffset.cjs +54 -0
- package/lib/primitive/private/useElementOffset.d.ts +4 -0
- package/lib/primitive/private/useElementOffset.js +31 -0
- package/lib/primitive/private/useTopActivity.cjs +34 -0
- package/lib/primitive/private/useTopActivity.d.ts +5 -0
- package/lib/primitive/private/useTopActivity.js +30 -0
- package/lib/utils/createStyleContext.cjs +77 -0
- package/lib/utils/createStyleContext.d.ts +23 -0
- package/lib/utils/createStyleContext.js +73 -0
- package/lib/utils/createWithStateProps.d.ts +10 -0
- package/lib/utils/styled.cjs +25 -0
- package/lib/utils/styled.d.ts +16 -0
- package/lib/utils/styled.js +21 -0
- package/package.json +61 -0
- package/src/components/AppBar/AppBar.namespace.ts +21 -0
- package/src/components/AppBar/AppBar.tsx +146 -0
- package/src/components/AppBar/index.ts +23 -0
- package/src/components/AppScreen/AppScreen.namespace.ts +14 -0
- package/src/components/AppScreen/AppScreen.tsx +83 -0
- package/src/components/AppScreen/index.ts +16 -0
- package/src/components/index.ts +2 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useActivityZIndexBase.ts +26 -0
- package/src/index.ts +6 -0
- package/src/plugin.tsx +27 -0
- package/src/primitive/AppBar/AppBar.namespace.ts +10 -0
- package/src/primitive/AppBar/AppBar.tsx +52 -0
- package/src/primitive/AppBar/index.ts +12 -0
- package/src/primitive/AppBar/useAppBar.tsx +42 -0
- package/src/primitive/AppBar/useAppBarContext.ts +21 -0
- package/src/primitive/AppScreen/AppScreen.namespace.ts +10 -0
- package/src/primitive/AppScreen/AppScreen.tsx +72 -0
- package/src/primitive/AppScreen/index.ts +13 -0
- package/src/primitive/AppScreen/useAppScreen.tsx +69 -0
- package/src/primitive/AppScreen/useAppScreenContext.ts +21 -0
- package/src/primitive/GlobalInteraction/GlobalInteraction.tsx +25 -0
- package/src/primitive/GlobalInteraction/index.ts +6 -0
- package/src/primitive/GlobalInteraction/useGlobalInteraction.ts +188 -0
- package/src/primitive/GlobalInteraction/useGlobalInteractionContext.tsx +21 -0
- package/src/primitive/GlobalInteraction/useSwipeBack.ts +56 -0
- package/src/primitive/index.ts +3 -0
- package/src/primitive/private/useElementOffset.ts +37 -0
- package/src/primitive/private/useTopActivity.ts +33 -0
- package/src/utils/createStyleContext.tsx +128 -0
- package/src/utils/createWithStateProps.tsx +28 -0
- package/src/utils/styled.ts +41 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import type { UseAppScreenReturn } from "./useAppScreen";
|
|
3
|
+
|
|
4
|
+
export interface UseAppScreenContext extends UseAppScreenReturn {}
|
|
5
|
+
|
|
6
|
+
const AppScreenContext = createContext<UseAppScreenContext | null>(null);
|
|
7
|
+
|
|
8
|
+
export const AppScreenProvider = AppScreenContext.Provider;
|
|
9
|
+
|
|
10
|
+
export function useAppScreenContext<T extends boolean | undefined = true>({
|
|
11
|
+
strict = true,
|
|
12
|
+
}: {
|
|
13
|
+
strict?: T;
|
|
14
|
+
} = {}): T extends false ? UseAppScreenContext | null : UseAppScreenContext {
|
|
15
|
+
const context = useContext(AppScreenContext);
|
|
16
|
+
if (!context && strict) {
|
|
17
|
+
throw new Error("useAppScreenContext must be used within a AppScreen");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return context as UseAppScreenContext;
|
|
21
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { composeRefs } from "@radix-ui/react-compose-refs";
|
|
2
|
+
import { mergeProps } from "@seed-design/dom-utils";
|
|
3
|
+
import { Primitive, type PrimitiveProps } from "@seed-design/react-primitive";
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import { useGlobalInteraction } from "./useGlobalInteraction";
|
|
6
|
+
import { GlobalInteractionProvider } from "./useGlobalInteractionContext";
|
|
7
|
+
|
|
8
|
+
export interface GlobalInteractionProps
|
|
9
|
+
extends PrimitiveProps,
|
|
10
|
+
React.HTMLAttributes<HTMLDivElement> {}
|
|
11
|
+
|
|
12
|
+
export const GlobalInteraction = forwardRef<HTMLDivElement, GlobalInteractionProps>(
|
|
13
|
+
(props, ref) => {
|
|
14
|
+
const api = useGlobalInteraction();
|
|
15
|
+
return (
|
|
16
|
+
<GlobalInteractionProvider value={api}>
|
|
17
|
+
<Primitive.div
|
|
18
|
+
ref={composeRefs(api.stackRef, ref)}
|
|
19
|
+
{...mergeProps(api.stackProps, props)}
|
|
20
|
+
/>
|
|
21
|
+
</GlobalInteractionProvider>
|
|
22
|
+
);
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
GlobalInteraction.displayName = "GlobalInteraction";
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { useCallbackRef } from "@radix-ui/react-use-callback-ref";
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { useTopActivity } from "../private/useTopActivity";
|
|
4
|
+
|
|
5
|
+
export type SwipeBackState = "idle" | "swiping" | "canceling" | "completing";
|
|
6
|
+
|
|
7
|
+
export type UseGlobalInteractionReturn = ReturnType<typeof useGlobalInteraction>;
|
|
8
|
+
|
|
9
|
+
export interface SwipeBackContext {
|
|
10
|
+
x0: number;
|
|
11
|
+
t0: number;
|
|
12
|
+
displacement: number;
|
|
13
|
+
displacementRatio: number;
|
|
14
|
+
velocity: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SwipeBackProps {
|
|
18
|
+
/**
|
|
19
|
+
* The threshold to determine whether the swipe back is intentional.
|
|
20
|
+
* @default 0.4
|
|
21
|
+
*/
|
|
22
|
+
swipeBackDisplacementRatioThreshold?: number;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The threshold to determine whether the swipe back is intentional.
|
|
26
|
+
* @default 1
|
|
27
|
+
*/
|
|
28
|
+
swipeBackVelocityThreshold?: number;
|
|
29
|
+
|
|
30
|
+
onSwipeBackStart?: () => void;
|
|
31
|
+
onSwipeBackMove?: (props: { displacement: number; displacementRatio: number }) => void;
|
|
32
|
+
onSwipeBackEnd?: (props: { swiped: boolean }) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface StartSwipeBackProps {
|
|
36
|
+
x0: number;
|
|
37
|
+
t0: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface MoveSwipeBackProps {
|
|
41
|
+
x: number;
|
|
42
|
+
t: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// biome-ignore lint/suspicious/noEmptyInterface: intentionally empty for future extension
|
|
46
|
+
export interface EndSwipeBackProps {}
|
|
47
|
+
|
|
48
|
+
export function useGlobalInteraction() {
|
|
49
|
+
const [swipeBackState, setSwipeBackState] = useState<SwipeBackState>("idle");
|
|
50
|
+
|
|
51
|
+
const swipeBackContextRef = useRef<SwipeBackContext>({
|
|
52
|
+
x0: 0,
|
|
53
|
+
t0: 0,
|
|
54
|
+
displacement: 0,
|
|
55
|
+
displacementRatio: 0,
|
|
56
|
+
velocity: 0,
|
|
57
|
+
});
|
|
58
|
+
const stackRef = useRef<HTMLDivElement>(null);
|
|
59
|
+
|
|
60
|
+
const setSwipeBackContext = useCallback((ctx: SwipeBackContext) => {
|
|
61
|
+
swipeBackContextRef.current = ctx;
|
|
62
|
+
stackRef.current?.style.setProperty(
|
|
63
|
+
"--swipe-back-displacement",
|
|
64
|
+
`${ctx.displacement.toString()}px`,
|
|
65
|
+
);
|
|
66
|
+
stackRef.current?.style.setProperty(
|
|
67
|
+
"--swipe-back-displacement-ratio",
|
|
68
|
+
ctx.displacementRatio.toString(),
|
|
69
|
+
);
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
const getSwipeBackEvents = useCallback(
|
|
73
|
+
(props: SwipeBackProps) => {
|
|
74
|
+
const {
|
|
75
|
+
swipeBackDisplacementRatioThreshold: displacementRatioThreshold = 0.4,
|
|
76
|
+
swipeBackVelocityThreshold: velocityThreshold = 1,
|
|
77
|
+
} = props;
|
|
78
|
+
const onSwipeStart = useCallbackRef(props.onSwipeBackStart);
|
|
79
|
+
const onSwipeMove = useCallbackRef(props.onSwipeBackMove);
|
|
80
|
+
const onSwipeEnd = useCallbackRef(props.onSwipeBackEnd);
|
|
81
|
+
|
|
82
|
+
const startSwipeBack = useCallback(
|
|
83
|
+
({ x0, t0 }: StartSwipeBackProps) => {
|
|
84
|
+
setSwipeBackContext({
|
|
85
|
+
x0,
|
|
86
|
+
t0,
|
|
87
|
+
displacement: 0,
|
|
88
|
+
displacementRatio: 0,
|
|
89
|
+
velocity: 0,
|
|
90
|
+
});
|
|
91
|
+
setSwipeBackState((prev) => (prev === "swiping" ? prev : "swiping"));
|
|
92
|
+
onSwipeStart?.();
|
|
93
|
+
},
|
|
94
|
+
[onSwipeStart],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const moveSwipeBack = useCallback(
|
|
98
|
+
({ x, t }: MoveSwipeBackProps) => {
|
|
99
|
+
const displacement = Math.max(0, x - swipeBackContextRef.current.x0);
|
|
100
|
+
const displacementRatio = displacement / window.innerWidth;
|
|
101
|
+
const velocity = displacement / (t - swipeBackContextRef.current.t0);
|
|
102
|
+
setSwipeBackContext({
|
|
103
|
+
...swipeBackContextRef.current,
|
|
104
|
+
displacement,
|
|
105
|
+
displacementRatio,
|
|
106
|
+
velocity,
|
|
107
|
+
});
|
|
108
|
+
setSwipeBackState((prev) => (prev === "swiping" ? prev : "swiping"));
|
|
109
|
+
onSwipeMove?.({ displacement, displacementRatio });
|
|
110
|
+
},
|
|
111
|
+
[onSwipeMove],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const endSwipeBack = useCallback(
|
|
115
|
+
(_: EndSwipeBackProps) => {
|
|
116
|
+
const swiped =
|
|
117
|
+
swipeBackContextRef.current.displacementRatio > displacementRatioThreshold ||
|
|
118
|
+
swipeBackContextRef.current.velocity > velocityThreshold;
|
|
119
|
+
|
|
120
|
+
if (swiped) {
|
|
121
|
+
stackRef.current?.style.setProperty("--swipe-back-target", "100%");
|
|
122
|
+
setSwipeBackState("completing");
|
|
123
|
+
} else {
|
|
124
|
+
stackRef.current?.style.setProperty("--swipe-back-target", "0");
|
|
125
|
+
setSwipeBackState("canceling");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
onSwipeEnd?.({ swiped });
|
|
129
|
+
},
|
|
130
|
+
[onSwipeEnd, displacementRatioThreshold, velocityThreshold],
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const reset = useCallback(() => {
|
|
134
|
+
setSwipeBackContext({
|
|
135
|
+
x0: 0,
|
|
136
|
+
t0: 0,
|
|
137
|
+
displacement: 0,
|
|
138
|
+
displacementRatio: 0,
|
|
139
|
+
velocity: 0,
|
|
140
|
+
});
|
|
141
|
+
stackRef.current?.style.setProperty("--swipe-back-target", "0");
|
|
142
|
+
setSwipeBackState("idle");
|
|
143
|
+
}, []);
|
|
144
|
+
|
|
145
|
+
return useMemo(
|
|
146
|
+
() => ({
|
|
147
|
+
startSwipeBack,
|
|
148
|
+
moveSwipeBack,
|
|
149
|
+
endSwipeBack,
|
|
150
|
+
reset,
|
|
151
|
+
}),
|
|
152
|
+
[startSwipeBack, moveSwipeBack, endSwipeBack, reset],
|
|
153
|
+
);
|
|
154
|
+
},
|
|
155
|
+
[setSwipeBackContext],
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const topActivity = useTopActivity();
|
|
159
|
+
|
|
160
|
+
const stackProps = useMemo(
|
|
161
|
+
() => ({
|
|
162
|
+
"data-swipe-back-state": swipeBackState,
|
|
163
|
+
"data-global-transition-state": topActivity.transitionState,
|
|
164
|
+
"data-top-activity-type": topActivity.activityType,
|
|
165
|
+
"data-top-transition-style": topActivity.transitionStyle,
|
|
166
|
+
}),
|
|
167
|
+
[
|
|
168
|
+
swipeBackState,
|
|
169
|
+
topActivity.transitionState,
|
|
170
|
+
topActivity.activityType,
|
|
171
|
+
topActivity.transitionStyle,
|
|
172
|
+
],
|
|
173
|
+
) as React.HTMLAttributes<HTMLElement>;
|
|
174
|
+
|
|
175
|
+
return useMemo(
|
|
176
|
+
() => ({
|
|
177
|
+
stackRef,
|
|
178
|
+
swipeBackContextRef,
|
|
179
|
+
swipeBackState,
|
|
180
|
+
setSwipeBackState,
|
|
181
|
+
setSwipeBackContext,
|
|
182
|
+
getSwipeBackEvents,
|
|
183
|
+
|
|
184
|
+
stackProps,
|
|
185
|
+
}),
|
|
186
|
+
[swipeBackState, setSwipeBackContext, getSwipeBackEvents, stackProps],
|
|
187
|
+
);
|
|
188
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import type { UseGlobalInteractionReturn } from "./useGlobalInteraction";
|
|
3
|
+
|
|
4
|
+
export interface UseGlobalInteractionContext extends UseGlobalInteractionReturn {}
|
|
5
|
+
|
|
6
|
+
const GlobalInteractionContext = createContext<UseGlobalInteractionContext | null>(null);
|
|
7
|
+
|
|
8
|
+
export const GlobalInteractionProvider = GlobalInteractionContext.Provider;
|
|
9
|
+
|
|
10
|
+
export function useGlobalInteractionContext<T extends boolean | undefined = true>({
|
|
11
|
+
strict = true,
|
|
12
|
+
}: {
|
|
13
|
+
strict?: T;
|
|
14
|
+
} = {}): T extends false ? UseGlobalInteractionContext | null : UseGlobalInteractionContext {
|
|
15
|
+
const context = useContext(GlobalInteractionContext);
|
|
16
|
+
if (!context && strict) {
|
|
17
|
+
throw new Error("useGlobalInteractionContext must be used within a GlobalInteractionProvider");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return context as UseGlobalInteractionContext;
|
|
21
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useEffect, useMemo } from "react";
|
|
2
|
+
import type { SwipeBackProps } from "./useGlobalInteraction";
|
|
3
|
+
import { useGlobalInteractionContext } from "./useGlobalInteractionContext";
|
|
4
|
+
|
|
5
|
+
export interface UseSwipeBackProps extends SwipeBackProps {}
|
|
6
|
+
|
|
7
|
+
export function useSwipeBack(props: UseSwipeBackProps) {
|
|
8
|
+
const globalInteraction = useGlobalInteractionContext();
|
|
9
|
+
const events = globalInteraction.getSwipeBackEvents(props);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
return () => {
|
|
13
|
+
events.reset();
|
|
14
|
+
};
|
|
15
|
+
}, [events]);
|
|
16
|
+
|
|
17
|
+
return useMemo(
|
|
18
|
+
() => ({
|
|
19
|
+
activityProps: {
|
|
20
|
+
"data-swipe-back": "",
|
|
21
|
+
} as React.HTMLAttributes<HTMLDivElement>,
|
|
22
|
+
layerProps: {
|
|
23
|
+
onAnimationEnd: (e) => {
|
|
24
|
+
if (e.target === e.currentTarget) {
|
|
25
|
+
events.reset();
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
onTransitionEnd: (e) => {
|
|
29
|
+
if (e.target === e.currentTarget) {
|
|
30
|
+
events.reset();
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
} as React.HTMLAttributes<HTMLDivElement>,
|
|
34
|
+
edgeProps: {
|
|
35
|
+
tabIndex: -1,
|
|
36
|
+
onTouchStart: (e) => {
|
|
37
|
+
const x0 = e.touches[0].clientX;
|
|
38
|
+
const t0 = Date.now();
|
|
39
|
+
events.startSwipeBack({ x0, t0 });
|
|
40
|
+
},
|
|
41
|
+
onTouchMove: (e) => {
|
|
42
|
+
const x = e.touches[0].clientX;
|
|
43
|
+
const t = Date.now();
|
|
44
|
+
events.moveSwipeBack({ x, t });
|
|
45
|
+
},
|
|
46
|
+
onTouchEnd: () => {
|
|
47
|
+
events.endSwipeBack({});
|
|
48
|
+
},
|
|
49
|
+
onTouchCancel: () => {
|
|
50
|
+
events.endSwipeBack({});
|
|
51
|
+
},
|
|
52
|
+
} as React.HTMLAttributes<HTMLElement>,
|
|
53
|
+
}),
|
|
54
|
+
[events],
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
const useLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
|
|
4
|
+
|
|
5
|
+
export function useElementOffset(element: HTMLElement | null) {
|
|
6
|
+
const [offset, setOffset] = React.useState<{ fromLeft: number; fromRight: number } | undefined>(
|
|
7
|
+
undefined,
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
useLayoutEffect(() => {
|
|
11
|
+
if (!element) {
|
|
12
|
+
setOffset(undefined);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (element) {
|
|
17
|
+
// provide as early as possible
|
|
18
|
+
setOffset({
|
|
19
|
+
fromLeft: element.offsetLeft + element.offsetWidth,
|
|
20
|
+
fromRight: (element.offsetParent ?? document.body).clientWidth - element.offsetLeft,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
24
|
+
const fromLeft = element.offsetLeft + element.offsetWidth;
|
|
25
|
+
const fromRight = (element.offsetParent ?? document.body).clientWidth - element.offsetLeft;
|
|
26
|
+
|
|
27
|
+
setOffset({ fromLeft, fromRight });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
resizeObserver.observe(element);
|
|
31
|
+
|
|
32
|
+
return () => resizeObserver.unobserve(element);
|
|
33
|
+
}
|
|
34
|
+
}, [element]);
|
|
35
|
+
|
|
36
|
+
return offset;
|
|
37
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useStack } from "@stackflow/react";
|
|
2
|
+
import { useLayoutEffect, useState } from "react";
|
|
3
|
+
|
|
4
|
+
export function useTopActivity() {
|
|
5
|
+
const stack = useStack();
|
|
6
|
+
if (!stack) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
"useStack is not available in the context. Make sure you are using @stackflow/react >= 1.4.1. (https://github.com/daangn/stackflow/blob/main/integrations/react/CHANGELOG.md#141)",
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const topId = stack.activities.find((activity) => activity.isTop)?.id;
|
|
13
|
+
const topActivity = stack.activities.find((activity) => activity.isTop);
|
|
14
|
+
const transitionState = topActivity?.transitionState ?? "enter-done";
|
|
15
|
+
|
|
16
|
+
// TODO: There should be a better way to handle this. We should not rely on the DOM element to get the activity type.
|
|
17
|
+
const [topEl, setTopEl] = useState<HTMLElement | null>(null);
|
|
18
|
+
const activityType = topEl?.dataset["activityType"];
|
|
19
|
+
const transitionStyle = topEl?.dataset["transitionStyle"];
|
|
20
|
+
|
|
21
|
+
useLayoutEffect(() => {
|
|
22
|
+
if (!topId) return;
|
|
23
|
+
|
|
24
|
+
const el = document.querySelector<HTMLElement>(`[data-activity-id="${topId}"]`);
|
|
25
|
+
setTopEl(el);
|
|
26
|
+
}, [topId]);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
transitionState,
|
|
30
|
+
activityType,
|
|
31
|
+
transitionStyle,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { createContext, forwardRef, useContext } from "react";
|
|
3
|
+
|
|
4
|
+
type Recipe<
|
|
5
|
+
Props extends Record<string, string | boolean | undefined>,
|
|
6
|
+
Classnames extends Record<string, string>,
|
|
7
|
+
> = ((props?: Props) => Classnames) & {
|
|
8
|
+
splitVariantProps: <T extends Props>(props: T) => [Props, Omit<T, keyof Props>];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function createStyleContext<
|
|
12
|
+
Props extends Record<string, string | boolean | undefined>,
|
|
13
|
+
Classnames extends Record<string, string>,
|
|
14
|
+
>(recipe: Recipe<Props, Classnames>) {
|
|
15
|
+
const ClassNamesContext = createContext<Classnames | null>(null);
|
|
16
|
+
const PropsContext = createContext<Props | null>(null);
|
|
17
|
+
|
|
18
|
+
const ClassNamesProvider = ({
|
|
19
|
+
children,
|
|
20
|
+
value,
|
|
21
|
+
}: {
|
|
22
|
+
children: React.ReactNode;
|
|
23
|
+
value: Classnames;
|
|
24
|
+
}) => {
|
|
25
|
+
return <ClassNamesContext.Provider value={value}>{children}</ClassNamesContext.Provider>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const PropsProvider = ({ children, value }: { children: React.ReactNode; value: Props }) => {
|
|
29
|
+
return <PropsContext.Provider value={value}>{children}</PropsContext.Provider>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function useClassNames() {
|
|
33
|
+
const context = useContext(ClassNamesContext);
|
|
34
|
+
if (context === null) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
"useClassNames must be used within a ClassNamesProvider. Did you forget to wrap your component in a ClassNamesProvider?",
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return context;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function useProps() {
|
|
44
|
+
return useContext(PropsContext);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const withRootProvider = <P,>(
|
|
48
|
+
Component: React.ElementType<any>,
|
|
49
|
+
options?: {
|
|
50
|
+
defaultProps?: Partial<P>;
|
|
51
|
+
},
|
|
52
|
+
): React.ForwardRefExoticComponent<React.PropsWithoutRef<P>> => {
|
|
53
|
+
const { defaultProps } = options ?? {};
|
|
54
|
+
|
|
55
|
+
const StyledComponent = (innerProps: any) => {
|
|
56
|
+
const props = { ...(defaultProps ?? {}), ...useProps(), ...innerProps } as Props &
|
|
57
|
+
React.HTMLAttributes<HTMLElement>;
|
|
58
|
+
const [variantProps, otherProps] = recipe.splitVariantProps(props);
|
|
59
|
+
const classNames = recipe(variantProps); // TODO: should we memoize this?
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<ClassNamesProvider value={classNames}>
|
|
63
|
+
<Component {...otherProps} />
|
|
64
|
+
</ClassNamesProvider>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// @ts-ignore
|
|
69
|
+
StyledComponent.displayName = Component.displayName || Component.name;
|
|
70
|
+
|
|
71
|
+
return StyledComponent as any;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const withProvider = <T, P>(
|
|
75
|
+
Component: React.ElementType<any>,
|
|
76
|
+
slot: keyof Classnames,
|
|
77
|
+
options?: {
|
|
78
|
+
defaultProps?: Partial<P>;
|
|
79
|
+
},
|
|
80
|
+
): React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<T>> => {
|
|
81
|
+
const { defaultProps } = options ?? {};
|
|
82
|
+
|
|
83
|
+
const StyledComponent = forwardRef<any, any>((innerProps, ref) => {
|
|
84
|
+
const props = { ...(defaultProps ?? {}), ...useProps(), ...innerProps } as Props &
|
|
85
|
+
React.HTMLAttributes<HTMLElement>;
|
|
86
|
+
const [variantProps, otherProps] = recipe.splitVariantProps(props);
|
|
87
|
+
const classNames = recipe(variantProps); // TODO: should we memoize this?
|
|
88
|
+
const className = classNames[slot as keyof typeof classNames];
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<ClassNamesProvider value={classNames}>
|
|
92
|
+
<Component ref={ref} {...otherProps} className={clsx(className, props.className)} />
|
|
93
|
+
</ClassNamesProvider>
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// @ts-ignore
|
|
98
|
+
StyledComponent.displayName = Component.displayName || Component.name;
|
|
99
|
+
|
|
100
|
+
return StyledComponent as any;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const withContext = <T, P>(
|
|
104
|
+
Component: React.ElementType<any>,
|
|
105
|
+
slot?: keyof Classnames,
|
|
106
|
+
): React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<T>> => {
|
|
107
|
+
const StyledComponent = forwardRef<any, React.HTMLAttributes<HTMLElement>>((props, ref) => {
|
|
108
|
+
const classNames = useClassNames();
|
|
109
|
+
const className = classNames?.[slot as keyof typeof classNames];
|
|
110
|
+
|
|
111
|
+
return <Component ref={ref} {...props} className={clsx(className, props.className)} />;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// @ts-ignore
|
|
115
|
+
StyledComponent.displayName = Component.displayName || Component.name;
|
|
116
|
+
return StyledComponent as any;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
ClassNamesProvider,
|
|
121
|
+
PropsProvider,
|
|
122
|
+
useClassNames,
|
|
123
|
+
useProps,
|
|
124
|
+
withRootProvider,
|
|
125
|
+
withProvider,
|
|
126
|
+
withContext,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
|
|
3
|
+
type AtLeastOne<T> = [T, ...T[]];
|
|
4
|
+
|
|
5
|
+
export function createWithStateProps(
|
|
6
|
+
useContexts: AtLeastOne<
|
|
7
|
+
(prop?: { strict?: boolean }) => { stateProps: React.HTMLAttributes<HTMLElement> } | null
|
|
8
|
+
>,
|
|
9
|
+
options?: { strict?: boolean },
|
|
10
|
+
) {
|
|
11
|
+
const strict = options?.strict ?? true;
|
|
12
|
+
|
|
13
|
+
return function withStateProps<P, R>(Component: React.ComponentType<P & React.RefAttributes<R>>) {
|
|
14
|
+
const Node = forwardRef<R, P>((props, ref) => {
|
|
15
|
+
const stateProps = {};
|
|
16
|
+
for (const useContext of useContexts) {
|
|
17
|
+
Object.assign(stateProps, useContext({ strict })?.stateProps);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
return <Component ref={ref} {...stateProps} {...props} />;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
Node.displayName = Component.displayName || Component.name;
|
|
25
|
+
|
|
26
|
+
return Node;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ScopedColorBg, ScopedColorPalette, ScopedColorBanner } from "@seed-design/css/vars";
|
|
2
|
+
import { vars } from "@seed-design/css/vars";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A minimal subset of `StyleProps` from `@seed-design/react`.
|
|
6
|
+
* Copied here to avoid package dependency. May be extracted to a shared package later.
|
|
7
|
+
*/
|
|
8
|
+
export interface BoxBackgroundProps {
|
|
9
|
+
/**
|
|
10
|
+
* Shorthand for `background`.
|
|
11
|
+
*/
|
|
12
|
+
bg?: ScopedColorBg | ScopedColorPalette | ScopedColorBanner | (string & {});
|
|
13
|
+
|
|
14
|
+
background?: ScopedColorBg | ScopedColorPalette | ScopedColorBanner | (string & {});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function handleColor(color: string | undefined) {
|
|
18
|
+
if (!color) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const [type, value] = color.split(".");
|
|
22
|
+
|
|
23
|
+
// @ts-expect-error - dynamic access
|
|
24
|
+
return vars.$color[type]?.[value] ?? color;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useBoxBackgroundProps<T extends BoxBackgroundProps>(
|
|
28
|
+
props: T,
|
|
29
|
+
): {
|
|
30
|
+
style: React.CSSProperties;
|
|
31
|
+
restProps: Omit<T, keyof BoxBackgroundProps>;
|
|
32
|
+
} {
|
|
33
|
+
const { bg, background, ...restProps } = props;
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
style: {
|
|
37
|
+
"--seed-box-background": handleColor(background ?? bg),
|
|
38
|
+
} as React.CSSProperties,
|
|
39
|
+
restProps: restProps as Omit<T, keyof BoxBackgroundProps>,
|
|
40
|
+
};
|
|
41
|
+
}
|