@connextar/house 0.4.0 → 0.5.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.
- package/README.md +109 -3
- package/dist/setup-guide/bits.d.ts +89 -0
- package/dist/setup-guide/bits.d.ts.map +1 -0
- package/dist/setup-guide/bits.js +134 -0
- package/dist/setup-guide/bits.js.map +1 -0
- package/dist/setup-guide/card.d.ts +33 -0
- package/dist/setup-guide/card.d.ts.map +1 -0
- package/dist/setup-guide/card.js +56 -0
- package/dist/setup-guide/card.js.map +1 -0
- package/dist/setup-guide/class-names.d.ts +68 -0
- package/dist/setup-guide/class-names.d.ts.map +1 -0
- package/dist/setup-guide/class-names.js +63 -0
- package/dist/setup-guide/class-names.js.map +1 -0
- package/dist/setup-guide/context.d.ts +89 -0
- package/dist/setup-guide/context.d.ts.map +1 -0
- package/dist/setup-guide/context.js +124 -0
- package/dist/setup-guide/context.js.map +1 -0
- package/dist/setup-guide/guide.d.ts +159 -0
- package/dist/setup-guide/guide.d.ts.map +1 -0
- package/dist/setup-guide/guide.js +128 -0
- package/dist/setup-guide/guide.js.map +1 -0
- package/dist/setup-guide/index.d.ts +27 -0
- package/dist/setup-guide/index.d.ts.map +1 -0
- package/dist/setup-guide/index.js +27 -0
- package/dist/setup-guide/index.js.map +1 -0
- package/dist/setup-guide/launcher.d.ts +38 -0
- package/dist/setup-guide/launcher.d.ts.map +1 -0
- package/dist/setup-guide/launcher.js +60 -0
- package/dist/setup-guide/launcher.js.map +1 -0
- package/dist/setup-guide/strip.d.ts +21 -0
- package/dist/setup-guide/strip.d.ts.map +1 -0
- package/dist/setup-guide/strip.js +42 -0
- package/dist/setup-guide/strip.js.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export const SETUP_GUIDE_CLASS_NAMES = {
|
|
2
|
+
card: "overflow-hidden rounded-2xl border border-border bg-card shadow-sm",
|
|
3
|
+
band: "flex flex-col p-6 text-white sm:p-8",
|
|
4
|
+
bandEyebrow: "text-xs font-bold tracking-[0.14em] text-emerald-300 uppercase",
|
|
5
|
+
bandTitle: "mt-3.5 text-2xl leading-tight font-bold tracking-tight text-balance",
|
|
6
|
+
bandLead: "mt-3 text-sm leading-relaxed text-slate-300",
|
|
7
|
+
bandMuted: "text-slate-400",
|
|
8
|
+
bandLink: "font-medium text-emerald-300 underline-offset-4 hover:underline",
|
|
9
|
+
bandQuietButton: "text-slate-400 hover:text-white focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
|
10
|
+
panel: "min-w-0 flex-1 p-3 sm:p-5",
|
|
11
|
+
panelHeading: "text-sm font-semibold",
|
|
12
|
+
panelNote: "text-xs text-muted-foreground",
|
|
13
|
+
row: "grid grid-cols-[auto_minmax(0,1fr)] gap-x-3.5 gap-y-2 border-b border-border px-3 py-3.5 last:border-b-0 sm:px-4",
|
|
14
|
+
rowNext: "my-1 rounded-xl border border-primary/25 bg-primary/5 px-3 py-4 sm:px-4",
|
|
15
|
+
rowTitle: "text-[15px] font-semibold",
|
|
16
|
+
rowTitleDone: "text-muted-foreground",
|
|
17
|
+
rowDescription: "mt-1 max-w-xl text-[13px] leading-normal text-muted-foreground",
|
|
18
|
+
subRow: "flex flex-wrap items-center gap-x-3 gap-y-1 border-t border-border py-2.5",
|
|
19
|
+
circle: "flex shrink-0 items-center justify-center rounded-full text-xs font-semibold tabular-nums",
|
|
20
|
+
circleDone: "bg-primary text-primary-foreground",
|
|
21
|
+
circleNext: "bg-primary text-primary-foreground ring-4 ring-primary/15",
|
|
22
|
+
circleReady: "border-[1.5px] border-primary bg-background text-primary",
|
|
23
|
+
circleTodo: "border-[1.5px] border-muted-foreground/40 bg-background text-muted-foreground",
|
|
24
|
+
chip: "inline-flex h-6 shrink-0 items-center gap-1 rounded-full border border-primary/30 bg-primary/10 px-2 text-xs font-semibold whitespace-nowrap tabular-nums text-primary",
|
|
25
|
+
chipEarned: "inline-flex h-6 shrink-0 items-center gap-1 px-0.5 text-xs font-semibold whitespace-nowrap tabular-nums text-primary",
|
|
26
|
+
chipOnComplete: "border-dashed border-primary/50 bg-background",
|
|
27
|
+
chipDark: "border-white/35 bg-white/10 text-white",
|
|
28
|
+
bar: "h-2 w-full overflow-hidden rounded-full bg-secondary",
|
|
29
|
+
barFill: "h-full rounded-full bg-primary transition-[width]",
|
|
30
|
+
segment: "h-1.5 flex-1 rounded-full",
|
|
31
|
+
segmentDone: "bg-primary",
|
|
32
|
+
segmentDoneDark: "bg-emerald-300",
|
|
33
|
+
segmentTodo: "bg-secondary",
|
|
34
|
+
segmentTodoDark: "bg-white/15",
|
|
35
|
+
button: "inline-flex items-center gap-1.5 rounded-md px-3 py-2 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none disabled:pointer-events-none disabled:opacity-60",
|
|
36
|
+
buttonPrimary: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
37
|
+
buttonOutline: "border border-border hover:bg-muted",
|
|
38
|
+
buttonQuiet: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
39
|
+
iconButton: "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
|
40
|
+
popover: "absolute right-0 z-50 mt-1 w-80 rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-lg",
|
|
41
|
+
popoverTitle: "text-sm font-semibold",
|
|
42
|
+
popoverStep: "flex gap-2.5 text-[13px] leading-normal",
|
|
43
|
+
popoverNumber: "mt-px flex size-5 shrink-0 items-center justify-center rounded-full bg-secondary text-[11px] font-bold text-secondary-foreground",
|
|
44
|
+
popoverFooter: "mt-3.5 flex items-center justify-between gap-2 border-t border-border pt-3",
|
|
45
|
+
strip: "rounded-2xl border border-border bg-card px-4 py-4 shadow-sm sm:px-5",
|
|
46
|
+
stripTitle: "text-[15px] font-semibold",
|
|
47
|
+
stripSummary: "text-[13px] text-muted-foreground",
|
|
48
|
+
stripTile: "flex min-h-[6.5rem] flex-col gap-2 rounded-xl border border-border bg-background p-3",
|
|
49
|
+
stripTileNext: "border-primary/35 bg-primary/5",
|
|
50
|
+
launcher: "relative inline-flex h-9 shrink-0 items-center gap-2 rounded-full border border-border bg-background py-0 pr-2.5 pl-1.5 text-[13px] font-semibold shadow-sm transition-colors hover:border-primary/40 hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
|
51
|
+
launcherBadge: "absolute -top-3 -right-2 rounded-full bg-primary px-1.5 py-0.5 text-xs leading-none font-semibold text-primary-foreground shadow-md",
|
|
52
|
+
blocked: "px-1.5 text-xs text-muted-foreground",
|
|
53
|
+
srOnly: "sr-only",
|
|
54
|
+
};
|
|
55
|
+
/** The dark band's background. Replaceable outright; `{}` falls back to the card. */
|
|
56
|
+
export const SETUP_BAND_STYLE = {
|
|
57
|
+
background: [
|
|
58
|
+
"radial-gradient(120% 70% at 0% 0%, color-mix(in oklab, var(--primary) 22%, transparent), transparent 55%)",
|
|
59
|
+
"radial-gradient(90% 60% at 100% 100%, color-mix(in oklab, var(--primary) 12%, transparent), transparent 60%)",
|
|
60
|
+
"#0e2f27",
|
|
61
|
+
].join(", "),
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=class-names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"class-names.js","sourceRoot":"","sources":["../../src/setup-guide/class-names.ts"],"names":[],"mappings":"AAiEA,MAAM,CAAC,MAAM,uBAAuB,GAAyB;IAC3D,IAAI,EAAE,oEAAoE;IAC1E,IAAI,EAAE,qCAAqC;IAC3C,WAAW,EAAE,gEAAgE;IAC7E,SAAS,EAAE,qEAAqE;IAChF,QAAQ,EAAE,6CAA6C;IACvD,SAAS,EAAE,gBAAgB;IAC3B,QAAQ,EAAE,iEAAiE;IAC3E,eAAe,EACb,yGAAyG;IAC3G,KAAK,EAAE,2BAA2B;IAClC,YAAY,EAAE,uBAAuB;IACrC,SAAS,EAAE,+BAA+B;IAC1C,GAAG,EAAE,kHAAkH;IACvH,OAAO,EAAE,yEAAyE;IAClF,QAAQ,EAAE,2BAA2B;IACrC,YAAY,EAAE,uBAAuB;IACrC,cAAc,EAAE,gEAAgE;IAChF,MAAM,EAAE,2EAA2E;IACnF,MAAM,EAAE,2FAA2F;IACnG,UAAU,EAAE,oCAAoC;IAChD,UAAU,EAAE,2DAA2D;IACvE,WAAW,EAAE,0DAA0D;IACvE,UAAU,EAAE,+EAA+E;IAC3F,IAAI,EAAE,wKAAwK;IAC9K,UAAU,EACR,sHAAsH;IACxH,cAAc,EAAE,+CAA+C;IAC/D,QAAQ,EAAE,wCAAwC;IAClD,GAAG,EAAE,sDAAsD;IAC3D,OAAO,EAAE,mDAAmD;IAC5D,OAAO,EAAE,2BAA2B;IACpC,WAAW,EAAE,YAAY;IACzB,eAAe,EAAE,gBAAgB;IACjC,WAAW,EAAE,cAAc;IAC3B,eAAe,EAAE,aAAa;IAC9B,MAAM,EACJ,sNAAsN;IACxN,aAAa,EAAE,wDAAwD;IACvE,aAAa,EAAE,qCAAqC;IACpD,WAAW,EAAE,4DAA4D;IACzE,UAAU,EACR,gNAAgN;IAClN,OAAO,EACL,kHAAkH;IACpH,YAAY,EAAE,uBAAuB;IACrC,WAAW,EAAE,yCAAyC;IACtD,aAAa,EACX,kIAAkI;IACpI,aAAa,EAAE,4EAA4E;IAC3F,KAAK,EAAE,sEAAsE;IAC7E,UAAU,EAAE,2BAA2B;IACvC,YAAY,EAAE,mCAAmC;IACjD,SAAS,EAAE,sFAAsF;IACjG,aAAa,EAAE,gCAAgC;IAC/C,QAAQ,EACN,+RAA+R;IACjS,aAAa,EACX,qIAAqI;IACvI,OAAO,EAAE,sCAAsC;IAC/C,MAAM,EAAE,SAAS;CAClB,CAAC;AAEF,qFAAqF;AACrF,MAAM,CAAC,MAAM,gBAAgB,GAA2B;IACtD,UAAU,EAAE;QACV,2GAA2G;QAC3G,8GAA8G;QAC9G,SAAS;KACV,CAAC,IAAI,CAAC,IAAI,CAAC;CACb,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { SetupGuide, SetupReward } from "./guide.js";
|
|
3
|
+
/**
|
|
4
|
+
* The guide, shared by everything that draws it: a launcher in the header, a
|
|
5
|
+
* card on the home page, a strip on each screen where steps are done.
|
|
6
|
+
*
|
|
7
|
+
* The host reads the guide on the server and hands it in. What is *done* is
|
|
8
|
+
* always the server's answer — this never decides that. Paying for it is a
|
|
9
|
+
* separate request, made when the server's answer shows something done and
|
|
10
|
+
* unpaid, or shortly after any save, so the reward lands while the person is
|
|
11
|
+
* still looking at what earned it.
|
|
12
|
+
*
|
|
13
|
+
* Two things are deliberately the host's: the transport (this package makes no
|
|
14
|
+
* HTTP requests and knows no routes) and what a reward is called. MAS has no
|
|
15
|
+
* credits; an app that gives nothing at all passes steps with no reward and
|
|
16
|
+
* gets a checklist that still works.
|
|
17
|
+
*/
|
|
18
|
+
export interface SetupSyncResult {
|
|
19
|
+
guide: SetupGuide;
|
|
20
|
+
rewards: SetupReward[];
|
|
21
|
+
}
|
|
22
|
+
export interface SetupGuideTransport {
|
|
23
|
+
/**
|
|
24
|
+
* Pay for everything done and unpaid, and answer with the guide as it now
|
|
25
|
+
* stands. `null` means the request did not succeed — the provider keeps what
|
|
26
|
+
* it has and says nothing, because a failed sync is not the person's problem
|
|
27
|
+
* and will be retried by the next save.
|
|
28
|
+
*/
|
|
29
|
+
sync: (pathway: string) => Promise<SetupSyncResult | null>;
|
|
30
|
+
/** Hide the guide, or bring it back. `false` means it did not take. */
|
|
31
|
+
setHidden: (pathway: string, hidden: boolean) => Promise<boolean>;
|
|
32
|
+
}
|
|
33
|
+
/** What one of these is called, singular and plural. */
|
|
34
|
+
export interface RewardWords {
|
|
35
|
+
one: string;
|
|
36
|
+
many: string;
|
|
37
|
+
}
|
|
38
|
+
export declare const DEFAULT_REWARD_WORDS: RewardWords;
|
|
39
|
+
export declare function rewardWord(count: number, words: RewardWords): string;
|
|
40
|
+
export interface SetupLinkProps {
|
|
41
|
+
href: string;
|
|
42
|
+
children: React.ReactNode;
|
|
43
|
+
className?: string;
|
|
44
|
+
onClick?: () => void;
|
|
45
|
+
target?: string;
|
|
46
|
+
rel?: string;
|
|
47
|
+
title?: string;
|
|
48
|
+
"aria-label"?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* How a link is drawn. The default is a plain `<a>`, which works everywhere and
|
|
52
|
+
* costs a full page load; an app with a router passes its own so the guide
|
|
53
|
+
* navigates the way the rest of the app does.
|
|
54
|
+
*/
|
|
55
|
+
export type SetupLinkComponent = (props: SetupLinkProps) => React.ReactNode;
|
|
56
|
+
export interface SetupGuideState {
|
|
57
|
+
guide: SetupGuide | null;
|
|
58
|
+
/** Somebody viewing this account rather than living in it: shown, never paid or changed. */
|
|
59
|
+
readOnly: boolean;
|
|
60
|
+
words: RewardWords;
|
|
61
|
+
Link: SetupLinkComponent;
|
|
62
|
+
panelOpen: boolean;
|
|
63
|
+
setPanelOpen: (open: boolean) => void;
|
|
64
|
+
setHidden: (hidden: boolean) => Promise<void>;
|
|
65
|
+
/** Paid a moment ago, for the launcher to flash. */
|
|
66
|
+
justEarned: number | null;
|
|
67
|
+
}
|
|
68
|
+
/** Null outside the provider, and for accounts with no guide — every part checks. */
|
|
69
|
+
export declare function useSetupGuide(): SetupGuideState | null;
|
|
70
|
+
export interface SetupGuideProviderProps {
|
|
71
|
+
initial: SetupGuide | null;
|
|
72
|
+
transport: SetupGuideTransport;
|
|
73
|
+
readOnly?: boolean;
|
|
74
|
+
words?: RewardWords;
|
|
75
|
+
/**
|
|
76
|
+
* A window event the app fires after a successful write. Without one the
|
|
77
|
+
* guide only catches up when the page is read again, which is correct but
|
|
78
|
+
* slower — the reward lands on the next navigation rather than in front of
|
|
79
|
+
* the person who earned it.
|
|
80
|
+
*/
|
|
81
|
+
changeEvent?: string;
|
|
82
|
+
settleMs?: number;
|
|
83
|
+
/** Called when bringing a hidden guide back, which needs a fresh read. */
|
|
84
|
+
onRefresh?: () => void;
|
|
85
|
+
link?: SetupLinkComponent;
|
|
86
|
+
children: React.ReactNode;
|
|
87
|
+
}
|
|
88
|
+
export declare function SetupGuideProvider({ initial, transport, readOnly, words, changeEvent, settleMs, onRefresh, link, children, }: SetupGuideProviderProps): React.JSX.Element;
|
|
89
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/setup-guide/context.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAC3D,uEAAuE;IACvE,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED,wDAAwD;AACxD,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,oBAAoB,EAAE,WAAgD,CAAC;AAEpF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAEpE;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,KAAK,CAAC,SAAS,CAAC;AAI5E,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,4FAA4F;IAC5F,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,WAAW,CAAC;IACnB,IAAI,EAAE,kBAAkB,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,oDAAoD;IACpD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAID,qFAAqF;AACrF,wBAAgB,aAAa,IAAI,eAAe,GAAG,IAAI,CAEtD;AAOD,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,mBAAmB,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,OAAO,EACP,SAAS,EACT,QAAgB,EAChB,KAA4B,EAC5B,WAAW,EACX,QAAoB,EACpB,SAAS,EACT,IAAgB,EAChB,QAAQ,GACT,EAAE,uBAAuB,qBAgHzB"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useActionReporter } from "../ux/feedback.js";
|
|
5
|
+
export const DEFAULT_REWARD_WORDS = { one: "credit", many: "credits" };
|
|
6
|
+
export function rewardWord(count, words) {
|
|
7
|
+
return count === 1 ? words.one : words.many;
|
|
8
|
+
}
|
|
9
|
+
const PlainLink = ({ children, ...rest }) => _jsx("a", { ...rest, children: children });
|
|
10
|
+
const SetupGuideContext = React.createContext(null);
|
|
11
|
+
/** Null outside the provider, and for accounts with no guide — every part checks. */
|
|
12
|
+
export function useSetupGuide() {
|
|
13
|
+
return React.useContext(SetupGuideContext);
|
|
14
|
+
}
|
|
15
|
+
/** Long enough that a burst of saves asks one question rather than five. */
|
|
16
|
+
const SETTLE_MS = 1200;
|
|
17
|
+
/** How long the launcher keeps showing what just landed. */
|
|
18
|
+
const FLASH_MS = 4000;
|
|
19
|
+
export function SetupGuideProvider({ initial, transport, readOnly = false, words = DEFAULT_REWARD_WORDS, changeEvent, settleMs = SETTLE_MS, onRefresh, link = PlainLink, children, }) {
|
|
20
|
+
const report = useActionReporter();
|
|
21
|
+
const [guide, setGuide] = React.useState(initial);
|
|
22
|
+
// A fresh reading from the server replaces ours: it knows things we cannot,
|
|
23
|
+
// like a learner added in another tab.
|
|
24
|
+
const [seen, setSeen] = React.useState(initial);
|
|
25
|
+
if (initial !== seen) {
|
|
26
|
+
setSeen(initial);
|
|
27
|
+
setGuide(initial);
|
|
28
|
+
}
|
|
29
|
+
const [panelOpen, setPanelOpen] = React.useState(false);
|
|
30
|
+
const [justEarned, setJustEarned] = React.useState(null);
|
|
31
|
+
const inFlight = React.useRef(false);
|
|
32
|
+
const pathway = guide?.pathway ?? null;
|
|
33
|
+
const listening = Boolean(guide && !guide.hidden && !guide.complete && !readOnly && changeEvent);
|
|
34
|
+
// Read through a ref so the sync function's identity never changes, and the
|
|
35
|
+
// listeners below are not torn down and rebuilt on every render.
|
|
36
|
+
const latest = React.useRef({ transport, words, report });
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
latest.current = { transport, words, report };
|
|
39
|
+
});
|
|
40
|
+
const sync = React.useCallback(async () => {
|
|
41
|
+
if (!pathway || inFlight.current)
|
|
42
|
+
return;
|
|
43
|
+
inFlight.current = true;
|
|
44
|
+
const { transport: post, words: said, report: tell } = latest.current;
|
|
45
|
+
let result = null;
|
|
46
|
+
try {
|
|
47
|
+
result = await post.sync(pathway);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
inFlight.current = false;
|
|
51
|
+
}
|
|
52
|
+
if (!result)
|
|
53
|
+
return;
|
|
54
|
+
setGuide(result.guide);
|
|
55
|
+
const earned = result.rewards.reduce((total, reward) => total + reward.reward, 0);
|
|
56
|
+
if (result.rewards.length === 0 || earned === 0)
|
|
57
|
+
return;
|
|
58
|
+
setJustEarned(earned);
|
|
59
|
+
const left = result.guide.totalReward - result.guide.earnedReward;
|
|
60
|
+
const what = result.rewards.length === 1
|
|
61
|
+
? (result.rewards[0]?.title ?? "A setup step")
|
|
62
|
+
: `${result.rewards.length} steps done`;
|
|
63
|
+
tell({
|
|
64
|
+
tone: "success",
|
|
65
|
+
title: `+${earned} ${rewardWord(earned, said)} — ${what}`,
|
|
66
|
+
description: left > 0
|
|
67
|
+
? `${result.guide.earnedReward} of ${result.guide.totalReward} earned so far — ${left} more to earn.`
|
|
68
|
+
: `All ${result.guide.totalReward} earned, and they are yours to keep.`,
|
|
69
|
+
});
|
|
70
|
+
}, [pathway]);
|
|
71
|
+
// Something done and not yet paid, as the server reads it: pay for it now.
|
|
72
|
+
// Deferred rather than called straight from the effect, which would be
|
|
73
|
+
// setting state during an effect's own render pass.
|
|
74
|
+
const pending = guide && !guide.hidden && !readOnly ? guide.pendingKeys.join(",") : "";
|
|
75
|
+
React.useEffect(() => {
|
|
76
|
+
if (!pending)
|
|
77
|
+
return;
|
|
78
|
+
const timer = setTimeout(() => void sync(), 0);
|
|
79
|
+
return () => clearTimeout(timer);
|
|
80
|
+
}, [pending, sync]);
|
|
81
|
+
// Most saves never refresh the page, so ask once the saving has settled.
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
if (!listening || !changeEvent)
|
|
84
|
+
return;
|
|
85
|
+
let timer;
|
|
86
|
+
const onChange = () => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
timer = setTimeout(() => void sync(), settleMs);
|
|
89
|
+
};
|
|
90
|
+
window.addEventListener(changeEvent, onChange);
|
|
91
|
+
return () => {
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
window.removeEventListener(changeEvent, onChange);
|
|
94
|
+
};
|
|
95
|
+
}, [listening, changeEvent, settleMs, sync]);
|
|
96
|
+
React.useEffect(() => {
|
|
97
|
+
if (justEarned === null)
|
|
98
|
+
return;
|
|
99
|
+
const timer = setTimeout(() => setJustEarned(null), FLASH_MS);
|
|
100
|
+
return () => clearTimeout(timer);
|
|
101
|
+
}, [justEarned]);
|
|
102
|
+
const setHidden = React.useCallback(async (hidden) => {
|
|
103
|
+
if (!pathway || readOnly)
|
|
104
|
+
return;
|
|
105
|
+
// Optimistic: hiding something should feel like closing it, not like
|
|
106
|
+
// asking permission to close it.
|
|
107
|
+
setGuide((current) => (current ? { ...current, hidden } : current));
|
|
108
|
+
const ok = await latest.current.transport.setHidden(pathway, hidden);
|
|
109
|
+
if (!ok) {
|
|
110
|
+
setGuide((current) => (current ? { ...current, hidden: !hidden } : current));
|
|
111
|
+
latest.current.report({ tone: "error", title: "Could not change the setup guide" });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (hidden)
|
|
115
|
+
setPanelOpen(false);
|
|
116
|
+
// A hidden guide skipped working out what was done, so bringing it back
|
|
117
|
+
// needs the server asked again.
|
|
118
|
+
else
|
|
119
|
+
onRefresh?.();
|
|
120
|
+
}, [pathway, readOnly, onRefresh]);
|
|
121
|
+
const value = React.useMemo(() => ({ guide, readOnly, words, Link: link, panelOpen, setPanelOpen, setHidden, justEarned }), [guide, readOnly, words, link, panelOpen, setHidden, justEarned]);
|
|
122
|
+
return _jsx(SetupGuideContext.Provider, { value: value, children: children });
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/setup-guide/context.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a new account is asked to do, how far through it is, and what that has
|
|
3
|
+
* earned. No React and no DOM, so the rules are tested without a browser and
|
|
4
|
+
* hold whatever draws them.
|
|
5
|
+
*
|
|
6
|
+
* **Nothing about progress is stored.** A step is *done* when the account holds
|
|
7
|
+
* what it asks for — a learner, a price, a published course — however it got
|
|
8
|
+
* there; the host reads that fresh each time. What *is* stored is the reward:
|
|
9
|
+
* one entry per step per account, keyed on the step, so a step pays once
|
|
10
|
+
* however many times its thing is made, deleted and made again. That split is
|
|
11
|
+
* the whole design, and it is why `done` and `earned` arrive here as two
|
|
12
|
+
* separate sets rather than one status per step.
|
|
13
|
+
*
|
|
14
|
+
* A step key is permanent. Rewards are keyed on it, so renaming one pays
|
|
15
|
+
* everybody a second time.
|
|
16
|
+
*/
|
|
17
|
+
import type { ReactNode } from "react";
|
|
18
|
+
/** Short numbered instructions. `**bold**` marks the app's own labels, `_italic_` an example. */
|
|
19
|
+
export interface SetupHowTo {
|
|
20
|
+
title: string;
|
|
21
|
+
steps: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
/** Where to read more. Rendered as a link, so the host decides what the href means. */
|
|
24
|
+
export interface SetupGuideLink {
|
|
25
|
+
href: string;
|
|
26
|
+
title: string;
|
|
27
|
+
}
|
|
28
|
+
export interface SetupStepDef {
|
|
29
|
+
/** Stable forever: rewards are keyed on it. */
|
|
30
|
+
key: string;
|
|
31
|
+
title: string;
|
|
32
|
+
/** What finishing it pays. Omit, or 0, for a step that is progress only. */
|
|
33
|
+
reward?: number;
|
|
34
|
+
how?: SetupHowTo;
|
|
35
|
+
guide?: SetupGuideLink;
|
|
36
|
+
}
|
|
37
|
+
export interface SetupItemDef {
|
|
38
|
+
key: string;
|
|
39
|
+
title: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
/** Drawn beside the title. The host's own icon element, so this package needs no icon set of its own. */
|
|
42
|
+
icon?: ReactNode;
|
|
43
|
+
/** Where the item is done. */
|
|
44
|
+
href: string;
|
|
45
|
+
actionLabel: string;
|
|
46
|
+
/**
|
|
47
|
+
* The screen that shows this item's steps, when it has several. Everywhere
|
|
48
|
+
* else shows the item's progress and a way here.
|
|
49
|
+
*/
|
|
50
|
+
screen?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Step keys that must be done before this item can be started. An item nobody
|
|
53
|
+
* can start yet says so instead of offering a button that leads nowhere useful.
|
|
54
|
+
*/
|
|
55
|
+
requires?: readonly string[];
|
|
56
|
+
/** What to say while it is blocked, e.g. "After your first learner". */
|
|
57
|
+
blockedLabel?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Its rewards land only once the whole thing is built — for a flow that saves
|
|
60
|
+
* nothing before its last step, so the steps tick as choices are made and pay
|
|
61
|
+
* at the end.
|
|
62
|
+
*/
|
|
63
|
+
paysOnComplete?: boolean;
|
|
64
|
+
steps: readonly SetupStepDef[];
|
|
65
|
+
}
|
|
66
|
+
export interface GuideStep extends SetupStepDef {
|
|
67
|
+
/** The account holds what the step asks for. */
|
|
68
|
+
done: boolean;
|
|
69
|
+
/** Its reward has been paid. */
|
|
70
|
+
earned: boolean;
|
|
71
|
+
/** Always a number here, whatever the definition left out. */
|
|
72
|
+
reward: number;
|
|
73
|
+
}
|
|
74
|
+
export interface GuideItem extends Omit<SetupItemDef, "steps"> {
|
|
75
|
+
steps: GuideStep[];
|
|
76
|
+
reward: number;
|
|
77
|
+
earnedReward: number;
|
|
78
|
+
doneCount: number;
|
|
79
|
+
done: boolean;
|
|
80
|
+
/** Why it cannot be started yet, when it cannot. */
|
|
81
|
+
blocked: string | null;
|
|
82
|
+
}
|
|
83
|
+
export interface SetupGuide {
|
|
84
|
+
/** Which set of steps this is — the host's own name for it. */
|
|
85
|
+
pathway: string;
|
|
86
|
+
items: GuideItem[];
|
|
87
|
+
totalReward: number;
|
|
88
|
+
earnedReward: number;
|
|
89
|
+
doneItems: number;
|
|
90
|
+
/** The first item not done and not blocked — what the guide leads with. */
|
|
91
|
+
nextKey: string | null;
|
|
92
|
+
/** Every step done and paid. */
|
|
93
|
+
complete: boolean;
|
|
94
|
+
/** Done but not yet paid. A sync would pay exactly these. */
|
|
95
|
+
pendingKeys: string[];
|
|
96
|
+
hidden: boolean;
|
|
97
|
+
}
|
|
98
|
+
/** What one step paid, for announcing it. */
|
|
99
|
+
export interface SetupReward {
|
|
100
|
+
key: string;
|
|
101
|
+
title: string;
|
|
102
|
+
reward: number;
|
|
103
|
+
}
|
|
104
|
+
export interface BuildGuideInput {
|
|
105
|
+
pathway: string;
|
|
106
|
+
items: readonly SetupItemDef[];
|
|
107
|
+
/** Step keys the account's own records satisfy. */
|
|
108
|
+
done: ReadonlySet<string>;
|
|
109
|
+
/** Step keys already paid for. */
|
|
110
|
+
earned: ReadonlySet<string>;
|
|
111
|
+
hidden?: boolean;
|
|
112
|
+
}
|
|
113
|
+
/** Every step of every item, in order. */
|
|
114
|
+
export declare function stepsOf(items: readonly SetupItemDef[]): SetupStepDef[];
|
|
115
|
+
/** Every step key, for checking what a request names before trusting it. */
|
|
116
|
+
export declare function stepKeysOf(items: readonly SetupItemDef[]): Set<string>;
|
|
117
|
+
/** The guide as it stands for one account. Pure: the same inputs always give the same answer. */
|
|
118
|
+
export declare function buildGuide({ pathway, items, done, earned, hidden }: BuildGuideInput): SetupGuide;
|
|
119
|
+
export declare function findItem(guide: SetupGuide | null | undefined, key: string): GuideItem | null;
|
|
120
|
+
/**
|
|
121
|
+
* What counts as done: what the records show now, plus everything already paid
|
|
122
|
+
* for.
|
|
123
|
+
*
|
|
124
|
+
* A reward, once earned, is not taken back — so a step whose thing has since
|
|
125
|
+
* been deleted stays ticked rather than reopening and asking to be paid a
|
|
126
|
+
* second time. Hosts pass the result of this as `done`.
|
|
127
|
+
*/
|
|
128
|
+
export declare function doneSteps(facts: ReadonlySet<string>, earned: ReadonlySet<string>): Set<string>;
|
|
129
|
+
/**
|
|
130
|
+
* The steps this account has done and not been paid for — what a sync owes it.
|
|
131
|
+
*
|
|
132
|
+
* Kept here, away from the database, because *which* steps are owed is a rule
|
|
133
|
+
* and writing the entries is a transaction. The host loops over this and writes
|
|
134
|
+
* one row each, letting its unique index refuse a second payment: two tabs
|
|
135
|
+
* asking at the same moment both get this list, and only one of them gets to
|
|
136
|
+
* write each row.
|
|
137
|
+
*/
|
|
138
|
+
export declare function rewardsOwed(items: readonly SetupItemDef[], done: ReadonlySet<string>, earned: ReadonlySet<string>): SetupReward[];
|
|
139
|
+
export interface ItemProgress {
|
|
140
|
+
/** Done, plus anything `ready` counts as good as done. */
|
|
141
|
+
progressed: number;
|
|
142
|
+
total: number;
|
|
143
|
+
percent: number;
|
|
144
|
+
/** The first step neither done nor ready. */
|
|
145
|
+
nextKey: string | null;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* How far through one item is.
|
|
149
|
+
*
|
|
150
|
+
* `ready` is for a flow that saves nothing until its last step: its choices are
|
|
151
|
+
* made but nothing exists yet, so they tick without being done. They count
|
|
152
|
+
* towards the bar — the point of the bar is to show the end approaching — but
|
|
153
|
+
* never towards what has been earned.
|
|
154
|
+
*/
|
|
155
|
+
export declare function itemProgress(item: GuideItem, ready?: ReadonlySet<string>): ItemProgress;
|
|
156
|
+
/** How a step reads: done, ready to be paid, the one to do next, or still to do. */
|
|
157
|
+
export type StepState = "done" | "ready" | "next" | "todo";
|
|
158
|
+
export declare function stepState(step: GuideStep, nextKey: string | null, ready?: ReadonlySet<string>): StepState;
|
|
159
|
+
//# sourceMappingURL=guide.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guide.d.ts","sourceRoot":"","sources":["../../src/setup-guide/guide.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a new account is asked to do, how far through it is, and what that has
|
|
3
|
+
* earned. No React and no DOM, so the rules are tested without a browser and
|
|
4
|
+
* hold whatever draws them.
|
|
5
|
+
*
|
|
6
|
+
* **Nothing about progress is stored.** A step is *done* when the account holds
|
|
7
|
+
* what it asks for — a learner, a price, a published course — however it got
|
|
8
|
+
* there; the host reads that fresh each time. What *is* stored is the reward:
|
|
9
|
+
* one entry per step per account, keyed on the step, so a step pays once
|
|
10
|
+
* however many times its thing is made, deleted and made again. That split is
|
|
11
|
+
* the whole design, and it is why `done` and `earned` arrive here as two
|
|
12
|
+
* separate sets rather than one status per step.
|
|
13
|
+
*
|
|
14
|
+
* A step key is permanent. Rewards are keyed on it, so renaming one pays
|
|
15
|
+
* everybody a second time.
|
|
16
|
+
*/
|
|
17
|
+
const rewardOf = (step) => step.reward ?? 0;
|
|
18
|
+
const sum = (numbers) => numbers.reduce((total, n) => total + n, 0);
|
|
19
|
+
/** Every step of every item, in order. */
|
|
20
|
+
export function stepsOf(items) {
|
|
21
|
+
return items.flatMap((item) => [...item.steps]);
|
|
22
|
+
}
|
|
23
|
+
/** Every step key, for checking what a request names before trusting it. */
|
|
24
|
+
export function stepKeysOf(items) {
|
|
25
|
+
return new Set(stepsOf(items).map((step) => step.key));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Whether an item can be started yet.
|
|
29
|
+
*
|
|
30
|
+
* Only while nothing of it is done: an item that somehow got started stays
|
|
31
|
+
* open, because telling somebody they cannot do a thing they have half done is
|
|
32
|
+
* worse than a requirement quietly not applying.
|
|
33
|
+
*/
|
|
34
|
+
function blockedBy(item, done, doneCount) {
|
|
35
|
+
if (doneCount > 0 || !item.requires?.length)
|
|
36
|
+
return null;
|
|
37
|
+
const unmet = item.requires.some((key) => !done.has(key));
|
|
38
|
+
return unmet ? (item.blockedLabel ?? "Not yet") : null;
|
|
39
|
+
}
|
|
40
|
+
/** The guide as it stands for one account. Pure: the same inputs always give the same answer. */
|
|
41
|
+
export function buildGuide({ pathway, items, done, earned, hidden = false }) {
|
|
42
|
+
const built = items.map((def) => {
|
|
43
|
+
const steps = def.steps.map((step) => ({
|
|
44
|
+
...step,
|
|
45
|
+
reward: rewardOf(step),
|
|
46
|
+
done: done.has(step.key),
|
|
47
|
+
earned: earned.has(step.key),
|
|
48
|
+
}));
|
|
49
|
+
const doneCount = steps.filter((step) => step.done).length;
|
|
50
|
+
return {
|
|
51
|
+
...def,
|
|
52
|
+
steps,
|
|
53
|
+
reward: sum(steps.map((step) => step.reward)),
|
|
54
|
+
earnedReward: sum(steps.filter((step) => step.earned).map((step) => step.reward)),
|
|
55
|
+
doneCount,
|
|
56
|
+
// An item with no steps at all is not "done" — it has nothing to be done about.
|
|
57
|
+
done: steps.length > 0 && doneCount === steps.length,
|
|
58
|
+
blocked: blockedBy(def, done, doneCount),
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
const all = built.flatMap((item) => item.steps);
|
|
62
|
+
return {
|
|
63
|
+
pathway,
|
|
64
|
+
items: built,
|
|
65
|
+
totalReward: sum(all.map((step) => step.reward)),
|
|
66
|
+
earnedReward: sum(all.filter((step) => step.earned).map((step) => step.reward)),
|
|
67
|
+
doneItems: built.filter((item) => item.done).length,
|
|
68
|
+
nextKey: built.find((item) => !item.done && !item.blocked)?.key ?? null,
|
|
69
|
+
complete: all.length > 0 && all.every((step) => step.done && step.earned),
|
|
70
|
+
pendingKeys: all.filter((step) => step.done && !step.earned).map((step) => step.key),
|
|
71
|
+
hidden,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function findItem(guide, key) {
|
|
75
|
+
return guide?.items.find((item) => item.key === key) ?? null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* What counts as done: what the records show now, plus everything already paid
|
|
79
|
+
* for.
|
|
80
|
+
*
|
|
81
|
+
* A reward, once earned, is not taken back — so a step whose thing has since
|
|
82
|
+
* been deleted stays ticked rather than reopening and asking to be paid a
|
|
83
|
+
* second time. Hosts pass the result of this as `done`.
|
|
84
|
+
*/
|
|
85
|
+
export function doneSteps(facts, earned) {
|
|
86
|
+
return new Set([...facts, ...earned]);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The steps this account has done and not been paid for — what a sync owes it.
|
|
90
|
+
*
|
|
91
|
+
* Kept here, away from the database, because *which* steps are owed is a rule
|
|
92
|
+
* and writing the entries is a transaction. The host loops over this and writes
|
|
93
|
+
* one row each, letting its unique index refuse a second payment: two tabs
|
|
94
|
+
* asking at the same moment both get this list, and only one of them gets to
|
|
95
|
+
* write each row.
|
|
96
|
+
*/
|
|
97
|
+
export function rewardsOwed(items, done, earned) {
|
|
98
|
+
return stepsOf(items)
|
|
99
|
+
.filter((step) => done.has(step.key) && !earned.has(step.key) && rewardOf(step) > 0)
|
|
100
|
+
.map((step) => ({ key: step.key, title: step.title, reward: rewardOf(step) }));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* How far through one item is.
|
|
104
|
+
*
|
|
105
|
+
* `ready` is for a flow that saves nothing until its last step: its choices are
|
|
106
|
+
* made but nothing exists yet, so they tick without being done. They count
|
|
107
|
+
* towards the bar — the point of the bar is to show the end approaching — but
|
|
108
|
+
* never towards what has been earned.
|
|
109
|
+
*/
|
|
110
|
+
export function itemProgress(item, ready) {
|
|
111
|
+
const isReady = (step) => !step.done && Boolean(ready?.has(step.key));
|
|
112
|
+
const progressed = item.steps.filter((step) => step.done || isReady(step)).length;
|
|
113
|
+
const total = item.steps.length;
|
|
114
|
+
return {
|
|
115
|
+
progressed,
|
|
116
|
+
total,
|
|
117
|
+
percent: total === 0 ? 0 : Math.round((progressed / total) * 100),
|
|
118
|
+
nextKey: item.steps.find((step) => !step.done && !isReady(step))?.key ?? null,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export function stepState(step, nextKey, ready) {
|
|
122
|
+
if (step.done)
|
|
123
|
+
return "done";
|
|
124
|
+
if (ready?.has(step.key))
|
|
125
|
+
return "ready";
|
|
126
|
+
return step.key === nextKey ? "next" : "todo";
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=guide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guide.js","sourceRoot":"","sources":["../../src/setup-guide/guide.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@connextar/house/setup-guide` — the checklist a new account works through,
|
|
3
|
+
* and what finishing a step earns.
|
|
4
|
+
*
|
|
5
|
+
* It exists because the hard parts are not the checklist. They are: knowing
|
|
6
|
+
* what is done without storing it (so a thing made any other way still counts),
|
|
7
|
+
* paying for it exactly once however many tabs ask at the same moment, and
|
|
8
|
+
* saying so while the person is still looking at what they just did. Those are
|
|
9
|
+
* the same three problems in every app, and they have one right answer each.
|
|
10
|
+
*
|
|
11
|
+
* What the host supplies is everything that is genuinely its own: the steps,
|
|
12
|
+
* how each one is read from its records, where the reward is written, and what
|
|
13
|
+
* a reward is even called. This package holds no routes, makes no requests,
|
|
14
|
+
* imports no toast library and knows nothing about a database.
|
|
15
|
+
*
|
|
16
|
+
* The three surfaces are a card for a home page, a strip for the screen where
|
|
17
|
+
* an item's steps are actually done, and a launcher for the header. The panel
|
|
18
|
+
* the launcher opens is the app's own sheet — see `launcher.tsx`.
|
|
19
|
+
*/
|
|
20
|
+
export { buildGuide, doneSteps, findItem, itemProgress, rewardsOwed, stepKeysOf, stepState, stepsOf, type BuildGuideInput, type GuideItem, type GuideStep, type ItemProgress, type SetupGuide, type SetupGuideLink, type SetupHowTo, type SetupItemDef, type SetupReward, type SetupStepDef, type StepState, } from "./guide.js";
|
|
21
|
+
export { DEFAULT_REWARD_WORDS, rewardWord, SetupGuideProvider, useSetupGuide, type RewardWords, type SetupGuideProviderProps, type SetupGuideState, type SetupGuideTransport, type SetupLinkComponent, type SetupLinkProps, type SetupSyncResult, } from "./context.js";
|
|
22
|
+
export { SETUP_BAND_STYLE, SETUP_GUIDE_CLASS_NAMES, type SetupGuideClassNames } from "./class-names.js";
|
|
23
|
+
export { GuideIconLink, HowText, HowToButton, ItemHelp, MiniProgress, ProgressRing, RewardChip, RewardRing, Segments, StepCircle, useClassNames, type ClassNameOverrides, type RewardState, } from "./bits.js";
|
|
24
|
+
export { SetupCard, type SetupActions, type SetupCardProps } from "./card.js";
|
|
25
|
+
export { SetupStrip, type SetupStripProps } from "./strip.js";
|
|
26
|
+
export { SetupGuideList, SetupGuideSummary, SetupLauncherButton } from "./launcher.js";
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/setup-guide/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EACL,UAAU,EACV,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,UAAU,EACV,SAAS,EACT,OAAO,EACP,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,SAAS,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,oBAAoB,EACpB,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExG,OAAO,EACL,aAAa,EACb,OAAO,EACP,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,UAAU,EACV,QAAQ,EACR,UAAU,EACV,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,WAAW,GACjB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,SAAS,EAAE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
|