@stackflow/plugin-basic-ui 0.19.3-alpha.1 → 0.20.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.
@@ -1,10 +1,10 @@
1
- import { useActions, useActivity } from "@stackflow/react";
1
+ import { useActions } from "@stackflow/react";
2
2
  import { assignInlineVars } from "@vanilla-extract/dynamic";
3
3
  import React, { useRef } from "react";
4
4
 
5
5
  import { IconBack, IconClose } from "../assets";
6
6
  import { useGlobalOptions } from "../basicUIPlugin";
7
- import { useMaxWidth } from "../hooks";
7
+ import { useMaxWidth, useNullableActivity } from "../hooks";
8
8
  import type { GlobalVars } from "../theme.css";
9
9
  import { globalVars } from "../theme.css";
10
10
  import { compactMap, noop } from "../utils";
@@ -17,6 +17,7 @@ type AppBarProps = Partial<
17
17
  | "borderColor"
18
18
  | "borderSize"
19
19
  | "height"
20
+ | "heightTransitionDuration"
20
21
  | "iconColor"
21
22
  | "textColor"
22
23
  | "backgroundColor"
@@ -66,7 +67,7 @@ const AppBar = React.forwardRef<HTMLDivElement, AppBarProps>(
66
67
  ref,
67
68
  ) => {
68
69
  const actions = useActions();
69
- const activity = useActivity();
70
+ const activity = useNullableActivity();
70
71
 
71
72
  const globalOptions = useGlobalOptions();
72
73
  const globalCloseButton = globalOptions.appBar?.closeButton;
@@ -90,6 +91,9 @@ const AppBar = React.forwardRef<HTMLDivElement, AppBarProps>(
90
91
  };
91
92
 
92
93
  const renderBackButton = () => {
94
+ if (!activity) {
95
+ return null;
96
+ }
93
97
  if (activity.isRoot) {
94
98
  return null;
95
99
  }
@@ -136,10 +140,9 @@ const AppBar = React.forwardRef<HTMLDivElement, AppBarProps>(
136
140
  };
137
141
 
138
142
  const renderCloseButton = () => {
139
- if (
140
- (!closeButton && !globalOptions.appBar?.closeButton) ||
141
- !activity.isRoot
142
- ) {
143
+ const isRoot = !activity || activity.isRoot;
144
+
145
+ if ((!closeButton && !globalOptions.appBar?.closeButton) || !isRoot) {
143
146
  return null;
144
147
  }
145
148
  if (closeButton && "render" in closeButton && closeButton.render) {
@@ -206,37 +209,42 @@ const AppBar = React.forwardRef<HTMLDivElement, AppBarProps>(
206
209
  }),
207
210
  )}
208
211
  >
209
- <div className={css.left}>
210
- {closeButtonLocation === "left" && renderCloseButton()}
211
- {renderBackButton()}
212
- {appendLeft?.()}
213
- </div>
214
- <div ref={centerRef} className={css.center}>
215
- <div
216
- className={css.centerMain({
217
- hasLeft,
218
- })}
219
- >
220
- {typeof title === "string" ? (
221
- <div className={css.centerText}>{title}</div>
222
- ) : (
223
- title
224
- )}
225
- <button
226
- className={css.centerMainEdge}
227
- type="button"
228
- aria-hidden="true"
229
- onClick={onTopClick}
230
- />
212
+ <div className={css.safeArea} />
213
+ <div className={css.container}>
214
+ <div className={css.left}>
215
+ {closeButtonLocation === "left" && renderCloseButton()}
216
+ {renderBackButton()}
217
+ {appendLeft?.()}
218
+ </div>
219
+ <div ref={centerRef} className={css.center}>
220
+ <div
221
+ className={css.centerMain({
222
+ hasLeft,
223
+ })}
224
+ >
225
+ {typeof title === "string" ? (
226
+ <div className={css.centerText}>{title}</div>
227
+ ) : (
228
+ title
229
+ )}
230
+ <button
231
+ className={css.centerMainEdge}
232
+ type="button"
233
+ aria-hidden="true"
234
+ onClick={onTopClick}
235
+ />
236
+ </div>
237
+ </div>
238
+ <div className={css.right}>
239
+ {appendRight?.()}
240
+ {closeButtonLocation === "right" && renderCloseButton()}
231
241
  </div>
232
- </div>
233
- <div className={css.right}>
234
- {appendRight?.()}
235
- {closeButtonLocation === "right" && renderCloseButton()}
236
242
  </div>
237
243
  </div>
238
244
  );
239
245
  },
