@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,78 @@
1
+ import { createThemeContract, style } from "@vanilla-extract/css";
2
+ import { recipe } from "@vanilla-extract/recipes";
3
+
4
+ import { f } from "../styles";
5
+ import { globalVars } from "../theme.css";
6
+
7
+ export const vars = createThemeContract({
8
+ transitionDuration: null,
9
+ zIndexes: {
10
+ dim: null,
11
+ paper: null,
12
+ },
13
+ });
14
+
15
+ const transition = style({
16
+ transition: vars.transitionDuration,
17
+ });
18
+
19
+ export const enterActive = style({});
20
+ export const enterDone = style({});
21
+ export const exitActive = style({});
22
+ export const exitDone = style({
23
+ transform: "translateX(100%)",
24
+ });
25
+
26
+ export const container = recipe({
27
+ base: [f.posAbsFull, f.overflowHidden],
28
+ variants: {
29
+ transitionState: {
30
+ "enter-active": enterActive,
31
+ "enter-done": enterDone,
32
+ "exit-active": exitActive,
33
+ "exit-done": exitDone,
34
+ },
35
+ },
36
+ });
37
+
38
+ export const dim = style([
39
+ f.posAbsFull,
40
+ f.overflowHidden,
41
+ f.flexAlignEnd,
42
+ transition,
43
+ {
44
+ backgroundColor: globalVars.dimBackgroundColor,
45
+ zIndex: vars.zIndexes.dim,
46
+ opacity: 0,
47
+ selectors: {
48
+ [`${enterActive} &, ${enterDone} &`]: {
49
+ opacity: 1,
50
+ },
51
+ [`${exitActive} &, ${exitDone} &`]: {
52
+ opacity: 0,
53
+ },
54
+ },
55
+ },
56
+ ]);
57
+
58
+ export const paper = style([
59
+ f.overflowHidden,
60
+ transition,
61
+ {
62
+ backgroundColor: globalVars.backgroundColor,
63
+ width: "100%",
64
+ borderRadius: `${globalVars.bottomSheet.borderRadius} ${globalVars.bottomSheet.borderRadius} 0 0`,
65
+ transform: "translateY(100%)",
66
+ opacity: 0,
67
+ selectors: {
68
+ [`${enterActive} &, ${enterDone} &`]: {
69
+ transform: "translateY(0)",
70
+ opacity: 1,
71
+ },
72
+ [`${exitActive} &, ${exitDone} &`]: {
73
+ transform: "translateY(100%)",
74
+ opacity: 0,
75
+ },
76
+ },
77
+ },
78
+ ]);
@@ -0,0 +1,91 @@
1
+ /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
2
+
3
+ import { useActions, useActivity } from "@stackflow/react";
4
+ import { assignInlineVars } from "@vanilla-extract/dynamic";
5
+ import React, { useRef } from "react";
6
+
7
+ import { useLazy, useStyleEffect } from "../hooks";
8
+ import type { GlobalVars } from "../theme.css";
9
+ import { globalVars } from "../theme.css";
10
+ import { compactMap } from "../utils";
11
+ import * as css from "./BottomSheet.css";
12
+
13
+ type BottomSheetProps = Partial<
14
+ Pick<GlobalVars, "backgroundColor" | "dimBackgroundColor">
15
+ > &
16
+ Partial<GlobalVars["bottomSheet"]> & {
17
+ children: React.ReactNode;
18
+ };
19
+ const BottomSheet: React.FC<BottomSheetProps> = ({
20
+ borderRadius = "1rem",
21
+ backgroundColor,
22
+ dimBackgroundColor,
23
+ children,
24
+ }) => {
25
+ const activity = useActivity();
26
+ const { pop } = useActions();
27
+
28
+ const containerRef = useRef<HTMLDivElement>(null);
29
+ const paperRef = useRef<HTMLDivElement>(null);
30
+
31
+ useStyleEffect({
32
+ styleName: "hide",
33
+ refs: [containerRef],
34
+ });
35
+ useStyleEffect({
36
+ styleName: "offset",
37
+ refs: [paperRef],
38
+ });
39
+ useStyleEffect({
40
+ styleName: "swipe-back",
41
+ refs: [paperRef],
42
+ });
43
+
44
+ const popLock = useRef(false);
45
+
46
+ const onDimClick = () => {
47
+ if (popLock.current) {
48
+ return;
49
+ }
50
+
51
+ pop();
52
+ popLock.current = true;
53
+ };
54
+ const onPaperClick: React.MouseEventHandler = (e) => {
55
+ e.stopPropagation();
56
+ };
57
+
58
+ const zIndexBase = activity.zIndex * 5 + 3;
59
+ const zIndexPaper = activity.zIndex * 5 + 4;
60
+
61
+ return (
62
+ <div
63
+ className={css.container({
64
+ transitionState: useLazy(activity.transitionState),
65
+ })}
66
+ ref={containerRef}
67
+ style={assignInlineVars(
68
+ compactMap({
69
+ [globalVars.bottomSheet.borderRadius]: borderRadius,
70
+ [globalVars.backgroundColor]: backgroundColor,
71
+ [globalVars.dimBackgroundColor]: dimBackgroundColor,
72
+ [css.vars.zIndexes.dim]: `${zIndexBase}`,
73
+ [css.vars.zIndexes.paper]: `${zIndexPaper}`,
74
+ [css.vars.transitionDuration]:
75
+ activity.transitionState === "enter-active" ||
76
+ activity.transitionState === "exit-active"
77
+ ? `var(--stackflow-transition-duration)`
78
+ : "0ms",
79
+ }),
80
+ )}
81
+ >
82
+ <div className={css.dim} ref={paperRef} onClick={onDimClick}>
83
+ <div className={css.paper} onClick={onPaperClick}>
84
+ {children}
85
+ </div>
86
+ </div>
87
+ </div>
88
+ );
89
+ };
90
+
91
+ export default BottomSheet;
@@ -0,0 +1,82 @@
1
+ import { createThemeContract, style } from "@vanilla-extract/css";
2
+ import { recipe } from "@vanilla-extract/recipes";
3
+
4
+ import { f } from "../styles";
5
+ import { globalVars } from "../theme.css";
6
+
7
+ export const vars = createThemeContract({
8
+ transitionDuration: null,
9
+ zIndexes: {
10
+ dim: null,
11
+ paper: null,
12
+ },
13
+ });
14
+
15
+ const transition = style({
16
+ transition: vars.transitionDuration,
17
+ });
18
+
19
+ export const enterActive = style({});
20
+ export const enterDone = style({});
21
+ export const exitActive = style({});
22
+ export const exitDone = style({
23
+ transform: "translateX(100%)",
24
+ });
25
+
26
+ export const container = recipe({
27
+ base: [f.posAbsFull, f.overflowHidden],
28
+ variants: {
29
+ transitionState: {
30
+ "enter-active": enterActive,
31
+ "enter-done": enterDone,
32
+ "exit-active": exitActive,
33
+ "exit-done": exitDone,
34
+ },
35
+ },
36
+ });
37
+
38
+ export const dim = style([
39
+ f.posAbsFull,
40
+ f.overflowHidden,
41
+ f.flexAlignCenter,
42
+ f.flexJustifyCenter,
43
+ transition,
44
+ {
45
+ backgroundColor: globalVars.dimBackgroundColor,
46
+ zIndex: vars.zIndexes.dim,
47
+ opacity: 0,
48
+ selectors: {
49
+ [`${enterActive} &, ${enterDone} &`]: {
50
+ opacity: 1,
51
+ },
52
+ [`${exitActive} &, ${exitDone} &`]: {
53
+ opacity: 0,
54
+ },
55
+ },
56
+ },
57
+ ]);
58
+
59
+ export const paper = style([
60
+ f.overflowHidden,
61
+ transition,
62
+ {
63
+ backgroundColor: globalVars.backgroundColor,
64
+ width: "100%",
65
+ margin: "0 2.5rem",
66
+ boxShadow:
67
+ "0px 0.625rem 2.375rem rgba(0, 0, 0, 0.15), 0px .5625rem 2.875rem rgba(0, 0, 0, 0.12), 0px .3125rem .9375rem rgba(0, 0, 0, 0.1)",
68
+ borderRadius: globalVars.modal.borderRadius,
69
+ transform: "scale(1.1)",
70
+ opacity: 0,
71
+ selectors: {
72
+ [`${enterActive} &, ${enterDone} &`]: {
73
+ transform: "scale(1)",
74
+ opacity: 1,
75
+ },
76
+ [`${exitActive} &, ${exitDone} &`]: {
77
+ transform: "scale(1.1)",
78
+ opacity: 0,
79
+ },
80
+ },
81
+ },
82
+ ]);
@@ -0,0 +1,91 @@
1
+ /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
2
+
3
+ import { useActions, useActivity } from "@stackflow/react";
4
+ import { assignInlineVars } from "@vanilla-extract/dynamic";
5
+ import React, { useRef } from "react";
6
+
7
+ import { useLazy, useStyleEffect } from "../hooks";
8
+ import type { GlobalVars } from "../theme.css";
9
+ import { globalVars } from "../theme.css";
10
+ import { compactMap } from "../utils";
11
+ import * as css from "./Modal.css";
12
+
13
+ type ModalProps = Partial<
14
+ Pick<GlobalVars, "backgroundColor" | "dimBackgroundColor">
15
+ > &
16
+ Partial<GlobalVars["modal"]> & {
17
+ children: React.ReactNode;
18
+ };
19
+ const Modal: React.FC<ModalProps> = ({
20
+ backgroundColor,
21
+ dimBackgroundColor,
22
+ borderRadius = "1rem",
23
+ children,
24
+ }) => {
25
+ const activity = useActivity();
26
+ const { pop } = useActions();
27
+
28
+ const containerRef = useRef<HTMLDivElement>(null);
29
+ const paperRef = useRef<HTMLDivElement>(null);
30
+
31
+ useStyleEffect({
32
+ styleName: "hide",
33
+ refs: [containerRef],
34
+ });
35
+ useStyleEffect({
36
+ styleName: "offset",
37
+ refs: [paperRef],
38
+ });
39
+ useStyleEffect({
40
+ styleName: "swipe-back",
41
+ refs: [paperRef],
42
+ });
43
+
44
+ const popLock = useRef(false);
45
+
46
+ const onDimClick = () => {
47
+ if (popLock.current) {
48
+ return;
49
+ }
50
+
51
+ pop();
52
+ popLock.current = true;
53
+ };
54
+ const onPaperClick: React.MouseEventHandler = (e) => {
55
+ e.stopPropagation();
56
+ };
57
+
58
+ const zIndexBase = activity.zIndex * 5 + 3;
59
+ const zIndexPaper = activity.zIndex * 5 + 4;
60
+
61
+ return (
62
+ <div
63
+ className={css.container({
64
+ transitionState: useLazy(activity.transitionState),
65
+ })}
66
+ ref={containerRef}
67
+ style={assignInlineVars(
68
+ compactMap({
69
+ [globalVars.bottomSheet.borderRadius]: borderRadius,
70
+ [globalVars.backgroundColor]: backgroundColor,
71
+ [globalVars.dimBackgroundColor]: dimBackgroundColor,
72
+ [css.vars.zIndexes.dim]: `${zIndexBase}`,
73
+ [css.vars.zIndexes.paper]: `${zIndexPaper}`,
74
+ [css.vars.transitionDuration]:
75
+ activity.transitionState === "enter-active" ||
76
+ activity.transitionState === "exit-active"
77
+ ? `var(--stackflow-transition-duration)`
78
+ : "0ms",
79
+ }),
80
+ )}
81
+ >
82
+ <div className={css.dim} ref={paperRef} onClick={onDimClick}>
83
+ <div className={css.paper} onClick={onPaperClick}>
84
+ {children}
85
+ </div>
86
+ </div>
87
+ </div>
88
+ );
89
+ };
90
+
91
+ export default Modal;
@@ -0,0 +1,7 @@
1
+ export * from "./useLazy";
2
+ export * from "./useMaxWidth";
3
+ export * from "./useStyleEffect";
4
+ export * from "./useStyleEffectHide";
5
+ export * from "./useStyleEffectOffset";
6
+ export * from "./useStyleEffectSwipeBack";
7
+ export * from "./useTheme";
@@ -0,0 +1,15 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ import { requestNextFrame } from "../utils";
4
+
5
+ export function useLazy<T>(value: T) {
6
+ const [state, setState] = useState<T | undefined>(undefined);
7
+
8
+ useEffect(() => {
9
+ requestNextFrame(() => {
10
+ setState(value);
11
+ });
12
+ }, [value]);
13
+
14
+ return state;
15
+ }
@@ -0,0 +1,43 @@
1
+ import type React from "react";
2
+ import { useEffect, useState } from "react";
3
+
4
+ import { listenResize, noop } from "../utils";
5
+
6
+ export function useMaxWidth({
7
+ outerRef,
8
+ innerRef,
9
+ enable,
10
+ }: {
11
+ outerRef: React.ForwardedRef<any>;
12
+ innerRef: React.MutableRefObject<any>;
13
+ enable: boolean;
14
+ }) {
15
+ const [maxWidth, setMaxWidth] = useState<number | undefined>(undefined);
16
+
17
+ useEffect(() => {
18
+ const $outer = outerRef && "current" in outerRef && outerRef.current;
19
+ const $inner = innerRef.current;
20
+
21
+ if (!enable || !$outer || !$inner) {
22
+ return noop;
23
+ }
24
+
25
+ const dispose = listenResize(() => {
26
+ const screenWidth = $outer.clientWidth;
27
+
28
+ const leftWidth = $inner.offsetLeft;
29
+ const centerWidth = $inner.clientWidth;
30
+ const rightWidth = screenWidth - leftWidth - centerWidth;
31
+
32
+ const sideMargin = Math.max(leftWidth, rightWidth);
33
+
34
+ setMaxWidth(screenWidth - 2 * sideMargin);
35
+ });
36
+
37
+ return dispose;
38
+ }, []);
39
+
40
+ return {
41
+ maxWidth,
42
+ };
43
+ }
@@ -0,0 +1,76 @@
1
+ import type { ActivityTransitionState } from "@stackflow/core";
2
+ import { useActivity } from "@stackflow/react";
3
+ import type React from "react";
4
+ import { useEffect } from "react";
5
+
6
+ const connections: {
7
+ [styleName: string]: Map<
8
+ number,
9
+ {
10
+ refs: Array<React.RefObject<any>>;
11
+ hasEffect: boolean;
12
+ }
13
+ >;
14
+ } = {};
15
+
16
+ export function useStyleEffect({
17
+ styleName,
18
+ refs,
19
+ effect,
20
+ }: {
21
+ styleName: string;
22
+ refs: Array<React.RefObject<any>>;
23
+ effect?: (params: {
24
+ activityTransitionState: ActivityTransitionState;
25
+ refs: Array<React.RefObject<any>>;
26
+ }) => (() => void) | void;
27
+ }) {
28
+ const activity = useActivity();
29
+
30
+ useEffect(() => {
31
+ if (!connections[styleName]) {
32
+ connections[styleName] = new Map();
33
+ }
34
+
35
+ connections[styleName].set(activity.zIndex, {
36
+ refs,
37
+ hasEffect: !!effect,
38
+ });
39
+
40
+ return () => {
41
+ connections[styleName].delete(activity.zIndex);
42
+ };
43
+ }, [activity.id, refs, effect]);
44
+
45
+ useEffect(() => {
46
+ if (!effect) {
47
+ return () => {};
48
+ }
49
+
50
+ const refs = (() => {
51
+ let arr: Array<React.RefObject<any>> = [];
52
+
53
+ for (let i = 1; i <= activity.zIndex; i += 1) {
54
+ const connection = connections[styleName].get(activity.zIndex - i);
55
+
56
+ if (connection?.refs) {
57
+ arr = [...arr, ...connection.refs];
58
+ }
59
+ if (connection?.hasEffect) {
60
+ break;
61
+ }
62
+ }
63
+
64
+ return arr;
65
+ })();
66
+
67
+ const cleanup = effect({
68
+ activityTransitionState: activity.transitionState,
69
+ refs,
70
+ });
71
+
72
+ return () => {
73
+ cleanup?.();
74
+ };
75
+ }, [activity.transitionState]);
76
+ }
@@ -0,0 +1,34 @@
1
+ /* eslint-disable no-param-reassign */
2
+
3
+ import { useStyleEffect } from "./useStyleEffect";
4
+
5
+ export function useStyleEffectHide({
6
+ refs,
7
+ hasEffect,
8
+ }: {
9
+ refs: Array<React.RefObject<any>>;
10
+ hasEffect?: boolean;
11
+ }) {
12
+ useStyleEffect({
13
+ styleName: "hide",
14
+ refs,
15
+ effect: hasEffect
16
+ ? ({ activityTransitionState, refs }) => {
17
+ switch (activityTransitionState) {
18
+ case "enter-done": {
19
+ refs.forEach((ref) => {
20
+ ref.current.style.display = "none";
21
+ });
22
+ break;
23
+ }
24
+ default: {
25
+ refs.forEach((ref) => {
26
+ ref.current.style.display = "";
27
+ });
28
+ break;
29
+ }
30
+ }
31
+ }
32
+ : undefined,
33
+ });
34
+ }
@@ -0,0 +1,57 @@
1
+ /* eslint-disable no-param-reassign */
2
+
3
+ import { listenOnce, requestNextFrame } from "../utils";
4
+ import { useStyleEffect } from "./useStyleEffect";
5
+
6
+ export const OFFSET_PX_ANDROID = 32;
7
+ export const OFFSET_PX_CUPERTINO = 80;
8
+
9
+ export function useStyleEffectOffset({
10
+ refs,
11
+ theme,
12
+ hasEffect,
13
+ }: {
14
+ refs: Array<React.RefObject<any>>;
15
+ theme: "android" | "cupertino";
16
+ hasEffect?: boolean;
17
+ }) {
18
+ useStyleEffect({
19
+ styleName: "offset",
20
+ refs,
21
+ effect: hasEffect
22
+ ? ({ activityTransitionState, refs }) => {
23
+ const transform =
24
+ theme === "cupertino"
25
+ ? `translateX(-${OFFSET_PX_CUPERTINO / 16}rem)`
26
+ : `translateY(-${OFFSET_PX_ANDROID / 16}rem)`;
27
+
28
+ switch (activityTransitionState) {
29
+ case "enter-active":
30
+ case "enter-done": {
31
+ refs.forEach((ref) => {
32
+ ref.current.style.transition = `var(--stackflow-transition-duration)`;
33
+ ref.current.style.transform = transform;
34
+ });
35
+ break;
36
+ }
37
+ case "exit-active":
38
+ case "exit-done": {
39
+ requestNextFrame(() => {
40
+ refs.forEach((ref) => {
41
+ ref.current.style.transform = "";
42
+
43
+ listenOnce(ref.current, "transitionend", () => {
44
+ ref.current.style.transition = "";
45
+ });
46
+ });
47
+ });
48
+ break;
49
+ }
50
+ default: {
51
+ break;
52
+ }
53
+ }
54
+ }
55
+ : undefined,
56
+ });
57
+ }