@stackflow/plugin-basic-ui 0.14.1

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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/dist/assets/IconBack.d.ts +5 -0
  4. package/dist/assets/IconClose.d.ts +5 -0
  5. package/dist/assets/index.d.ts +2 -0
  6. package/dist/basicUIPlugin.d.ts +10 -0
  7. package/dist/components/AppBar.css.d.ts +24 -0
  8. package/dist/components/AppBar.d.ts +27 -0
  9. package/dist/components/AppScreen.css.d.ts +44 -0
  10. package/dist/components/AppScreen.d.ts +10 -0
  11. package/dist/components/BottomSheet.css.d.ts +21 -0
  12. package/dist/components/BottomSheet.d.ts +7 -0
  13. package/dist/components/Modal.css.d.ts +21 -0
  14. package/dist/components/Modal.d.ts +7 -0
  15. package/dist/hooks/index.d.ts +7 -0
  16. package/dist/hooks/useLazy.d.ts +1 -0
  17. package/dist/hooks/useMaxWidth.d.ts +8 -0
  18. package/dist/hooks/useStyleEffect.d.ts +10 -0
  19. package/dist/hooks/useStyleEffectHide.d.ts +5 -0
  20. package/dist/hooks/useStyleEffectOffset.d.ts +8 -0
  21. package/dist/hooks/useStyleEffectSwipeBack.d.ts +9 -0
  22. package/dist/hooks/useTheme.d.ts +3 -0
  23. package/dist/index.css +2 -0
  24. package/dist/index.css.map +7 -0
  25. package/dist/index.d.ts +5 -0
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +7 -0
  28. package/dist/index.mjs +2 -0
  29. package/dist/index.mjs.map +7 -0
  30. package/dist/styles/f.css.d.ts +18 -0
  31. package/dist/styles/index.d.ts +1 -0
  32. package/dist/theme.css.d.ts +25 -0
  33. package/dist/utils/PropOf.d.ts +2 -0
  34. package/dist/utils/compactMap.d.ts +3 -0
  35. package/dist/utils/index.d.ts +7 -0
  36. package/dist/utils/last.d.ts +1 -0
  37. package/dist/utils/listenOnce.d.ts +1 -0
  38. package/dist/utils/listenResize.d.ts +1 -0
  39. package/dist/utils/noop.d.ts +1 -0
  40. package/dist/utils/requestNextFrame.d.ts +1 -0
  41. package/package.json +61 -0
  42. package/src/assets/IconBack.tsx +21 -0
  43. package/src/assets/IconClose.tsx +25 -0
  44. package/src/assets/index.ts +2 -0
  45. package/src/basicUIPlugin.tsx +48 -0
  46. package/src/components/AppBar.css.ts +173 -0
  47. package/src/components/AppBar.tsx +193 -0
  48. package/src/components/AppScreen.css.ts +159 -0
  49. package/src/components/AppScreen.tsx +106 -0
  50. package/src/components/BottomSheet.css.ts +78 -0
  51. package/src/components/BottomSheet.tsx +91 -0
  52. package/src/components/Modal.css.ts +82 -0
  53. package/src/components/Modal.tsx +91 -0
  54. package/src/hooks/index.ts +7 -0
  55. package/src/hooks/useLazy.tsx +15 -0
  56. package/src/hooks/useMaxWidth.ts +43 -0
  57. package/src/hooks/useStyleEffect.ts +76 -0
  58. package/src/hooks/useStyleEffectHide.ts +34 -0
  59. package/src/hooks/useStyleEffectOffset.ts +57 -0
  60. package/src/hooks/useStyleEffectSwipeBack.ts +228 -0
  61. package/src/hooks/useTheme.ts +9 -0
  62. package/src/index.ts +5 -0
  63. package/src/styles/f.css.ts +84 -0
  64. package/src/styles/index.ts +1 -0
  65. package/src/theme.css.ts +75 -0
  66. package/src/utils/PropOf.ts +1 -0
  67. package/src/utils/compactMap.ts +13 -0
  68. package/src/utils/index.ts +7 -0
  69. package/src/utils/last.ts +1 -0
  70. package/src/utils/listenOnce.ts +12 -0
  71. package/src/utils/listenResize.ts +8 -0
  72. package/src/utils/noop.ts +1 -0
  73. package/src/utils/requestNextFrame.tsx +8 -0