240
246
  );
241
247
 
248
+ AppBar.displayName = "AppBar";
249
+
242
250
  export default AppBar;
@@ -1,5 +1,4 @@
1
1
  import { createThemeContract, style } from "@vanilla-extract/css";
2
- import { calc } from "@vanilla-extract/css-utils";
3
2
  import { recipe } from "@vanilla-extract/recipes";
4
3
 
5
4
  import { f } from "../styles";
@@ -127,7 +126,7 @@ export const paper = recipe({
127
126
  true: [
128
127
  f.borderBox,
129
128
  {
130
- transition: `transform ${vars.transitionDuration}, opacity ${vars.transitionDuration}, height ${globalVars.appBar.showTransitionDuration}`,
129
+ transition: `transform ${vars.transitionDuration}, opacity ${vars.transitionDuration}, padding-top ${globalVars.appBar.heightTransitionDuration}`,
131
130
  paddingTop: [
132
131
  `calc(${globalVars.appBar.height} + constant(safe-area-inset-top))`,
133
132
  `calc(${globalVars.appBar.height} + env(safe-area-inset-top))`,
@@ -153,7 +152,7 @@ export const edge = recipe({
153
152
  hasAppBar: {
154
153
  true: {
155
154
  top: globalVars.appBar.height,
156
- height: calc("100%").subtract(globalVars.appBar.height).toString(),
155
+ height: `calc(100% - ${globalVars.appBar.height})`,
157
156
  },
158
157
  },
159
158
  },
@@ -1,12 +1,13 @@
1
1
  /* eslint-disable no-param-reassign */
2
2
 
3
- import { useActions, useActivity } from "@stackflow/react";
3
+ import { useActions } from "@stackflow/react";
4
4
  import { assignInlineVars } from "@vanilla-extract/dynamic";
5
5
  import React, { useRef } from "react";
6
6
 
7
7
  import { useGlobalOptions } from "../basicUIPlugin";
8
8
  import {
9
9
  useLazy,
10
+ useNullableActivity,
10
11
  useStyleEffectHide,
11
12
  useStyleEffectOffset,
12
13
  useStyleEffectSwipeBack,
@@ -22,8 +23,7 @@ type AppScreenProps = Partial<
22
23
  Pick<GlobalVars, "backgroundColor" | "dimBackgroundColor">
23
24
  > & {
24
25
  appBar?: Omit<PropOf<typeof AppBar>, "theme" | "ref" | "key">;
25
- showAppBar?: boolean;
26
- showAppBarTransitionDuration?: string;
26
+
27
27
  preventSwipeBack?: boolean;
28
28
  children: React.ReactNode;
29
29
  };
@@ -31,13 +31,11 @@ const AppScreen: React.FC<AppScreenProps> = ({
31
31
  backgroundColor,
32
32
  dimBackgroundColor,
33
33
  appBar,
34
- showAppBar = !!appBar,
35
- showAppBarTransitionDuration,
36
34
  preventSwipeBack,
37
35
  children,
38
36
  }) => {
39
37
  const globalOptions = useGlobalOptions();
40
- const activity = useActivity();
38
+ const activity = useNullableActivity();
41
39
  const { pop } = useActions();
42
40
 
43
41
  const appScreenRef = useRef<HTMLDivElement>(null);
@@ -67,16 +65,16 @@ const AppScreen: React.FC<AppScreenProps> = ({
67
65
  },
68
66
  });
69
67
 
70
- const hasAppBar = !!appBar && showAppBar;
68
+ const hasAppBar = !!appBar;
71
69
 
72
- const zIndexBase = activity.zIndex * 5;
70
+ const zIndexBase = (activity?.zIndex ?? 0) * 5;
73
71
  const zIndexDim = zIndexBase;
74
72
  const zIndexPaper =
75
73
  zIndexBase + (globalOptions.theme === "cupertino" && hasAppBar ? 1 : 3);
76
74
  const zIndexEdge = zIndexBase + 4;
77
75
  const zIndexAppBar = zIndexBase + 7;
78
76
 
79
- const { transitionState } = activity;
77
+ const transitionState = activity?.transitionState ?? "enter-done";
80
78
  const lazyTransitionState = useLazy(transitionState);
81
79
 
82
80
  const onAppBarTopClick: React.MouseEventHandler = (e) => {
@@ -103,16 +101,16 @@ const AppScreen: React.FC<AppScreenProps> = ({
103
101
  compactMap({
104
102
  [globalVars.backgroundColor]: backgroundColor,
105
103
  [globalVars.dimBackgroundColor]: dimBackgroundColor,
106
- [globalVars.appBar.height]: showAppBar ? appBar?.height : "0",
107
- [globalVars.appBar.showTransitionDuration]:
108
- showAppBarTransitionDuration,
104
+ [globalVars.appBar.height]: appBar?.height,
105
+ [globalVars.appBar.heightTransitionDuration]:
106
+ appBar?.heightTransitionDuration,
109
107
  [css.vars.zIndexes.dim]: `${zIndexDim}`,
110
108
  [css.vars.zIndexes.paper]: `${zIndexPaper}`,
111
109
  [css.vars.zIndexes.edge]: `${zIndexEdge}`,
112
110
  [css.vars.zIndexes.appBar]: `${zIndexAppBar}`,
113
111
  [css.vars.transitionDuration]:
114
- activity.transitionState === "enter-active" ||
115
- activity.transitionState === "exit-active"
112
+ transitionState === "enter-active" ||
113
+ transitionState === "exit-active"
116
114
  ? `var(--stackflow-transition-duration)`
117
115
  : "0ms",
118
116
  }),
@@ -120,7 +118,7 @@ const AppScreen: React.FC<AppScreenProps> = ({
120
118
  >
121
119
  <div className={css.dim} ref={dimRef} />
122
120
  <div
123
- key={activity.id}
121
+ key={activity?.id}
124
122
  className={css.paper({
125
123
  hasAppBar,
126
124
  })}
@@ -128,7 +126,7 @@ const AppScreen: React.FC<AppScreenProps> = ({
128
126
  >
129
127
  {children}
130
128
  </div>
131
- {!activity.isRoot &&
129
+ {!activity?.isRoot &&
132
130
  globalOptions.theme === "cupertino" &&
133
131
  !preventSwipeBack && (
134
132
  <div className={css.edge({ hasAppBar })} ref={edgeRef} />
@@ -140,4 +138,6 @@ const AppScreen: React.FC<AppScreenProps> = ({
140
138
  );
141
139
  };
142
140
 
141
+ AppScreen.displayName = "AppScreen";
142
+
143
143
  export default AppScreen;
@@ -1,10 +1,10 @@
1
1
  /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
2
2
 
3
- import { useActions, useActivity } from "@stackflow/react";
3
+ import { useActions } from "@stackflow/react";
4
4
  import { assignInlineVars } from "@vanilla-extract/dynamic";
5
5
  import React, { useRef } from "react";
6
6
 
7
- import { useLazy, useStyleEffect } from "../hooks";
7
+ import { useLazy, useNullableActivity, useStyleEffect } from "../hooks";
8
8
  import type { GlobalVars } from "../theme.css";
9
9
  import { globalVars } from "../theme.css";
10
10
  import { compactMap } from "../utils";
@@ -24,7 +24,7 @@ const BottomSheet: React.FC<BottomSheetProps> = ({
24
24
  onOutsideClick,
25
25
  children,
26
26
  }) => {
27
- const activity = useActivity();
27
+ const activity = useNullableActivity();
28
28
  const { pop } = useActions();
29
29
 
30
30
  const containerRef = useRef<HTMLDivElement>(null);
@@ -63,13 +63,14 @@ const BottomSheet: React.FC<BottomSheetProps> = ({
63
63
  e.stopPropagation();
64
64
  };
65
65
 
66
- const zIndexBase = activity.zIndex * 5 + 3;
67
- const zIndexPaper = activity.zIndex * 5 + 4;
66
+ const zIndexBase = (activity?.zIndex ?? 0) * 5 + 3;
67
+ const zIndexPaper = (activity?.zIndex ?? 0) * 5 + 4;
68
+ const transitionState = activity?.transitionState ?? "enter-done";
68
69
 
69
70
  return (
70
71
  <div
71
72
  className={css.container({
72
- transitionState: useLazy(activity.transitionState),
73
+ transitionState: useLazy(transitionState),
73
74
  })}
74
75
  ref={containerRef}
75
76
  style={assignInlineVars(
@@ -80,8 +81,8 @@ const BottomSheet: React.FC<BottomSheetProps> = ({
80
81
  [css.vars.zIndexes.dim]: `${zIndexBase}`,
81
82
  [css.vars.zIndexes.paper]: `${zIndexPaper}`,
82
83
  [css.vars.transitionDuration]:
83
- activity.transitionState === "enter-active" ||
84
- activity.transitionState === "exit-active"
84
+ transitionState === "enter-active" ||
85
+ transitionState === "exit-active"
85
86
  ? `var(--stackflow-transition-duration)`
86
87
  : "0ms",
87
88
  }),
@@ -96,4 +97,6 @@ const BottomSheet: React.FC<BottomSheetProps> = ({
96
97
  );
97
98
  };
98
99
 
100
+ BottomSheet.displayName = "BottomSheet";
101
+
99
102
  export default BottomSheet;
@@ -1,10 +1,10 @@
1
1
  /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
2
2
 
3
- import { useActions, useActivity } from "@stackflow/react";
3
+ import { useActions } from "@stackflow/react";
4
4
  import { assignInlineVars } from "@vanilla-extract/dynamic";
5
5
  import React, { useRef } from "react";
6
6
 
7
- import { useLazy, useStyleEffect } from "../hooks";
7
+ import { useLazy, useNullableActivity, useStyleEffect } from "../hooks";
8
8
  import type { GlobalVars } from "../theme.css";
9
9
  import { globalVars } from "../theme.css";
10
10
  import { compactMap } from "../utils";
@@ -24,7 +24,7 @@ const Modal: React.FC<ModalProps> = ({
24
24
  onOutsideClick,
25
25
  children,
26
26
  }) => {
27
- const activity = useActivity();
27
+ const activity = useNullableActivity();
28
28
  const { pop } = useActions();
29
29
 
30
30
  const containerRef = useRef<HTMLDivElement>(null);
@@ -63,13 +63,14 @@ const Modal: React.FC<ModalProps> = ({
63
63
  e.stopPropagation();
64
64
  };
65
65
 
66
- const zIndexBase = activity.zIndex * 5 + 3;
67
- const zIndexPaper = activity.zIndex * 5 + 4;
66
+ const zIndexBase = (activity?.zIndex ?? 0) * 5 + 3;
67
+ const zIndexPaper = (activity?.zIndex ?? 0) * 5 + 4;
68
+ const transitionState = activity?.transitionState ?? "enter-done";
68
69
 
69
70
  return (
70
71
  <div
71
72
  className={css.container({
72
- transitionState: useLazy(activity.transitionState),
73
+ transitionState: useLazy(transitionState),
73
74
  })}
74
75
  ref={containerRef}
75
76
  style={assignInlineVars(
@@ -80,8 +81,8 @@ const Modal: React.FC<ModalProps> = ({
80
81
  [css.vars.zIndexes.dim]: `${zIndexBase}`,
81
82
  [css.vars.zIndexes.paper]: `${zIndexPaper}`,
82
83
  [css.vars.transitionDuration]:
83
- activity.transitionState === "enter-active" ||
84
- activity.transitionState === "exit-active"
84
+ transitionState === "enter-active" ||
85
+ transitionState === "exit-active"
85
86
  ? `var(--stackflow-transition-duration)`
86
87
  : "0ms",
87
88
  }),
@@ -96,4 +97,6 @@ const Modal: React.FC<ModalProps> = ({
96
97
  );
97
98
  };
98
99
 
100
+ Modal.displayName = "Modal";
101
+
99
102
  export default Modal;
@@ -1,5 +1,6 @@
1
1
  export * from "./useLazy";
2
2
  export * from "./useMaxWidth";
3
+ export * from "./useNullableActivity";
3
4
  export * from "./useStyleEffect";
4
5
  export * from "./useStyleEffectHide";
5
6
  export * from "./useStyleEffectOffset";
@@ -0,0 +1,6 @@
1
+ import type { Activity } from "@stackflow/core";
2
+ import { useActivity } from "@stackflow/react";
3
+
4
+ export function useNullableActivity() {
5
+ return useActivity() as Activity | null;
6
+ }
@@ -1,8 +1,9 @@
1
1
  import type { ActivityTransitionState } from "@stackflow/core";
2
- import { useActivity } from "@stackflow/react";
3
2
  import type React from "react";
4
3
  import { useEffect } from "react";
5
4
 
5
+ import { useNullableActivity } from "./useNullableActivity";
6
+
6
7
  const connections: {
7
8
  [styleName: string]: Map<
8
9
  number,
@@ -25,9 +26,12 @@ export function useStyleEffect({
25
26
  refs: Array<React.RefObject<HTMLElement>>;
26
27
  }) => (() => void) | void;
27
28
  }) {
28
- const activity = useActivity();
29
+ const activity = useNullableActivity();
29
30
 
30
31
  useEffect(() => {
32
+ if (!activity) {
33
+ return () => {};
34
+ }
31
35
  if (!connections[styleName]) {
32
36
  connections[styleName] = new Map();
33
37
  }
@@ -40,9 +44,12 @@ export function useStyleEffect({
40
44
  return () => {
41
45
  connections[styleName].delete(activity.zIndex);
42
46
  };
43
- }, [activity.id, refs, effect]);
47
+ }, [activity?.id, refs, effect]);
44
48
 
45
49
  useEffect(() => {
50
+ if (!activity) {
51
+ return () => {};
52
+ }
46
53
  if (!effect) {
47
54
  return () => {};
48
55
  }
@@ -72,5 +79,5 @@ export function useStyleEffect({
72
79
  return () => {
73
80
  cleanup?.();
74
81
  };
75
- }, [activity.transitionState]);
82
+ }, [activity?.transitionState]);
76
83
  }
package/src/index.ts CHANGED
@@ -2,10 +2,4 @@ export { basicUIPlugin } from "./basicUIPlugin";
2
2
  export { default as AppScreen } from "./components/AppScreen";
3
3
  export { default as BottomSheet } from "./components/BottomSheet";
4
4
  export { default as Modal } from "./components/Modal";
5
- export {
6
- useStyleEffect,
7
- useStyleEffectHide,
8
- useStyleEffectOffset,
9
- useStyleEffectSwipeBack,
10
- } from "./hooks";
11
5
  export { globalVars as cssVars } from "./theme.css";
@@ -30,6 +30,10 @@ export const flex = style({
30
30
  display: "flex",
31
31
  });
32
32
 
33
+ export const flex1 = style({
34
+ flex: 1,
35
+ });
36
+
33
37
  export const flexAlignCenter = style([
34
38
  flex,
35
39
  {
package/src/theme.css.ts CHANGED
@@ -13,10 +13,11 @@ export const globalVars = createGlobalThemeContract(
13
13
  borderColor: "app-bar-border-color",
14
14
  borderSize: "app-bar-border-size",
15
15
  height: "app-bar-height",
16
+ heightTransitionDuration: "app-bar-height-transition-duration",
17
+ minHeight: "app-bar-min-height",
16
18
  iconColor: "app-bar-icon-color",
17
19
  textColor: "app-bar-text-color",
18
20
  backgroundColor: "app-bar-background-color",
19
- showTransitionDuration: "app-bar-show-transition-duration",
20
21
  },
21
22
  bottomSheet: {
22
23
  borderRadius: "bottom-sheet-border-radius",
@@ -38,10 +39,11 @@ const defaultVars = {
38
39
  borderColor: "rgba(0, 0, 0, 0.07)",
39
40
  borderSize: "1px",
40
41
  height: "3.5rem",
42
+ heightTransitionDuration: "275ms",
43
+ minHeight: "3.5rem",
41
44
  iconColor: "#212124",
42
45
  textColor: "#212124",
43
46
  backgroundColor: "#fff",
44
- showTransitionDuration: "275ms",
45
47
  },
46
48
  bottomSheet: {
47
49
  borderRadius: "1rem",
@@ -56,6 +58,7 @@ const cupertinoVars = {
56
58
  appBar: {
57
59
  ...defaultVars.appBar,
58
60
  height: "2.75rem",
61
+ minHeight: "2.75rem",
59
62
  borderSize: "0.5px",
60
63
  },
61
64
  };
@@ -0,0 +1,6 @@
1
+ export function compact<T>(arr: Array<T>): Array<NonNullable<T>> {
2
+ return arr.filter(
3
+ (item): item is NonNullable<typeof item> =>
4
+ item !== undefined && item !== null,
5
+ );
6
+ }
@@ -1,3 +1,4 @@
1
+ export * from "./compact";
1
2
  export * from "./compactMap";
2
3
  export * from "./last";
3
4
  export * from "./listenOnce";