({
hidden
}: {
hidden: boolean
})
| 189 | } |
| 190 | |
| 191 | function AppUpdateNotice({ |
| 192 | hidden |
| 193 | }: { |
| 194 | hidden: boolean |
| 195 | }): JSX.Element | null { |
| 196 | const updateState = useAppUpdateState() |
| 197 | const label = appUpdateNoticeLabel(updateState) |
| 198 | const actionLabel = appUpdatePrimaryActionLabel(updateState) |
| 199 | |
| 200 | if (hidden || !label) return null |
| 201 | |
| 202 | const runPrimaryAction = (): void => { |
| 203 | if (updateState?.phase === 'available') { |
| 204 | void window.zen.downloadAppUpdate() |
| 205 | return |
| 206 | } |
| 207 | if (updateState?.phase === 'downloaded') { |
| 208 | void window.zen.installAppUpdate() |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | return ( |
| 213 | <div |
| 214 | role="status" |
| 215 | aria-live="polite" |
| 216 | className="fixed bottom-4 right-4 z-40 flex max-w-[min(28rem,calc(100vw-2rem))] items-center gap-2 rounded-xl border border-accent/30 bg-paper-50/95 px-3 py-2 text-sm text-ink-800 shadow-float backdrop-blur" |
| 217 | > |
| 218 | <span className="h-2 w-2 shrink-0 rounded-full bg-accent shadow-[0_0_0_4px_rgb(var(--z-accent)/0.12)]" /> |
| 219 | <span className="min-w-0 truncate font-medium">{label}</span> |
| 220 | {updateState?.phase === 'downloading' && ( |
| 221 | <span className="shrink-0 rounded-md bg-paper-200/80 px-1.5 py-0.5 text-xs font-medium text-ink-600"> |
| 222 | {Math.round(updateState.progressPercent ?? 0)}% |
| 223 | </span> |
| 224 | )} |
| 225 | {actionLabel && ( |
| 226 | <button |
| 227 | type="button" |
| 228 | onClick={runPrimaryAction} |
| 229 | className="shrink-0 rounded-lg border border-accent/30 bg-accent/10 px-2.5 py-1 text-xs font-medium text-accent transition-colors hover:bg-accent/15" |
| 230 | > |
| 231 | {actionLabel} |
| 232 | </button> |
| 233 | )} |
| 234 | </div> |
| 235 | ) |
| 236 | } |
| 237 | |
| 238 | function App(): JSX.Element { |
| 239 | const mountedAtRef = useRef(performance.now()) |
nothing calls this directly
no test coverage detected