clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/141-effect-library
lofi / package / preact / PwaActions.tsx
PwaActions.tsx 4.7K
1import type { VNode } from "preact";2import { useEffect, useState } from "preact/hooks";3import {4 type PwaController,5 pwaController,6 pwaFailureMessage,7 type PwaState,8} from "../runtime/pwa.ts";9import { type StorageForkSurface, useStorageFork } from "./use-storage-fork.ts";1011export { pwaFailureMessage } from "../runtime/pwa.ts";12export type {13 PwaController,14 PwaFailureCode,15 PwaInstallState,16 PwaState,17 PwaUpdateState,18 PwaWorkerState,19} from "../runtime/pwa.ts";2021/**22 * Subscribes a Preact component to an isolated or shared PWA controller.23 *24 * @example25 * ```tsx26 * import { usePwaState } from "@nzip/lofi/preact";27 *28 * const pwa = usePwaState();29 * const updateReady = pwa.update === "ready";30 * ```31 *32 * @param controller The controller to observe; defaults to the package-wide PWA controller.33 * @returns The current install/update state, kept live via subscription.34 */35export function usePwaState(controller: PwaController = pwaController): PwaState {36 const [state, setState] = useState<PwaState>(controller.getState());37 useEffect(() => controller.subscribe(setState), [controller]);38 return state;39}4041/** Optional controller, fork guard, and heading text for {@link PwaActions}. */42export interface PwaActionsProps {43 /** Controller to observe; defaults to the package-wide PWA controller. */44 readonly controller?: PwaController;45 /** Fork guard to observe; defaults to the package-wide guard. */46 readonly fork?: StorageForkSurface;47 /** Heading rendered above install or update actions. */48 readonly title?: string;49}5051/**52 * A composable install/update surface that keeps browser event handling package-owned.53 *54 * @example55 * ```tsx56 * import { PwaActions } from "@nzip/lofi/preact";57 *58 * <PwaActions title="Install this app" />;59 * ```60 *61 * @param props An optional controller override and heading text.62 * @returns The install/update section, or `null` when no action or status is relevant.63 */64export function PwaActions({65 controller = pwaController,66 fork,67 title = "Install & updates",68}: PwaActionsProps): VNode | null {69 const state = usePwaState(controller);70 const forkState = useStorageFork(fork);71 const visible = state.install === "available" || state.install === "manual-ios" ||72 state.install === "manual-browser" || state.install === "unsupported" ||73 state.install === "accepted" || state.install === "dismissed" ||74 state.update !== "idle" || Boolean(state.failure);75 if (!visible) return null;7677 return (78 <section class="pwa-actions" aria-labelledby="pwa-actions-title">79 <h3 id="pwa-actions-title">{title}</h3>80 {state.install === "available" && (81 <button82 type="button"83 onClick={() => void controller.requestInstall()}84 >85 Install app86 </button>87 )}88 {state.install === "accepted" && (89 <p role="status">Installation accepted. Follow the browser prompt to finish.</p>90 )}91 {state.install === "dismissed" && (92 <p role="status">Installation dismissed. You can use the browser install menu later.</p>93 )}94 {state.install === "manual-ios" && (95 <div class="pwa-ios-guidance">96 {forkState.state === "browser-data-at-risk" && (97 <p class="pwa-fork-warning" role="alert">98 Installing opens a fresh, empty copy of this app — the data you created here stays in99 Safari and is not carried over. Turn on sync or back up your account first, then100 restore it in the installed app.101 </p>102 )}103 <p>Install this app on your iPhone or iPad:</p>104 <ol>105 <li>Open the Share menu.</li>106 <li>Choose Add to Home Screen.</li>107 <li>Confirm Add.</li>108 </ol>109 </div>110 )}111 {state.install === "manual-browser" && (112 <div class="pwa-browser-guidance">113 <p>No in-page install button is available right now.</p>114 <p>Look in its menu for Install app or Add to Home Screen, if offered.</p>115 </div>116 )}117 {state.install === "unsupported" && (118 <p>Installation is not supported in this browser or browsing context.</p>119 )}120 {state.update === "checking" && <p role="status">Checking for updates…</p>}121 {state.update === "installing" && <p role="status">Installing update…</p>}122 {state.update === "ready" && (123 <button124 type="button"125 onClick={() => controller.applyUpdate()}126 >127 Update app128 </button>129 )}130 {state.update === "applying" && <p role="status">Applying update…</p>}131 {state.failure && (132 <p class="pwa-error" role="alert">{pwaFailureMessage(state.failure.code)}</p>133 )}134 </section>135 );136}