@indietabletop/appkit 7.0.0-0 → 7.0.0-rc.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.
Files changed (279) hide show
  1. package/lib/AppConfig/AppConfig.tsx +80 -0
  2. package/lib/AppConfig/formatters.tsx +43 -0
  3. package/lib/AuthCard/AuthCard.stories.ts +34 -0
  4. package/lib/AuthCard/AuthCard.tsx +64 -0
  5. package/lib/AuthCard/style.css.ts +49 -0
  6. package/lib/CacheProvider.tsx +20 -0
  7. package/lib/DialogTrigger/index.tsx +36 -0
  8. package/lib/DocumentTitle/DocumentTitle.tsx +10 -0
  9. package/lib/EnumMapper.ts +50 -0
  10. package/lib/ExternalLink.tsx +10 -0
  11. package/lib/FullscreenDismissBlocker.tsx +23 -0
  12. package/{dist/HistoryState.d.ts → lib/HistoryState.ts} +5 -2
  13. package/lib/IndieTabletopClubLogo.tsx +44 -0
  14. package/lib/IndieTabletopClubSymbol.tsx +37 -0
  15. package/lib/InfoPage/index.tsx +46 -0
  16. package/lib/InfoPage/pages.tsx +36 -0
  17. package/lib/InfoPage/style.css.ts +36 -0
  18. package/lib/Letterhead/index.tsx +85 -0
  19. package/lib/Letterhead/stories.tsx +41 -0
  20. package/lib/Letterhead/style.css.ts +152 -0
  21. package/lib/LetterheadForm/LetterheadReadonlyTextField.stories.tsx +17 -0
  22. package/lib/LetterheadForm/LetterheadSubmitError.stories.tsx +19 -0
  23. package/lib/LetterheadForm/LetterheadTextField.stories.tsx +19 -0
  24. package/lib/LetterheadForm/index.tsx +137 -0
  25. package/lib/LetterheadForm/style.css.ts +89 -0
  26. package/lib/LoadingIndicator.tsx +40 -0
  27. package/lib/MiddotSeparated/MiddotSeparated.stories.ts +26 -0
  28. package/lib/MiddotSeparated/MiddotSeparated.tsx +26 -0
  29. package/lib/MiddotSeparated/style.css.ts +10 -0
  30. package/lib/ModalDialog/index.tsx +28 -0
  31. package/lib/ModalDialog/style.css.ts +88 -0
  32. package/lib/ModernIDB/Cursor.ts +91 -0
  33. package/lib/ModernIDB/ModernIDB.ts +337 -0
  34. package/lib/ModernIDB/ModernIDBError.ts +9 -0
  35. package/lib/ModernIDB/ObjectStore.ts +195 -0
  36. package/lib/ModernIDB/ObjectStoreIndex.ts +102 -0
  37. package/lib/ModernIDB/README.md +9 -0
  38. package/lib/ModernIDB/Transaction.ts +40 -0
  39. package/lib/ModernIDB/VersionChangeManager.ts +57 -0
  40. package/lib/ModernIDB/bindings/factory.tsx +165 -0
  41. package/lib/ModernIDB/bindings/index.ts +2 -0
  42. package/{dist/ModernIDB/bindings/types.d.ts → lib/ModernIDB/bindings/types.ts} +32 -13
  43. package/lib/ModernIDB/bindings/utils.tsx +32 -0
  44. package/lib/ModernIDB/index.ts +10 -0
  45. package/lib/ModernIDB/types.ts +120 -0
  46. package/lib/ModernIDB/utils.ts +51 -0
  47. package/lib/QRCode/QRCode.stories.tsx +41 -0
  48. package/lib/QRCode/QRCode.tsx +54 -0
  49. package/lib/QRCode/style.css.ts +23 -0
  50. package/lib/ReleaseInfo/index.tsx +29 -0
  51. package/lib/RulesetResolver.ts +214 -0
  52. package/lib/SafariCheck/SafariCheck.stories.tsx +99 -0
  53. package/lib/SafariCheck/SafariCheck.tsx +273 -0
  54. package/lib/SafariCheck/addToDock.svg +13 -0
  55. package/lib/SafariCheck/addToHomeScreen.svg +12 -0
  56. package/lib/SafariCheck/safari.svg +32 -0
  57. package/lib/SafariCheck/shareIcon.svg +11 -0
  58. package/lib/SafariCheck/style.css.ts +106 -0
  59. package/lib/ServiceWorkerHandler.tsx +53 -0
  60. package/lib/ShareButton/ShareButton.stories.tsx +58 -0
  61. package/lib/ShareButton/ShareButton.tsx +153 -0
  62. package/lib/ShareButton/test.css.ts +3 -0
  63. package/lib/SubscribeCard/LetterheadInfoCard.tsx +23 -0
  64. package/lib/SubscribeCard/SubscribeByEmailCard.stories.tsx +69 -0
  65. package/lib/SubscribeCard/SubscribeByEmailCard.tsx +183 -0
  66. package/lib/SubscribeCard/SubscribeByPledgeCard.stories.tsx +133 -0
  67. package/lib/SubscribeCard/SubscribeByPledgeCard.tsx +127 -0
  68. package/lib/SubscribeCard/style.css.ts +14 -0
  69. package/lib/Sync/SyncIcon.stories.tsx +67 -0
  70. package/lib/Sync/SyncIcon.tsx +102 -0
  71. package/lib/Sync/SyncLog.tsx +222 -0
  72. package/lib/Sync/SyncLogList.stories.tsx +219 -0
  73. package/lib/Sync/style.css.ts +126 -0
  74. package/lib/account/AccountIcon.tsx +15 -0
  75. package/lib/account/AccountIssueView.tsx +44 -0
  76. package/lib/account/AlreadyLoggedInView.tsx +47 -0
  77. package/lib/account/CurrentUserFetcher.stories.tsx +292 -0
  78. package/lib/account/CurrentUserFetcher.tsx +118 -0
  79. package/lib/account/FailureFallbackView.tsx +36 -0
  80. package/lib/account/JoinCard.stories.tsx +257 -0
  81. package/lib/account/JoinCard.tsx +301 -0
  82. package/lib/account/LoadingView.tsx +14 -0
  83. package/lib/account/LoginCard.stories.tsx +288 -0
  84. package/lib/account/LoginCard.tsx +100 -0
  85. package/lib/account/LoginView.tsx +151 -0
  86. package/lib/account/NoConnectionView.tsx +34 -0
  87. package/lib/account/PasswordResetCard.stories.tsx +242 -0
  88. package/lib/account/PasswordResetCard.tsx +296 -0
  89. package/lib/account/UserMismatchView.tsx +62 -0
  90. package/lib/account/VerifyPage.tsx +195 -0
  91. package/lib/account/iconMask.svg +3 -0
  92. package/lib/account/style.css.ts +81 -0
  93. package/{dist/account/types.d.ts → lib/account/types.ts} +6 -3
  94. package/lib/account/useFetchCurrentUser.tsx +63 -0
  95. package/lib/account/useRedirectPath.ts +21 -0
  96. package/lib/animations.css.ts +17 -0
  97. package/lib/append-copy-to-text.ts +35 -0
  98. package/lib/async-op.ts +286 -0
  99. package/lib/atomic.css.ts +11 -0
  100. package/{dist/caught-value.d.ts → lib/caught-value.ts} +11 -1
  101. package/{dist/class-names.d.ts → lib/class-names.ts} +17 -6
  102. package/lib/client.ts +662 -0
  103. package/lib/common.css.ts +48 -0
  104. package/lib/copyrightRange.ts +10 -0
  105. package/lib/createSafeStorage.ts +91 -0
  106. package/lib/createStrictContext.ts +15 -0
  107. package/lib/failureMessages.ts +108 -0
  108. package/lib/form/FormSubmitButton.tsx +58 -0
  109. package/lib/form/SubmitErrorAlert.tsx +21 -0
  110. package/lib/form/style.css.ts +9 -0
  111. package/lib/globals.css.ts +62 -0
  112. package/lib/groupBy.ts +25 -0
  113. package/lib/hrefs.ts +48 -0
  114. package/lib/idToDate.ts +8 -0
  115. package/lib/ids.ts +6 -0
  116. package/lib/index.ts +75 -0
  117. package/lib/internal.css.ts +10 -0
  118. package/lib/mailto.ts +40 -0
  119. package/lib/media.ts +50 -0
  120. package/lib/random.ts +19 -0
  121. package/lib/result/swr.ts +18 -0
  122. package/{dist/sleep.d.ts → lib/sleep.ts} +3 -1
  123. package/lib/store/index.tsx +294 -0
  124. package/lib/store/store.ts +482 -0
  125. package/lib/store/types.ts +45 -0
  126. package/lib/store/utils.ts +54 -0
  127. package/lib/storybook/decorators.tsx +10 -0
  128. package/lib/structs.ts +3 -0
  129. package/{dist/typeguards.d.ts → lib/typeguards.ts} +3 -1
  130. package/{dist/types.d.ts → lib/types.ts} +23 -12
  131. package/lib/unique.ts +24 -0
  132. package/lib/use-async-op.ts +16 -0
  133. package/lib/use-document-background-color.ts +16 -0
  134. package/lib/use-form.ts +78 -0
  135. package/lib/use-is-installed.ts +17 -0
  136. package/lib/use-media-query.ts +21 -0
  137. package/lib/use-reverting-state.ts +32 -0
  138. package/lib/use-scroll-restoration.ts +99 -0
  139. package/lib/useEnsureValue.ts +31 -0
  140. package/lib/useInvokeClient.ts +54 -0
  141. package/lib/useIsVisible.ts +27 -0
  142. package/lib/utm.ts +92 -0
  143. package/lib/validations.ts +25 -0
  144. package/lib/vars.css.ts +13 -0
  145. package/package.json +23 -29
  146. package/dist/AppConfig/AppConfig.d.ts +0 -29
  147. package/dist/AuthCard/AuthCard.d.ts +0 -10
  148. package/dist/AuthCard/AuthCard.stories.d.ts +0 -34
  149. package/dist/AuthCard/style.css.d.ts +0 -23
  150. package/dist/DialogTrigger/index.d.ts +0 -13
  151. package/dist/DocumentTitle/DocumentTitle.d.ts +0 -3
  152. package/dist/EnumMapper.d.ts +0 -25
  153. package/dist/ExternalLink.d.ts +0 -3
  154. package/dist/FullscreenDismissBlocker.d.ts +0 -5
  155. package/dist/IndieTabletopClubLogo.d.ts +0 -7
  156. package/dist/IndieTabletopClubSymbol.d.ts +0 -7
  157. package/dist/InfoPage/index.d.ts +0 -8
  158. package/dist/InfoPage/pages.d.ts +0 -2
  159. package/dist/InfoPage/style.css.d.ts +0 -5
  160. package/dist/Letterhead/index.d.ts +0 -19
  161. package/dist/Letterhead/stories.d.ts +0 -13
  162. package/dist/Letterhead/style.css.d.ts +0 -46
  163. package/dist/LetterheadForm/LetterheadReadonlyTextField.stories.d.ts +0 -17
  164. package/dist/LetterheadForm/LetterheadSubmitError.stories.d.ts +0 -11
  165. package/dist/LetterheadForm/LetterheadTextField.stories.d.ts +0 -336
  166. package/dist/LetterheadForm/index.d.ts +0 -44
  167. package/dist/LetterheadForm/style.css.d.ts +0 -8
  168. package/dist/LoadingIndicator.d.ts +0 -3
  169. package/dist/MiddotSeparated/MiddotSeparated.d.ts +0 -8
  170. package/dist/MiddotSeparated/MiddotSeparated.stories.d.ts +0 -586
  171. package/dist/MiddotSeparated/style.css.d.ts +0 -1
  172. package/dist/ModalDialog/index.d.ts +0 -12
  173. package/dist/ModalDialog/style.css.d.ts +0 -58
  174. package/dist/ModernIDB/Cursor.d.ts +0 -56
  175. package/dist/ModernIDB/ModernIDB.d.ts +0 -66
  176. package/dist/ModernIDB/ModernIDBError.d.ts +0 -3
  177. package/dist/ModernIDB/ObjectStore.d.ts +0 -112
  178. package/dist/ModernIDB/ObjectStoreIndex.d.ts +0 -53
  179. package/dist/ModernIDB/Transaction.d.ts +0 -16
  180. package/dist/ModernIDB/VersionChangeManager.d.ts +0 -30
  181. package/dist/ModernIDB/bindings/factory.d.ts +0 -12
  182. package/dist/ModernIDB/bindings/index.d.ts +0 -2
  183. package/dist/ModernIDB/bindings/utils.d.ts +0 -2
  184. package/dist/ModernIDB/index.d.ts +0 -10
  185. package/dist/ModernIDB/types.d.ts +0 -88
  186. package/dist/ModernIDB/utils.d.ts +0 -4
  187. package/dist/QRCode/QRCode.d.ts +0 -7
  188. package/dist/QRCode/QRCode.stories.d.ts +0 -33
  189. package/dist/QRCode/style.css.d.ts +0 -4
  190. package/dist/ReleaseInfo/index.d.ts +0 -5
  191. package/dist/RulesetResolver.d.ts +0 -87
  192. package/dist/SafariCheck/SafariCheck.d.ts +0 -23
  193. package/dist/SafariCheck/SafariCheck.stories.d.ts +0 -73
  194. package/dist/SafariCheck/style.css.d.ts +0 -17
  195. package/dist/ServiceWorkerHandler.d.ts +0 -11
  196. package/dist/ShareButton/ShareButton.d.ts +0 -57
  197. package/dist/ShareButton/ShareButton.stories.d.ts +0 -1577
  198. package/dist/ShareButton/test.css.d.ts +0 -1
  199. package/dist/SubscribeCard/LetterheadInfoCard.d.ts +0 -2
  200. package/dist/SubscribeCard/SubscribeByEmailCard.d.ts +0 -24
  201. package/dist/SubscribeCard/SubscribeByEmailCard.stories.d.ts +0 -10
  202. package/dist/SubscribeCard/SubscribeByPledgeCard.d.ts +0 -36
  203. package/dist/SubscribeCard/SubscribeByPledgeCard.stories.d.ts +0 -65
  204. package/dist/SubscribeCard/style.css.d.ts +0 -4
  205. package/dist/account/AccountIssueView.d.ts +0 -3
  206. package/dist/account/AlreadyLoggedInView.d.ts +0 -5
  207. package/dist/account/CurrentUserFetcher.d.ts +0 -20
  208. package/dist/account/CurrentUserFetcher.stories.d.ts +0 -136
  209. package/dist/account/FailureFallbackView.d.ts +0 -1
  210. package/dist/account/JoinCard.d.ts +0 -14
  211. package/dist/account/JoinCard.stories.d.ts +0 -143
  212. package/dist/account/LoadingView.d.ts +0 -1
  213. package/dist/account/LoginCard.d.ts +0 -39
  214. package/dist/account/LoginCard.stories.d.ts +0 -217
  215. package/dist/account/LoginView.d.ts +0 -10
  216. package/dist/account/NoConnectionView.d.ts +0 -4
  217. package/dist/account/PasswordResetCard.d.ts +0 -15
  218. package/dist/account/PasswordResetCard.stories.d.ts +0 -128
  219. package/dist/account/UserMismatchView.d.ts +0 -6
  220. package/dist/account/VerifyPage.d.ts +0 -13
  221. package/dist/account/style.css.d.ts +0 -10
  222. package/dist/account/useFetchCurrentUser.d.ts +0 -28
  223. package/dist/account/useRedirectPath.d.ts +0 -6
  224. package/dist/animations.css.d.ts +0 -3
  225. package/dist/append-copy-to-text.d.ts +0 -10
  226. package/dist/append-copy-to-text.test.d.ts +0 -1
  227. package/dist/appkit.css +0 -1
  228. package/dist/appkit.js +0 -10692
  229. package/dist/async-op.d.ts +0 -101
  230. package/dist/atomic.css.d.ts +0 -6
  231. package/dist/client.d.ts +0 -424
  232. package/dist/common.css.d.ts +0 -5
  233. package/dist/copyrightRange.d.ts +0 -1
  234. package/dist/copyrightRange.test.d.ts +0 -1
  235. package/dist/createSafeStorage.d.ts +0 -34
  236. package/dist/failureMessages.d.ts +0 -20
  237. package/dist/failureMessages.test.d.ts +0 -1
  238. package/dist/form/FormSubmitButton.d.ts +0 -17
  239. package/dist/form/SubmitErrorAlert.d.ts +0 -5
  240. package/dist/form/style.css.d.ts +0 -3
  241. package/dist/globals.css.d.ts +0 -0
  242. package/dist/groupBy.d.ts +0 -1
  243. package/dist/groupBy.test.d.ts +0 -1
  244. package/dist/hrefs.d.ts +0 -32
  245. package/dist/hrefs.test.d.ts +0 -1
  246. package/dist/idToDate.d.ts +0 -5
  247. package/dist/idToDate.test.d.ts +0 -1
  248. package/dist/ids.d.ts +0 -1
  249. package/dist/ids.test.d.ts +0 -1
  250. package/dist/index.d.ts +0 -64
  251. package/dist/internal.css.d.ts +0 -2
  252. package/dist/mailto.d.ts +0 -8
  253. package/dist/mailto.test.d.ts +0 -1
  254. package/dist/media.d.ts +0 -39
  255. package/dist/random.d.ts +0 -3
  256. package/dist/result/swr.d.ts +0 -4
  257. package/dist/store/index.d.ts +0 -237
  258. package/dist/store/store.d.ts +0 -144
  259. package/dist/store/types.d.ts +0 -49
  260. package/dist/store/utils.d.ts +0 -10
  261. package/dist/storybook/decorators.d.ts +0 -3
  262. package/dist/structs.d.ts +0 -1
  263. package/dist/typeguards.test.d.ts +0 -1
  264. package/dist/unique.d.ts +0 -10
  265. package/dist/unique.test.d.ts +0 -1
  266. package/dist/use-async-op.d.ts +0 -6
  267. package/dist/use-document-background-color.d.ts +0 -4
  268. package/dist/use-form.d.ts +0 -29
  269. package/dist/use-is-installed.d.ts +0 -8
  270. package/dist/use-media-query.d.ts +0 -1
  271. package/dist/use-reverting-state.d.ts +0 -5
  272. package/dist/use-scroll-restoration.d.ts +0 -25
  273. package/dist/useEnsureValue.d.ts +0 -6
  274. package/dist/useInvokeClient.d.ts +0 -25
  275. package/dist/useIsVisible.d.ts +0 -4
  276. package/dist/utm.d.ts +0 -58
  277. package/dist/utm.test.d.ts +0 -1
  278. package/dist/validations.d.ts +0 -3
  279. package/dist/vars.css.d.ts +0 -10
