@indietabletop/appkit 6.0.0 → 6.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.
@@ -5,7 +5,7 @@ import { SafariCheck } from "./SafariCheck.tsx";
5
5
 
6
6
  function Content() {
7
7
  return (
8
- <>
8
+ <div>
9
9
  <h1>Safari Check passed</h1>
10
10
  <button
11
11
  onClick={() => {
@@ -15,14 +15,31 @@ function Content() {
15
15
  >
16
16
  Clear local storage
17
17
  </button>
18
- </>
18
+ </div>
19
19
  );
20
20
  }
21
21
 
22
+ function userAgentResponse(params: { browserName: string }) {
23
+ return http.get("http://mock.api/ua", async () => {
24
+ await sleep(1500);
25
+ return HttpResponse.json({
26
+ browser: { name: params.browserName },
27
+ device: params.browserName.includes("Mobile") ? { type: "mobile" } : {},
28
+ });
29
+ });
30
+ }
31
+
22
32
  const meta = preview.meta({
23
33
  title: "Components/SafariCheck",
24
34
  component: SafariCheck,
25
35
  tags: ["autodocs"],
36
+ decorators: [
37
+ (Story) => (
38
+ <div style={{ height: "100svh", display: "grid" }}>
39
+ <Story />
40
+ </div>
41
+ ),
42
+ ],
26
43
  parameters: {
27
44
  msw: {
28
45
  handlers: {
@@ -32,9 +49,6 @@ const meta = preview.meta({
32
49
  },
33
50
  });
34
51
 
35
- /**
36
- * The default case in which all steps of the flow succeed.
37
- */
38
52
  export const SafariDesktop = meta.story({
39
53
  args: {
40
54
  children: <Content />,
@@ -48,9 +62,6 @@ export const SafariDesktop = meta.story({
48
62
  },
49
63
  });
50
64
 
51
- /**
52
- * The default case in which all steps of the flow succeed.
53
- */
54
65
  export const SafariMobile = meta.story({
55
66
  args: {
56
67
  children: <Content />,
@@ -64,9 +75,6 @@ export const SafariMobile = meta.story({
64
75
  },
65
76
  });
66
77
 
67
- /**
68
- * The default case in which all steps of the flow succeed.
69
- */
70
78
  export const OtherBrowser = meta.story({
71
79
  args: {
72
80
  children: <Content />,
@@ -80,12 +88,12 @@ export const OtherBrowser = meta.story({
80
88
  },
81
89
  });
82
90
 
83
- function userAgentResponse(params: { browserName: string }) {
84
- return http.get("http://mock.api/ua", async () => {
85
- await sleep(1500);
86
- return HttpResponse.json({
87
- browser: { name: params.browserName },
88
- device: params.browserName.includes("Mobile") ? { type: "mobile" } : {},
89
- });
90
- });
91
- }
91
+ /**
92
+ * The check behaviour has been disabled.
93
+ */
94
+ export const Disabled = meta.story({
95
+ args: {
96
+ children: <Content />,
97
+ performCheck: false,
98
+ },
99
+ });
@@ -2,6 +2,7 @@ import { Button, DialogDisclosure, DialogDismiss } from "@ariakit/react";
2
2
  import { useCallback, useMemo, useState, type ReactNode } from "react";
3
3
  import { number } from "superstruct";
4
4
  import useImmutableSWR from "swr/immutable";
5
+ import { Link } from "wouter";
5
6
  import { useAppConfig, useClient } from "../AppConfig/AppConfig.tsx";
6
7
  import { Failure, Pending, Success } from "../async-op.ts";
7
8
  import { interactiveText } from "../common.css.ts";
@@ -16,6 +17,7 @@ import { button } from "../Letterhead/style.css.ts";
16
17
  import { LoadingIndicator } from "../LoadingIndicator.tsx";
17
18
  import { ModalDialog } from "../ModalDialog/index.tsx";
18
19
  import { swrResponseToResult } from "../result/swr.ts";
20
+ import { useCurrentUser } from "../store/index.tsx";
19
21
  import type { FailurePayload } from "../types.ts";
20
22
  import { useIsInstalled } from "../use-is-installed.ts";
21
23
  import addToDockUrl from "./addToDock.svg";
@@ -133,9 +135,9 @@ function SafariPrompt(props: { onDismiss: () => void; isMobile?: boolean }) {
133
135
  <DialogDisclosure className={button()}>Install App</DialogDisclosure>
134
136
  </DialogTrigger>
135
137
 
136
- <a className={button()} href={hrefs.join()}>
138
+ <Link className={button()} href={hrefs.join()}>
137
139
  Create Account
138
- </a>
140
+ </Link>
139
141
  </div>
140
142
 
141
143
  <div className={css.safariDismissArea}>
@@ -145,6 +147,7 @@ function SafariPrompt(props: { onDismiss: () => void; isMobile?: boolean }) {
145
147
  </Button>
146
148
  </LetterheadParagraph>
147
149
  </div>
150
+
148
151
  <LetterheadFooter />
149
152
  </div>
150
153
  );
@@ -184,23 +187,19 @@ type SafariCheckResult =
184
187
  | Failure<FailurePayload>
185
188
  | Pending;
186
189
 
187
- function useSafariCheck() {
190
+ function useSafariCheck({ performCheck }: { performCheck: boolean }) {
191
+ const currentUser = useCurrentUser();
188
192
  const isInstalled = useIsInstalled();
189
193
  const { dismissedAt, setDismissed } = useInstallPromptState();
190
194
  const installedOrDismissed = isInstalled || !!dismissedAt;
191
- const userAgentResult = useUserAgent({ performFetch: !installedOrDismissed });
192
-
193
- console.log({
194
- isInstalled,
195
- dismissedAt,
196
- installedOrDismissed,
197
- userAgentResult,
195
+ const userAgentResult = useUserAgent({
196
+ performFetch: performCheck && !currentUser && !installedOrDismissed,
198
197
  });
199
198
 
200
199
  const result = useMemo((): SafariCheckResult => {
201
- return installedOrDismissed
202
- ? // If the safari prompt was previously dismissed, or the app is already
203
- // installed, we skipt the prompt entirely.
200
+ return installedOrDismissed || !performCheck
201
+ ? // If the safari prompt was previously dismissed, the app is already
202
+ // installed, or check was disabled explicitly, we skip the prompt.
204
203
  new Success({ showPrompt: false })
205
204
  : // Otherwise, we want to check whether the name includes Safari (could be
206
205
  // Safari or Safari Mobile), and we also report whether this is a mobile
@@ -211,22 +210,48 @@ function useSafariCheck() {
211
210
  isMobile: value.device?.type === "mobile",
212
211
  };
213
212
  });
214
- }, [installedOrDismissed, userAgentResult]);
213
+ }, [installedOrDismissed, userAgentResult, performCheck]);
215
214
 
216
215
  return { result, setDismissed };
217
216
  }
218
217
 
219
- export function SafariCheck(props: { children: ReactNode }) {
220
- const { result, setDismissed } = useSafariCheck();
218
+ /**
219
+ * Checks whether the browser which is running the app is Safari/Safari Mobile
220
+ * and warns the user that their data might be deleted due to inactivity.
221
+ *
222
+ * The warning will be shown if:
223
+ *
224
+ * - The check has not been explicitly disabled via `performCheck: false`
225
+ * - The warning has not been previously dismissed (as reported by localStorage)
226
+ * - The app is not installed (Safari behaves differently in that case)
227
+ * - The user is not already logged in (as data will be safely backed up that way)
228
+ */
229
+ export function SafariCheck(props: {
230
+ children: ReactNode;
231
+
232
+ /**
233
+ * Optionally opt out of the check.
234
+ *
235
+ * This can be useful in cases where, e.g. the user already has some content
236
+ * in the app and we want to avoid checking the user agent, but it is
237
+ * impractical to entirely avoid rendering this component.
238
+ */
239
+ performCheck?: boolean;
240
+ }) {
241
+ const { result, setDismissed } = useSafariCheck({
242
+ performCheck: props.performCheck ?? true,
243
+ });
221
244
 
222
245
  if (result.isPending) {
223
246
  return (
224
- <div className={css.compatCheckLoaderContainer}>
225
- <LoadingIndicator className={css.indicator} />
247
+ <div className={css.container}>
248
+ <div className={css.compatCheckLoaderContainer}>
249
+ <LoadingIndicator className={css.indicator} />
226
250
 
227
- {/* Intentionally not using Letterhead Paragraph to pick up the default
251
+ {/* Intentionally not using Letterhead Paragraph to pick up the default
228
252
  font size and style. */}
229
- <p className={css.compatCheckLoaderText}>Checking compatibility...</p>
253
+ <p className={css.compatCheckLoaderText}>Checking compatibility...</p>
254
+ </div>
230
255
  </div>
231
256
  );
232
257
  }
@@ -236,7 +261,11 @@ export function SafariCheck(props: { children: ReactNode }) {
236
261
  // keep rolling as if the showPrompt was false.
237
262
  const value = result.valueOrNull();
238
263
  if (value?.showPrompt) {
239
- return <SafariPrompt onDismiss={setDismissed} isMobile={value.isMobile} />;
264
+ return (
265
+ <div className={css.container}>
266
+ <SafariPrompt onDismiss={setDismissed} isMobile={value.isMobile} />
267
+ </div>
268
+ );
240
269
  }
241
270
 
242
271
  // We're all good, render children.
@@ -1,22 +1,12 @@
1
- import { createContainer, style } from "@vanilla-extract/css";
1
+ import { style } from "@vanilla-extract/css";
2
2
  import { fadeIn } from "../animations.css.ts";
3
- import { MinWidth } from "../media.ts";
4
3
 
5
- const promptContainerName = createContainer();
6
-
7
- export const safariPromptContainer = style({
4
+ export const container = style({
8
5
  display: "flex",
9
- justifyContent: "center",
10
- alignItems: "start",
11
- container: `${promptContainerName} / inline-size`,
12
- padding: "1rem",
6
+ alignItems: "center",
7
+ justifyItems: "center",
8
+ flexDirection: "column",
13
9
  blockSize: "100%",
14
-
15
- "@media": {
16
- [MinWidth.MEDIUM]: {
17
- alignItems: "center",
18
- },
19
- },
20
10
  });
21
11
 
22
12
  export const safariPrompt = style({
@@ -24,6 +14,7 @@ export const safariPrompt = style({
24
14
  borderRadius: "1rem",
25
15
  maxWidth: "38rem",
26
16
  padding: "clamp(1rem, 5vw, 3rem)",
17
+ margin: "auto",
27
18
  });
28
19
 
29
20
  export const safariLogo = style({
@@ -51,7 +42,9 @@ export const safariDismissArea = style({
51
42
 
52
43
  export const compatCheckLoaderContainer = style({
53
44
  maxInlineSize: "16rem",
45
+ margin: "auto",
54
46
  });
47
+
55
48
  export const indicator = style({
56
49
  marginInline: "auto",
57
50
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indietabletop/appkit",
3
- "version": "6.0.0",
3
+ "version": "6.1.0",
4
4
  "description": "A collection of modules used in apps built by Indie Tabletop Club",
5
5
  "private": false,
6
6
  "type": "module",