@@ -0,0 +1,228 @@
1
+ /* eslint-disable no-param-reassign */
2
+
3
+ import { listenOnce } from "../utils";
4
+ import { useStyleEffect } from "./useStyleEffect";
5
+ import { OFFSET_PX_CUPERTINO } from "./useStyleEffectOffset";
6
+
7
+ export function useStyleEffectSwipeBack({
8
+ theme,
9
+ dimRef,
10
+ edgeRef,
11
+ paperRef,
12
+ hasEffect,
13
+ onSwiped,
14
+ }: {
15
+ theme: "android" | "cupertino";
16
+ dimRef: React.RefObject<HTMLDivElement>;
17
+ edgeRef: React.RefObject<HTMLDivElement>;
18
+ paperRef: React.RefObject<HTMLDivElement>;
19
+ hasEffect?: boolean;
20
+ onSwiped?: () => void;
21
+ }) {
22
+ useStyleEffect({
23
+ styleName: "swipe-back",
24
+ refs: [paperRef],
25
+ effect: hasEffect
26
+ ? ({ refs }) => {
27
+ if (theme !== "cupertino") {
28
+ return () => {};
29
+ }
30
+
31
+ if (!dimRef.current || !edgeRef.current || !paperRef.current) {
32
+ return () => {};
33
+ }
34
+
35
+ const $dim = dimRef.current;
36
+ const $edge = edgeRef.current;
37
+ const $paper = paperRef.current;
38
+
39
+ let x0: number | null = null;
40
+ let t0: number | null = null;
41
+ let x: number | null = null;
42
+
43
+ let cachedRefs: Array<{
44
+ style: {
45
+ transform: string;
46
+ transition: string;
47
+ };
48
+ parentElement?: {
49
+ style: {
50
+ display: string;
51
+ };
52
+ };
53
+ }> = [];
54
+
55
+ const resetState = () => {
56
+ x0 = null;
57
+ t0 = null;
58
+ x = null;
59
+ cachedRefs = [];
60
+ };
61
+
62
+ let _rAFLock = false;
63
+
64
+ function movePaper(dx: number) {
65
+ if (!_rAFLock) {
66
+ _rAFLock = true;
67
+
68
+ requestAnimationFrame(() => {
69
+ const p = dx / $paper.clientWidth;
70
+
71
+ $dim.style.opacity = `${1 - p}`;
72
+ $dim.style.transition = "0s";
73
+
74
+ $paper.style.overflowY = "hidden";
75
+ $paper.style.transform = `translateX(${dx}px)`;
76
+ $paper.style.transition = "0s";
77
+
78
+ refs.forEach((ref: React.RefObject<HTMLElement>) => {
79
+ if (!ref.current) {
80
+ return;
81
+ }
82
+
83
+ ref.current.style.transform = `translateX(${
84
+ -1 * (1 - p) * OFFSET_PX_CUPERTINO
85
+ }px)`;
86
+ ref.current.style.transition = "0s";
87
+
88
+ if (ref.current.parentElement?.style.display === "none") {
89
+ ref.current.parentElement.style.display = "block";
90
+ }
91
+ });
92
+
93
+ _rAFLock = false;
94
+ });
95
+ }
96
+ }
97
+
98
+ function resetPaper({ swiped }: { swiped: boolean }) {
99
+ requestAnimationFrame(() => {
100
+ $dim.style.opacity = `${swiped ? 0 : 1}`;
101
+ $dim.style.transition = "var(--stackflow-transition-duration)";
102
+
103
+ $paper.style.overflowY = "hidden";
104
+ $paper.style.transform = `translateX(${swiped ? "100%" : "0"})`;
105
+ $paper.style.transition = "var(--stackflow-transition-duration)";
106
+
107
+ refs.forEach((ref: React.RefObject<HTMLElement>) => {
108
+ if (!ref.current) {
109
+ return;
110
+ }
111
+
112
+ ref.current.style.transition = `var(--stackflow-transition-duration)`;
113
+ ref.current.style.transform = `translateX(${
114
+ swiped ? "0" : `-${OFFSET_PX_CUPERTINO / 16}rem`
115
+ })`;
116
+ });
117
+
118
+ listenOnce($paper, "transitionend", () => {
119
+ $dim.style.opacity = "";
120
+ $paper.style.overflowY = "";
121
+ $paper.style.transform = "";
122
+
123
+ refs.forEach((ref: React.RefObject<HTMLElement>, i) => {
124
+ if (!ref.current) {
125
+ return;
126
+ }
127
+
128
+ const cachedRef = cachedRefs[i];
129
+
130
+ if (swiped) {
131
+ ref.current.style.transition = "";
132
+ ref.current.style.transform = "";
133
+
134
+ if (ref.current.parentElement) {
135
+ ref.current.parentElement.style.display = "";
136
+ }
137
+ } else if (cachedRef) {
138
+ ref.current.style.transition = cachedRef.style.transition;
139
+ ref.current.style.transform = cachedRef.style.transform;
140
+
141
+ if (ref.current.parentElement && cachedRef.parentElement) {
142
+ ref.current.parentElement.style.display =
143
+ cachedRef.parentElement.style.display;
144
+ }
145
+ }
146
+ });
147
+ });
148
+ });
149
+ }
150
+
151
+ const onTouchStart = (e: TouchEvent) => {
152
+ const { activeElement } = document as any;
153
+
154
+ activeElement?.blur?.();
155
+ // eslint-disable-next-line no-multi-assign
156
+ x0 = x = e.touches[0].clientX;
157
+ t0 = Date.now();
158
+
159
+ cachedRefs = refs.map((ref) => {
160
+ if (!ref.current) {
161
+ return {
162
+ style: {
163
+ transform: "",
164
+ transition: "",
165
+ },
166
+ };
167
+ }
168
+
169
+ return {
170
+ style: {
171
+ transform: ref.current.style.transform,
172
+ transition: ref.current.style.transition,
173
+ },
174
+ parentElement: ref.current.parentElement
175
+ ? {
176
+ style: {
177
+ display: ref.current.parentElement.style.display,
178
+ },
179
+ }
180
+ : undefined,
181
+ };
182
+ });
183
+ };
184
+
185
+ const onTouchMove = (e: TouchEvent) => {
186
+ if (!x0) {
187
+ resetState();
188
+ return;
189
+ }
190
+
191
+ x = e.touches[0].clientX;
192
+
193
+ movePaper(x - x0);
194
+ };
195
+
196
+ const onTouchEnd = () => {
197
+ if (!x0 || !t0 || !x) {
198
+ resetState();
199
+ return;
200
+ }
201
+
202
+ const t = Date.now();
203
+ const v = (x - x0) / (t - t0);
204
+ const swiped = v > 1 || x / $paper.clientWidth > 0.4;
205
+
206
+ if (swiped) {
207
+ onSwiped?.();
208
+ }
209
+
210
+ resetState();
211
+ resetPaper({ swiped });
212
+ };
213
+
214
+ $edge.addEventListener("touchstart", onTouchStart, { passive: true });
215
+ $edge.addEventListener("touchmove", onTouchMove, { passive: true });
216
+ $edge.addEventListener("touchend", onTouchEnd, { passive: true });
217
+ $edge.addEventListener("touchcancel", onTouchEnd, { passive: true });
218
+
219
+ return () => {
220
+ $edge.removeEventListener("touchstart", onTouchStart);
221
+ $edge.removeEventListener("touchmove", onTouchMove);
222
+ $edge.removeEventListener("touchend", onTouchEnd);
223
+ $edge.removeEventListener("touchcancel", onTouchEnd);
224
+ };
225
+ }
226
+ : undefined,
227
+ });
228
+ }
@@ -0,0 +1,9 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ const ThemeContext = createContext<"android" | "cupertino">("android");
4
+
5
+ export const ThemeProvider = ThemeContext.Provider;
6
+
7
+ export function useTheme() {
8
+ return useContext(ThemeContext);
9
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { basicUIPlugin } from "./basicUIPlugin";
2
+ export { default as AppScreen } from "./components/AppScreen";
3
+ export { default as BottomSheet } from "./components/BottomSheet";
4
+ export { default as Modal } from "./components/Modal";
5
+ export { globalVars as cssVars } from "./theme.css";
@@ -0,0 +1,84 @@
1
+ import { style } from "@vanilla-extract/css";
2
+
3
+ export const fullWidth = style({
4
+ width: "100%",
5
+ });
6
+
7
+ export const fullHeight = style({
8
+ height: "100%",
9
+ });
10
+
11
+ export const top0 = style({
12
+ top: 0,
13
+ });
14
+
15
+ export const left0 = style({
16
+ left: 0,
17
+ });
18
+
19
+ export const posAbs = style({
20
+ position: "absolute",
21
+ });
22
+
23
+ export const posRel = style({
24
+ position: "relative",
25
+ });
26
+
27
+ export const posAbsFull = style([posAbs, fullWidth, fullHeight, top0, left0]);
28
+
29
+ export const flex = style({
30
+ display: "flex",
31
+ });
32
+
33
+ export const flexAlignCenter = style([
34
+ flex,
35
+ {
36
+ alignItems: "center",
37
+ },
38
+ ]);
39
+
40
+ export const flexAlignEnd = style([
41
+ flex,
42
+ {
43
+ alignItems: "flex-end",
44
+ },
45
+ ]);
46
+
47
+ export const flexJustifyCenter = style([
48
+ flex,
49
+ {
50
+ justifyContent: "center",
51
+ },
52
+ ]);
53
+
54
+ export const overflowHidden = style({
55
+ overflow: "hidden",
56
+ });
57
+
58
+ export const whiteSpaceNowrap = style({
59
+ whiteSpace: "nowrap",
60
+ });
61
+
62
+ export const textAlignCenter = style({
63
+ textAlign: "center",
64
+ });
65
+
66
+ export const cursorPointer = style({
67
+ cursor: "pointer",
68
+ WebkitTapHighlightColor: "transparent",
69
+ });
70
+
71
+ export const borderBox = style({
72
+ boxSizing: "border-box",
73
+ });
74
+
75
+ export const contentBox = style({
76
+ boxSizing: "content-box",
77
+ });
78
+
79
+ export const resetButton = style({
80
+ appearance: "none",
81
+ border: 0,
82
+ padding: 0,
83
+ background: "none",
84
+ });
@@ -0,0 +1 @@
1
+ export * as f from "./f.css";
@@ -0,0 +1,75 @@
1
+ import {
2
+ createGlobalTheme,
3
+ createGlobalThemeContract,
4
+ createTheme,
5
+ } from "@vanilla-extract/css";
6
+ import type { MapLeafNodes } from "@vanilla-extract/private";
7
+
8
+ export const globalVars = createGlobalThemeContract(
9
+ {
10
+ backgroundColor: "background-color",
11
+ dimBackgroundColor: "dim-background-color",
12
+ appBar: {
13
+ borderColor: "app-bar-border-color",
14
+ borderSize: "app-bar-border-size",
15
+ height: "app-bar-height",
16
+ iconColor: "app-bar-icon-color",
17
+ textColor: "app-bar-text-color",
18
+ },
19
+ bottomSheet: {
20
+ borderRadius: "bottom-sheet-border-radius",
21
+ },
22
+ modal: {
23
+ borderRadius: "bottom-sheet-border-radius",
24
+ },
25
+ },
26
+ (value) => `stackflow-basic-ui-${value}`,
27
+ );
28
+
29
+ type InferVars<T> = T extends MapLeafNodes<infer U, any> ? U : never;
30
+ export type GlobalVars = InferVars<typeof globalVars>;
31
+
32
+ const defaultVars = {
33
+ backgroundColor: "#fff",
34
+ dimBackgroundColor: "rgba(0, 0, 0, 0.15)",
35
+ appBar: {
36
+ borderColor: "rgba(0, 0, 0, 0.07)",
37
+ borderSize: "1px",
38
+ height: "3.5rem",
39
+ iconColor: "#212124",
40
+ textColor: "#212124",
41
+ },
42
+ bottomSheet: {
43
+ borderRadius: "1rem",
44
+ },
45
+ modal: {
46
+ borderRadius: "1rem",
47
+ },
48
+ };
49
+
50
+ const cupertinoVars = {
51
+ ...defaultVars,
52
+ appBar: {
53
+ ...defaultVars.appBar,
54
+ height: "2.75rem",
55
+ borderSize: "0.5px",
56
+ },
57
+ };
58
+
59
+ const root = ":root";
60
+ export const rootAndroid = ":root[data-stackflow-basic-ui-theme=android]";
61
+ export const rootCupertino = ":root[data-stackflow-basic-ui-theme=cupertino]";
62
+
63
+ createGlobalTheme(`${root}, ${rootAndroid}`, globalVars, {
64
+ ...defaultVars,
65
+ });
66
+ createGlobalTheme(rootCupertino, globalVars, {
67
+ ...cupertinoVars,
68
+ });
69
+
70
+ export const android = createTheme(globalVars, {
71
+ ...defaultVars,
72
+ });
73
+ export const cupertino = createTheme(globalVars, {
74
+ ...cupertinoVars,
75
+ });
@@ -0,0 +1 @@
1
+ export type PropOf<T> = T extends React.ComponentType<infer U> ? U : never;
@@ -0,0 +1,13 @@
1
+ export function compactMap<T extends {}>(
2
+ obj: T,
3
+ ): {
4
+ [key in keyof T]: NonNullable<T[key]>;
5
+ } {
6
+ return Object.entries(obj).reduce<any>((acc, [key, value]) => {
7
+ if (value !== null && value !== undefined) {
8
+ acc[key] = value;
9
+ }
10
+
11
+ return acc;
12
+ }, {});
13
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./compactMap";
2
+ export * from "./last";
3
+ export * from "./listenOnce";
4
+ export * from "./listenResize";
5
+ export * from "./noop";
6
+ export * from "./PropOf";
7
+ export * from "./requestNextFrame";
@@ -0,0 +1 @@
1
+ export const last = <T extends unknown>(arr: T[]) => arr[arr.length - 1];
@@ -0,0 +1,12 @@
1
+ export function listenOnce<T extends HTMLElement>(
2
+ el: T,
3
+ type: keyof HTMLElementEventMap,
4
+ cb: () => void,
5
+ ) {
6
+ const listener = () => {
7
+ el.removeEventListener(type, listener);
8
+ cb();
9
+ };
10
+
11
+ el.addEventListener(type, listener);
12
+ }
@@ -0,0 +1,8 @@
1
+ export const listenResize = (cb: () => void) => {
2
+ cb();
3
+ window.addEventListener("resize", cb);
4
+
5
+ return () => {
6
+ window.removeEventListener("resize", cb);
7
+ };
8
+ };
@@ -0,0 +1 @@
1
+ export const noop = () => {};
@@ -0,0 +1,8 @@
1
+ // https://github.com/MingruiZhang/react-animate-mount/blob/32300c04a4fdfa97724b70aaaf0c0f403d6da247/src/Animate.js#L171-L179
2
+ export function requestNextFrame(cb: () => void) {
3
+ requestAnimationFrame(() => {
4
+ requestAnimationFrame(() => {
5
+ cb();
6
+ });
7
+ });
8
+ }