@@ -0,0 +1,91 @@
1
+ import { type Struct, validate } from "superstruct";
2
+ import { fromTryCatch } from "./async-op.ts";
3
+
4
+ type AnyStruct = Struct<any, any>;
5
+
6
+ type StructsConfig = Record<string, AnyStruct>;
7
+
8
+ type SafeStorage<T extends Record<string, AnyStruct>> = ReturnType<
9
+ typeof createSafeStorage<T>
10
+ >;
11
+
12
+ export type SafeStorageKey<T extends SafeStorage<StructsConfig>> = ReturnType<
13
+ T["keys"]
14
+ >[number];
15
+
16
+ type StructValue<T> = T extends Struct<infer V> ? V : never;
17
+
18
+ /**
19
+ * Creates an object with an interface similar to localStorage, but that
20
+ * enforces that values are parsed and validated before being retrieved,
21
+ * stringified when being set, and that both keys and values typecheck.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const safeStorage = createSafeStorage({
26
+ * currentUser: currentUser(),
27
+ * sessionInfo: sessionInfo(),
28
+ * lastSuccessfulSyncTs: number(),
29
+ * });
30
+ *
31
+ * safeStorage.getItem("currentUser") // Will be valid current user or `null`
32
+ * safeStorage.setItem("currentUser", { ... }) // Typechecked key and value
33
+ * safeStorage.removeItem("currentUser") // Typechecked key
34
+ * safeStorage.clear() // Removes all "owned" keys
35
+ *
36
+ * ```
37
+ */
38
+ export function createSafeStorage<T extends StructsConfig>(structs: T) {
39
+ return {
40
+ getItem<K extends keyof T & string>(key: K) {
41
+ const struct = structs[key];
42
+ if (!struct) {
43
+ throw new Error(`No struct found for ${key}`);
44
+ }
45
+
46
+ const storedValue = localStorage.getItem(key);
47
+
48
+ if (storedValue === null) {
49
+ return null;
50
+ }
51
+
52
+ const parsedResult = fromTryCatch<unknown>(() => JSON.parse(storedValue));
53
+
54
+ if (parsedResult.isFailure) {
55
+ console.warn(`Could not parse localStorage value at key '${key}'.`);
56
+ return null;
57
+ }
58
+
59
+ const [error, value] = validate(parsedResult.value, struct, {
60
+ coerce: true,
61
+ mask: true,
62
+ });
63
+
64
+ if (error) {
65
+ console.warn(
66
+ `Validation failed for localStorage value at key '${key}'. ${error.message}`,
67
+ );
68
+ }
69
+
70
+ return value as StructValue<T[K]> | null;
71
+ },
72
+
73
+ setItem<K extends keyof T & string>(key: K, value: StructValue<T[K]>) {
74
+ localStorage.setItem(key, JSON.stringify(value));
75
+ },
76
+
77
+ removeItem(key: keyof T & string) {
78
+ localStorage.removeItem(key);
79
+ },
80
+
81
+ clear() {
82
+ for (const key in structs) {
83
+ localStorage.removeItem(key);
84
+ }
85
+ },
86
+
87
+ keys() {
88
+ return Object.keys(structs) as (keyof T)[];
89
+ },
90
+ };
91
+ }
@@ -0,0 +1,15 @@
1
+ import { createContext, use } from "react";
2
+
3
+ export function createStrictContext<T>() {
4
+ const Context = createContext<T | null>(null);
5
+
6
+ const useStrictContext = () => {
7
+ const value = use(Context);
8
+ if (!value) {
9
+ throw new Error(`Value not found in context.`);
10
+ }
11
+ return value;
12
+ };
13
+
14
+ return [Context, useStrictContext] as const;
15
+ }
@@ -0,0 +1,108 @@
1
+ import type { FailurePayload } from "./types.ts";
2
+
3
+ type OnOverride<T> = (fallback: T, override?: Partial<T>) => T;
4
+
5
+ function createFailureMessageGetter<T>(
6
+ defaults: Record<number | "fallback" | "connection", T>,
7
+ options: { onOverride: OnOverride<T> },
8
+ ) {
9
+ return function getMessage(
10
+ failure: FailurePayload,
11
+ overrides: Record<number, Partial<T>> = {},
12
+ ) {
13
+ switch (failure.type) {
14
+ case "API_ERROR": {
15
+ return options.onOverride(
16
+ defaults[failure.code] ?? defaults.fallback,
17
+ overrides[failure.code],
18
+ );
19
+ }
20
+
21
+ case "NETWORK_ERROR": {
22
+ return defaults.connection;
23
+ }
24
+
25
+ default: {
26
+ return defaults.fallback;
27
+ }
28
+ }
29
+ };
30
+ }
31
+
32
+ export type FetchFailureAction =
33
+ | {
34
+ type: "LINK";
35
+ href: string;
36
+ label: string;
37
+ }
38
+ | {
39
+ type: "RELOAD" | "REFETCH";
40
+ label: string;
41
+ };
42
+
43
+ export type FetchFailureMessages = {
44
+ title: string;
45
+ description: string;
46
+ action: FetchFailureAction;
47
+ };
48
+
49
+ export const getFetchFailureMessages =
50
+ createFailureMessageGetter<FetchFailureMessages>(
51
+ {
52
+ 401: {
53
+ title: "Not logged in",
54
+ description: "You must be logged in to view this page.",
55
+ action: { type: "LINK", href: "~/login", label: "Go to login" },
56
+ },
57
+ 403: {
58
+ title: "Not authorized",
59
+ description: "You might be logged into the wrong account.",
60
+ action: { type: "LINK", href: "~/login", label: "Go to login" },
61
+ },
62
+ 404: {
63
+ title: `Not found`,
64
+ description: `The link you have followed might be broken.`,
65
+ action: { type: "LINK", href: "~/", label: "Go back" },
66
+ },
67
+ 500: {
68
+ title: `Ooops, something went wrong`,
69
+ description: `This is probably an issue with our servers. You can try refreshing.`,
70
+ action: { type: "RELOAD", label: "Reload app" },
71
+ },
72
+ connection: {
73
+ title: `No connection`,
74
+ description: `Check your interent connection and try again.`,
75
+ action: { type: "REFETCH", label: "Retry request" },
76
+ },
77
+ fallback: {
78
+ title: `Ooops, something went wrong`,
79
+ description: `This is probably an issue on our side. You can try refreshing.`,
80
+ action: { type: "RELOAD", label: "Reload app" },
81
+ },
82
+ },
83
+ {
84
+ onOverride(fallback, override) {
85
+ return { ...fallback, ...override };
86
+ },
87
+ },
88
+ );
89
+
90
+ export const getSubmitFailureMessage = createFailureMessageGetter(
91
+ {
92
+ 500: `Could not submit form due to an unexpected server error. Please refresh the page and try again.`,
93
+ connection: `Could not submit form due to network error. Make sure you are connected to the internet and try again.`,
94
+ fallback: `Could not submit form due to an unexpected error. Please refresh the page and try again.`,
95
+ },
96
+ {
97
+ onOverride(fallback, override) {
98
+ return override ?? fallback;
99
+ },
100
+ },
101
+ );
102
+
103
+ /**
104
+ * @deprecated Use {@link getSubmitFailureMessage} instead.
105
+ */
106
+ export function toKnownFailureMessage(failure: FailurePayload) {
107
+ return getSubmitFailureMessage(failure);
108
+ }
@@ -0,0 +1,58 @@
1
+ import {
2
+ type FormSubmitProps,
3
+ FormSubmit,
4
+ useFormContext,
5
+ useStoreState,
6
+ } from "@ariakit/react";
7
+ import type { ReactNode } from "react";
8
+ import { fadeIn } from "../animations.css.ts";
9
+
10
+ export type FormSubmitButtonProps = FormSubmitProps & {
11
+ children: ReactNode;
12
+ loading: ReactNode;
13
+ };
14
+
15
+ /**
16
+ * Renders Ariakit FormSubmit component.
17
+ *
18
+ * It's main responsibility is to render the loading component (provided via
19
+ * the `loading` prop) when the form is in the submitting state. This component
20
+ * will be rendered over the usual content of the button, which will be hidden
21
+ * as long as the form is submitting.
22
+ *
23
+ * @remarks Must be rendered within Ariakit Form Context.
24
+ */
25
+ export function FormSubmitButton(props: FormSubmitButtonProps) {
26
+ const { children, className, style, loading, ...submitProps } = props;
27
+ const form = useFormContext();
28
+ const isSubmitting = useStoreState(form, (s) => s?.submitting);
29
+
30
+ return (
31
+ <FormSubmit
32
+ {...submitProps}
33
+ className={className}
34
+ style={{ position: "relative", ...style }}
35
+ >
36
+ <span
37
+ style={{ opacity: isSubmitting ? 0 : 1, transition: "200ms opacity" }}
38
+ >
39
+ {children}
40
+ </span>
41
+
42
+ {isSubmitting && (
43
+ <div
44
+ style={{
45
+ display: "flex",
46
+ position: "absolute",
47
+ inset: "0",
48
+ alignItems: "center",
49
+ justifyContent: "center",
50
+ animation: `${fadeIn} 200ms 200ms both`,
51
+ }}
52
+ >
53
+ {loading}
54
+ </div>
55
+ )}
56
+ </FormSubmit>
57
+ );
58
+ }
@@ -0,0 +1,21 @@
1
+ import { useFormContext, useStoreState } from "@ariakit/react";
2
+ import { cx } from "../class-names.ts";
3
+ import { submitErrorAlert } from "./style.css.ts";
4
+
5
+ export type SubmitErrorMessageProps = {
6
+ name: string;
7
+ className?: string;
8
+ };
9
+
10
+ export function SubmitErrorAlert(props: SubmitErrorMessageProps) {
11
+ const form = useFormContext();
12
+ const message = useStoreState(form, (s) => {
13
+ return s?.errors[props.name] as string | undefined;
14
+ });
15
+
16
+ return (
17
+ <div role="alert" {...cx(props, submitErrorAlert.container)}>
18
+ {message}
19
+ </div>
20
+ );
21
+ }
@@ -0,0 +1,9 @@
1
+ import { style } from "@vanilla-extract/css";
2
+
3
+ export const submitErrorAlert = {
4
+ container: style({
5
+ ":empty": {
6
+ display: "none",
7
+ },
8
+ }),
9
+ };
@@ -0,0 +1,62 @@
1
+ import { globalStyle } from "@vanilla-extract/css";
2
+
3
+ // Apply global vars
4
+ import "./vars.css.ts";
5
+
6
+ globalStyle(":root", {
7
+ interpolateSize: "allow-keywords",
8
+ fontSynthesis: "none",
9
+ textRendering: "optimizeLegibility",
10
+ WebkitFontSmoothing: "antialiased",
11
+ MozOsxFontSmoothing: "grayscale",
12
+ WebkitTapHighlightColor: "transparent",
13
+ });
14
+
15
+ globalStyle("*", {
16
+ boxSizing: "border-box",
17
+ });
18
+
19
+ globalStyle("img, picture, svg", {
20
+ display: "block",
21
+ });
22
+
23
+ globalStyle("a", {
24
+ display: "block",
25
+ color: "inherit",
26
+ textDecoration: "none",
27
+ });
28
+
29
+ globalStyle("input, textarea", {
30
+ fontFamily: "inherit",
31
+ });
32
+
33
+ globalStyle("button", {
34
+ display: "block",
35
+ fontSize: "inherit",
36
+ fontFamily: "inherit",
37
+ backgroundColor: "transparent",
38
+ border: "none",
39
+ color: "inherit",
40
+ cursor: "pointer",
41
+ padding: 0,
42
+ });
43
+
44
+ globalStyle("body, h1, h2, h3, h4, h5, h6, p, ul, li, ol", {
45
+ margin: 0,
46
+ padding: 0,
47
+ });
48
+
49
+ globalStyle("ul, ol", {
50
+ listStyle: "none",
51
+ });
52
+
53
+ // Fathom SPA support depends on this image being added to the DOM, but they
54
+ // are sloppy about taking out of the document flow, meaning that on pages
55
+ // that are 100vh, there is a scrollbar flicker as the img element is added
56
+ // to the DOM and then removed. This fixes said issue.
57
+ globalStyle(`img[src^="https://cdn.usefathom.com/"]`, {
58
+ position: "absolute",
59
+ top: 0,
60
+ left: 0,
61
+ opacity: 0.01,
62
+ });
package/lib/groupBy.ts ADDED
@@ -0,0 +1,25 @@
1
+ export function groupBy<T, K extends string>(
2
+ items: T[],
3
+ getKey: (item: T) => K,
4
+ ) {
5
+ const groups: Partial<Record<K, T[]>> = {};
6
+
7
+ for (const item of items) {
8
+ const key = getKey(item);
9
+ const group = groups[key];
10
+ if (group) {
11
+ group.push(item);
12
+ } else {
13
+ groups[key] = [item];
14
+ }
15
+ }
16
+
17
+ // Using a Proxy to make sure that even if one of the possible group keys
18
+ // was not included in the provided list, that group will still return
19
+ // an empty list rather than `undefined`.
20
+ return new Proxy(groups, {
21
+ get(target, prop, receiver) {
22
+ return Reflect.get(target, prop, receiver) ?? [];
23
+ },
24
+ }) as Record<K, T[]>;
25
+ }
package/lib/hrefs.ts ADDED
@@ -0,0 +1,48 @@
1
+ import type { LinkUtmParams, createUtm } from "./utm.ts";
2
+
3
+ type InputAppHrefs = {
4
+ login: () => string;
5
+ password: () => string;
6
+ join: () => string;
7
+ dashboard: () => string;
8
+ account: () => string;
9
+
10
+ // These are usually external links to the root domain, so we want to be
11
+ // able to set some tracking params.
12
+ terms?: (linkUtm?: LinkUtmParams) => string;
13
+ privacy?: (linkUtm?: LinkUtmParams) => string;
14
+ cookies?: (linkUtm?: LinkUtmParams) => string;
15
+ };
16
+
17
+ export type AppHrefs = Required<InputAppHrefs>;
18
+
19
+ export function createHrefs<T extends InputAppHrefs>(params: {
20
+ /**
21
+ * Hrefs to be used for the given app. At minimum, you need to provide
22
+ * the core hrefs required by Appkit.
23
+ */
24
+ hrefs: T;
25
+
26
+ /**
27
+ * The function responsible for generating UTM tags. You should
28
+ * use the return value of {@link createUtm} unless you are doing something
29
+ * unusual.
30
+ */
31
+ utm: ReturnType<typeof createUtm>;
32
+ }) {
33
+ const { utm, hrefs } = params;
34
+
35
+ return {
36
+ terms: (linkUtm?: LinkUtmParams) =>
37
+ `https://indietabletop.club/terms?${utm(linkUtm)}`,
38
+ privacy: (linkUtm?: LinkUtmParams) =>
39
+ `https://indietabletop.club/privacy?${utm(linkUtm)}`,
40
+ cookies: (linkUtm?: LinkUtmParams) =>
41
+ `https://indietabletop.club/cookies?${utm(linkUtm)}`,
42
+ itc: (linkUtm?: LinkUtmParams) =>
43
+ `https://indietabletop.club?${utm(linkUtm)}`,
44
+ fathom: () => `https://usefathom.com`,
45
+
46
+ ...hrefs,
47
+ };
48
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Given an doc id like `2025-01-01-some-name`, will return a date matching
3
+ * the starting portion of the id.
4
+ */
5
+ export function idToDate(id: string, fallback: () => Date) {
6
+ const dateString = /^(?<date>\d{4}-\d{2}-\d{2})/.exec(id)?.groups?.date;
7
+ return dateString ? new Date(dateString) : fallback();
8
+ }
package/lib/ids.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { customAlphabet } from "nanoid";
2
+
3
+ export const getId = customAlphabet(
4
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
5
+ 12,
6
+ );
package/lib/index.ts ADDED
@@ -0,0 +1,75 @@
1
+ // Components
2
+ export * from "./account/AccountIcon.tsx";
3
+ export * from "./account/CurrentUserFetcher.tsx";
4
+ export * from "./account/JoinCard.tsx";
5
+ export * from "./account/LoginCard.tsx";
6
+ export * from "./account/PasswordResetCard.tsx";
7
+ export * from "./AppConfig/AppConfig.tsx";
8
+ export * from "./AuthCard/AuthCard.tsx";
9
+ export * from "./CacheProvider.tsx";
10
+ export * from "./DialogTrigger/index.tsx";
11
+ export * from "./DocumentTitle/DocumentTitle.tsx";
12
+ export * from "./ExternalLink.tsx";
13
+ export * from "./form/FormSubmitButton.tsx";
14
+ export * from "./form/SubmitErrorAlert.tsx";
15
+ export * from "./FullscreenDismissBlocker.tsx";
16
+ export * from "./IndieTabletopClubLogo.tsx";
17
+ export * from "./IndieTabletopClubSymbol.tsx";
18
+ export * from "./Letterhead/index.tsx";
19
+ export * from "./LetterheadForm/index.tsx";
20
+ export * from "./LoadingIndicator.tsx";
21
+ export * from "./MiddotSeparated/MiddotSeparated.tsx";
22
+ export * from "./ModalDialog/index.tsx";
23
+ export * from "./QRCode/QRCode.tsx";
24
+ export * from "./ReleaseInfo/index.tsx";
25
+ export * from "./SafariCheck/SafariCheck.tsx";
26
+ export * from "./ServiceWorkerHandler.tsx";
27
+ export * from "./ShareButton/ShareButton.tsx";
28
+ export * from "./SubscribeCard/SubscribeByEmailCard.tsx";
29
+ export * from "./SubscribeCard/SubscribeByPledgeCard.tsx";
30
+ export * from "./Sync/SyncIcon.tsx";
31
+ export * from "./Sync/SyncLog.tsx";
32
+
33
+ // Hooks
34
+ export * from "./RulesetResolver.ts";
35
+ export * from "./use-async-op.ts";
36
+ export * from "./use-document-background-color.ts";
37
+ export * from "./use-form.ts";
38
+ export * from "./use-is-installed.ts";
39
+ export * from "./use-media-query.ts";
40
+ export * from "./use-reverting-state.ts";
41
+ export * from "./use-scroll-restoration.ts";
42
+ export * from "./useEnsureValue.ts";
43
+ export * from "./useInvokeClient.ts";
44
+ export * from "./useIsVisible.ts";
45
+
46
+ // Utils
47
+ export * from "./append-copy-to-text.ts";
48
+ export * from "./async-op.ts";
49
+ export * from "./caught-value.ts";
50
+ export * from "./class-names.ts";
51
+ export * from "./client.ts";
52
+ export * from "./copyrightRange.ts";
53
+ export * from "./createSafeStorage.ts";
54
+ export * from "./failureMessages.ts";
55
+ export * from "./groupBy.ts";
56
+ export * from "./HistoryState.ts";
57
+ export * from "./hrefs.ts";
58
+ export * from "./ids.ts";
59
+ export * from "./idToDate.ts";
60
+ export * from "./mailto.ts";
61
+ export * from "./media.ts";
62
+ export * from "./random.ts";
63
+ export * from "./result/swr.ts";
64
+ export * from "./sleep.ts";
65
+ export * from "./structs.ts";
66
+ export * from "./typeguards.ts";
67
+ export * from "./types.ts";
68
+ export * from "./unique.ts";
69
+ export * from "./utm.ts";
70
+ export * from "./validations.ts";
71
+
72
+ // Other
73
+ export * from "./EnumMapper.ts";
74
+ export * from "./ModernIDB/index.ts";
75
+ export * from "./store/index.tsx";
@@ -0,0 +1,10 @@
1
+ import { createVar, style } from "@vanilla-extract/css";
2
+ import { bounce } from "./animations.css.ts";
3
+
4
+ export const animationDelay = createVar();
5
+
6
+ export const dot = style({
7
+ fill: "currentcolor",
8
+ opacity: 0.8,
9
+ animation: `${bounce} 2s ${animationDelay} infinite`,
10
+ });
package/lib/mailto.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Encodes values to be used in mailto protocol.
3
+ *
4
+ * Note that we cannot simply use URLSeachParams because they, for example, encode a space
5
+ * as plus (+), which cannot be used in mailto if we want to have consistent behaviour in
6
+ * all email clients.
7
+ */
8
+ function encodeValuesForMailto<T extends object>(object: T) {
9
+ return Object.entries(object)
10
+ .filter(([_, v]) => !!v)
11
+ .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
12
+ .join("&");
13
+ }
14
+
15
+ function serializeArray(value?: string | string[] | null) {
16
+ return Array.isArray(value) ? value.join(",") : value;
17
+ }
18
+
19
+ type MailtoPayload = {
20
+ body?: string | null;
21
+ subject?: string | null;
22
+ cc?: string | string[] | null;
23
+ bcc?: string | string[] | null;
24
+ };
25
+
26
+ export function mailto(recipient: string | null, payload?: MailtoPayload) {
27
+ // If the recipient is falsy the user can choose who to send the email to.
28
+ const to = recipient ?? "";
29
+
30
+ const serialized = payload
31
+ ? `?${encodeValuesForMailto({
32
+ body: payload.body,
33
+ subject: payload.subject,
34
+ cc: serializeArray(payload.cc),
35
+ bcc: serializeArray(payload.bcc),
36
+ })}`
37
+ : "";
38
+
39
+ return `mailto:${to}${serialized}`;
40
+ }
package/lib/media.ts ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme
3
+ */
4
+
5
+ export enum PrefersColorScheme {
6
+ LIGHT = "(prefers-color-scheme: light)",
7
+ DARK = "(prefers-color-scheme: dark)",
8
+ }
9
+ /**
10
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion
11
+ */
12
+
13
+ export enum PrefersReducedMotion {
14
+ NO_PREFERENCE = "(prefers-reduced-motion: no-preference)",
15
+ REDUCE = "(prefers-reduced-motion: reduce)",
16
+ }
17
+
18
+ export enum Hover {
19
+ NONE = "(hover: none)",
20
+
21
+ // Some Samsung phones incorrectly report that they have "hover" even though they
22
+ // do not. Adding the pointer query correctly filters these phones out.
23
+ HOVER = "(hover: hover) and (pointer: fine)",
24
+ }
25
+
26
+ export enum MediaType {
27
+ PRINT = "print",
28
+ SCREEN = "screen",
29
+ }
30
+
31
+ export enum MinHeight {
32
+ TALL = "(min-height: 40em)",
33
+ }
34
+
35
+ export enum MinWidth {
36
+ SMALL = "(min-width: 28em)",
37
+ MEDIUM = "(min-width: 50em)",
38
+ WIDE = "(min-width: 66em)",
39
+ X_WIDE = "(min-width: 80em)",
40
+ XX_WIDE = "(min-width: 140em)",
41
+ }
42
+
43
+ export enum DisplayMode {
44
+ STANDALONE = "(display-mode: standalone)",
45
+ }
46
+
47
+ export enum Pointer {
48
+ COARSE = "(pointer: coarse)",
49
+ FINE = "(pointer: fine)",
50
+ }
package/lib/random.ts ADDED
@@ -0,0 +1,19 @@
1
+ export function random(max: number) {
2
+ return Math.floor(Math.random() * max);
3
+ }
4
+
5
+ export function randomItem<T>(array: T[]) {
6
+ return array[random(array.length)];
7
+ }
8
+
9
+ export function randomItemOrThrow<T>(array: T[]) {
10
+ const item = array[random(array.length)];
11
+
12
+ if (!item) {
13
+ throw new Error(
14
+ "Could not select a random item from list. Perhaps the list is empty?",
15
+ );
16
+ }
17
+
18
+ return item;
19
+ }
@@ -0,0 +1,18 @@
1
+ import type { SWRResponse } from "swr";
2
+ import { Failure, Pending } from "../async-op.ts";
3
+ import type { FailurePayload } from "../types.ts";
4
+
5
+ export function swrResponseToResult<T>(response: SWRResponse<T, unknown>) {
6
+ const { data, error } = response;
7
+
8
+ if (data !== undefined) {
9
+ return data;
10
+ }
11
+
12
+ if (error !== undefined) {
13
+ console.error(error);
14
+ return new Failure<FailurePayload>({ type: "UNKNOWN_ERROR" });
15
+ }
16
+
17
+ return new Pending();
18
+ }
@@ -9,4 +9,6 @@
9
9
  * await sleep(2000) // Waits for 2s
10
10
  * ```
11
11
  */
12
- export declare function sleep(ms: number): Promise<unknown>;
12
+ export function sleep(ms: number) {
13
+ return new Promise((r) => setTimeout(r, ms));
14
+ }