@artstorefronts/arthelper-nav 0.1.0-alpha.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 (51) hide show
  1. package/README.md +266 -0
  2. package/dist/config.d.ts +23 -0
  3. package/dist/config.d.ts.map +1 -0
  4. package/dist/config.js +134 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/context.d.ts +21 -0
  7. package/dist/context.d.ts.map +1 -0
  8. package/dist/context.js +12 -0
  9. package/dist/context.js.map +1 -0
  10. package/dist/cookie.d.ts +13 -0
  11. package/dist/cookie.d.ts.map +1 -0
  12. package/dist/cookie.js +28 -0
  13. package/dist/cookie.js.map +1 -0
  14. package/dist/fallback.d.ts +13 -0
  15. package/dist/fallback.d.ts.map +1 -0
  16. package/dist/fallback.js +17 -0
  17. package/dist/fallback.js.map +1 -0
  18. package/dist/icons.d.ts +14 -0
  19. package/dist/icons.d.ts.map +1 -0
  20. package/dist/icons.js +29 -0
  21. package/dist/icons.js.map +1 -0
  22. package/dist/index.css +509 -0
  23. package/dist/index.d.ts +13 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +8 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/nav-bar.d.ts +13 -0
  28. package/dist/nav-bar.d.ts.map +1 -0
  29. package/dist/nav-bar.js +91 -0
  30. package/dist/nav-bar.js.map +1 -0
  31. package/dist/nav-trigger.d.ts +16 -0
  32. package/dist/nav-trigger.d.ts.map +1 -0
  33. package/dist/nav-trigger.js +21 -0
  34. package/dist/nav-trigger.js.map +1 -0
  35. package/dist/provider.d.ts +19 -0
  36. package/dist/provider.d.ts.map +1 -0
  37. package/dist/provider.js +46 -0
  38. package/dist/provider.js.map +1 -0
  39. package/dist/resolve.d.ts +32 -0
  40. package/dist/resolve.d.ts.map +1 -0
  41. package/dist/resolve.js +51 -0
  42. package/dist/resolve.js.map +1 -0
  43. package/dist/theme.d.ts +13 -0
  44. package/dist/theme.d.ts.map +1 -0
  45. package/dist/theme.js +21 -0
  46. package/dist/theme.js.map +1 -0
  47. package/dist/types.d.ts +50 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +10 -0
  50. package/dist/types.js.map +1 -0
  51. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,266 @@
1
+ # @artstorefronts/arthelper-nav
2
+
3
+ The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher for the
4
+ satellite apps in the ArtHelper suite.
5
+
6
+ This document is for an engineer wiring the bar into a host app that lives **outside**
7
+ this monorepo. If you're working inside this repo, see `docs/llms/suite-nav.md` for how
8
+ the pieces fit together end to end.
9
+
10
+ ## Architecture in one line
11
+
12
+ Configure in data, render in code. The bar's content — theme, logo, and the app catalog
13
+ — lives in a single database row, served as JSON over a public HTTP endpoint. Every
14
+ mounted host fetches that JSON on load. An edit made in the admin editor is live for
15
+ every app in the suite on their next page load — no deploy, no version bump, no
16
+ coordinated release across repos.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ npm install @artstorefronts/arthelper-nav
22
+ ```
23
+
24
+ Peer dependencies (from `package.json` — install these yourself, they are not bundled):
25
+
26
+ ```json
27
+ {
28
+ "lucide-react": ">=0.446.0",
29
+ "react": "^18.0.0 || ^19.0.0",
30
+ "react-dom": "^18.0.0 || ^19.0.0"
31
+ }
32
+ ```
33
+
34
+ > **Not yet on npm.** Publishing is automatic: `.github/workflows/release-nav.yml` runs on
35
+ > any push to `develop` that touches `packages/nav`, and publishes when the version in
36
+ > `packages/nav/package.json` is not already on the registry. There is no tag and no
37
+ > manual release step — **bumping that version in your PR is what ships the package**, and
38
+ > a PR that changes the published surface without bumping it fails CI.
39
+ >
40
+ > The `@artstorefronts` scope already exists (it publishes `@artstorefronts/ui` and
41
+ > `@artstorefronts/cli`). What is still missing is a **trusted publisher** configured on
42
+ > npm for this package, pointing at this repository and the workflow filename
43
+ > `release-nav.yml`. Authentication is OIDC, so there is no token to create or rotate.
44
+ > Until that is configured, `npm install @artstorefronts/arthelper-nav` has nothing to
45
+ > resolve.
46
+ >
47
+ > To develop against it before the first publish, use a workspace link or a path
48
+ > dependency from the satellite repo:
49
+ >
50
+ > ```bash
51
+ > cd packages/nav && yarn link
52
+ > cd ../../../<satellite> && yarn link @artstorefronts/arthelper-nav
53
+ > ```
54
+ >
55
+ > `react` and `react-dom` are peer dependencies, so make sure the satellite's bundler
56
+ > resolves a single copy of React — a linked package pulling in its own is the usual
57
+ > cause of the duplicate-React hooks error.
58
+
59
+ ## Minimal mounting example
60
+
61
+ ```tsx
62
+ // Import the stylesheet by its deep path — it is not re-exported from the package root.
63
+ import '@artstorefronts/arthelper-nav/dist/index.css';
64
+ import { NavProvider, NavBar, NavTrigger } from '@artstorefronts/arthelper-nav';
65
+
66
+ function AppRoot() {
67
+ return (
68
+ <NavProvider activeAppId="my-app" configUrl="https://api.arthelper.com/rest/nav-config">
69
+ {/* NavBar first, so it occupies real layout space at the top of the shell. */}
70
+ <NavBar />
71
+ <AppHeader />
72
+ <AppContent />
73
+ </NavProvider>
74
+ );
75
+ }
76
+
77
+ function AppHeader() {
78
+ return (
79
+ <header>
80
+ <Logo />
81
+ {/* NavTrigger is the RESTORE control. Omit it and a viewer who collapses the
82
+ bar has no way to bring it back. */}
83
+ <NavTrigger />
84
+ </header>
85
+ );
86
+ }
87
+ ```
88
+
89
+ `NavProvider` renders no DOM of its own — `NavBar` and whatever else you nest inside it
90
+ stay direct children of your own layout, so the 56px the bar reserves comes purely from
91
+ `NavBar` itself, not from a wrapper.
92
+
93
+ ## Components and props
94
+
95
+ ### `NavProvider`
96
+
97
+ The context host. Fetches config, resolves the app list, and owns collapsed state. Must
98
+ wrap `NavBar`, `NavTrigger`, and any component calling `useNav()`.
99
+
100
+ | Prop | Type | Required | Description |
101
+ | ------------------ | --------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
102
+ | `configUrl` | `string` | yes | Full URL of the public nav-config endpoint. See [Deriving `configUrl`](#deriving-configurl) below. |
103
+ | `activeAppId` | `string` | yes | The catalog id of the app the host itself is. Never inferred from the URL or the payload — the host always declares it. |
104
+ | `onAppClick` | `(args: NavAppClickArgs) => void` | no | Fired immediately before navigation. The package's only analytics seam — see [`onAppClick`](#onappclick-the-analytics-seam). |
105
+ | `fallbackConfig` | `NavConfigPayload` | no | Overrides the package's compiled-in fallback, e.g. for a satellite with a different first-party destination set. |
106
+ | `initialCollapsed` | `boolean` | no | The collapsed cookie value a server-rendered host already read, so the first byte paints the correct state. See [SSR and the collapsed cookie](#ssr-and-the-collapsed-cookie). |
107
+ | `children` | `ReactNode` | no | — |
108
+
109
+ ### `NavBar`
110
+
111
+ Renders the bar itself: waffle toggle, wordmark/logo, the desktop tile row (or, below the
112
+ 768px breakpoint, a labelled dropdown trigger and panel with the same entries as rows).
113
+
114
+ | Prop | Type | Default | Description |
115
+ | ----------- | --------- | ------- | -------------------------------------------------------------------------------------- |
116
+ | `sticky` | `boolean` | `false` | Opt in only for a host whose document actually scrolls. See [`sticky`](#sticky) below. |
117
+ | `className` | `string` | — | Appended to the bar's root class list. |
118
+
119
+ ### `NavTrigger`
120
+
121
+ The docked waffle-plus-active-app chip a host places in its own header, next to its own
122
+ logo. This is the **restore** control: while the bar is collapsed (`NavBar` reserves
123
+ nothing and is out of the accessibility tree), `NavTrigger` is the only way back. It is
124
+ `display: none` while the bar is expanded. A host that mounts `NavBar` without also
125
+ mounting `NavTrigger` ships a bar a viewer can collapse and never recover.
126
+
127
+ | Prop | Type | Default | Description |
128
+ | ----------- | -------- | ------- | ------------------------------------------ |
129
+ | `className` | `string` | — | Appended to the trigger's root class list. |
130
+
131
+ ### Exported types
132
+
133
+ `NavContextValue`, `NavBarProps`, `NavTriggerProps`, `NavProviderProps`, `NavAlignment`,
134
+ `NavApp`, `NavAppClickArgs`, `NavConfigPayload`, `NavLogo`, `NavTheme`, `NavThemeMode`,
135
+ `ResolvedNavApp` — all exported from the package root (`src/index.ts`), also useful if
136
+ you're typing `fallbackConfig` or `onAppClick` yourself.
137
+
138
+ `useNav()` is also exported, for a component nested under `NavProvider` that needs
139
+ `collapsed`, `toggle`, `activeApp`, or `apps` directly — the two host mounts in this
140
+ monorepo use it, for example, to publish the bar's live height as a CSS variable.
141
+
142
+ ## `activeAppId`
143
+
144
+ The host is always the one source of truth for which app it is. `NavProvider` never
145
+ infers this from the current URL and never reads it from the config payload — you pass
146
+ it explicitly, and it must match one of the `id`s in the nav-config app catalog (ask the
147
+ ArtHelper team for your app's id, or check the payload at `GET /rest/nav-config`).
148
+
149
+ That entry is always rendered and indicated (its tile carries the active styling) even if
150
+ the payload has it disabled or omits a destination for it. If your own app has no `href`
151
+ in the catalog yet, its tile renders as an inert indicator instead of a link — it never
152
+ disappears from the bar.
153
+
154
+ ## Deriving `configUrl`
155
+
156
+ No new environment variable is needed. Derive it from whatever ArtHelper API base URL
157
+ your app already has, by swapping the `graphql` path segment for `rest` and appending
158
+ `/nav-config`:
159
+
160
+ ```ts
161
+ const NAV_CONFIG_URL = `${ARTHELPER_API_BASE.replace('graphql', 'rest')}/nav-config`;
162
+ // e.g. https://api.arthelper.com/graphql -> https://api.arthelper.com/rest/nav-config
163
+ ```
164
+
165
+ This is the same derivation both in-monorepo hosts (`client/`, `town-square/`) use.
166
+
167
+ ## `onAppClick`: the analytics seam
168
+
169
+ The package imports no analytics library of any kind. `onAppClick` is the one hook
170
+ through which a host wires its own analytics — it fires immediately before navigation,
171
+ from **both** the desktop tile row and the mobile dropdown, so one callback covers both
172
+ surfaces:
173
+
174
+ ```ts
175
+ <NavProvider
176
+ activeAppId="my-app"
177
+ configUrl={NAV_CONFIG_URL}
178
+ onAppClick={({ app, activeAppId, collapsed }) => {
179
+ myAnalytics.track('suite_nav_app_clicked', { targetAppId: app.id, activeAppId, collapsed });
180
+ }}
181
+ >
182
+ ```
183
+
184
+ Navigation itself happens via a real `<a href>` the browser follows natively —
185
+ `onAppClick` never intercepts or prevents it.
186
+
187
+ ## SSR and the collapsed cookie
188
+
189
+ The collapsed flag lives in a **cookie** (`arthelper_suite_nav_collapsed`), not
190
+ `localStorage`. That's deliberate: a server-rendered host can read the cookie during SSR
191
+ and pass its value in as `initialCollapsed`, so the very first byte the browser paints
192
+ already has the bar in the right state — no expanded-then-collapsed jump after
193
+ hydration.
194
+
195
+ ```tsx
196
+ // Next.js App Router example
197
+ import { cookies } from 'next/headers';
198
+ import { COLLAPSED_COOKIE } from '@artstorefronts/arthelper-nav';
199
+
200
+ export default async function RootLayout({ children }: { children: React.ReactNode }) {
201
+ const cookieStore = await cookies();
202
+ const initialCollapsed = cookieStore.get(COLLAPSED_COOKIE)?.value === 'true';
203
+
204
+ return (
205
+ <NavProvider activeAppId="my-app" configUrl={NAV_CONFIG_URL} initialCollapsed={initialCollapsed}>
206
+ {children}
207
+ </NavProvider>
208
+ );
209
+ }
210
+ ```
211
+
212
+ The cookie is **per-origin**. `app.example.com` and `www.example.com` are different
213
+ registrable domains and share no cookie scope, so collapsing the bar on one origin does
214
+ not sync to another — each origin's viewer sets their own collapsed state independently.
215
+
216
+ ## Theming
217
+
218
+ `theme.mode` (`'dark' | 'light'`), `theme.accent_color`, and `theme.alignment`
219
+ (`'left' | 'centered'`) all come from the config payload, set by an admin in the nav
220
+ editor. Changing any of them is a config edit, not a package release or a host
221
+ deployment — every mounted app picks it up on its next fetch.
222
+
223
+ ## The layout contract
224
+
225
+ `NavBar` reserves exactly 56px from first paint, using the package's compiled-in
226
+ fallback config while the real payload is in flight — so config arrival never shifts the
227
+ page. Collapsing the bar (`toggle()`, or the waffle button, or `NavTrigger`) removes it
228
+ from layout **entirely**: height goes to 0 and the 56px is handed back to the page, not
229
+ just visually hidden.
230
+
231
+ ## `sticky`
232
+
233
+ Pass `sticky` only when your host's own document actually scrolls (e.g. a normal page
234
+ layout) — in that case the bar should stick to the top of the viewport as the page
235
+ scrolls beneath it. Leave it `false` (the default) for a host that is a fixed-height flex
236
+ column whose document itself never scrolls (only an inner pane does) — sticky
237
+ positioning there is dead weight.
238
+
239
+ If your host is `sticky` and also has its own sticky header below the bar, that header's
240
+ top offset has to account for the bar's height — **and that offset has to react to
241
+ collapse**, or a viewer who collapses the bar is left with a 56px void at the top of the
242
+ page where your header no longer starts. Read `collapsed` from `useNav()` and publish the
243
+ live height (0 collapsed, 56px expanded) as a CSS variable your header's `top` reads,
244
+ rather than hardcoding 56px once.
245
+
246
+ ## Degradation
247
+
248
+ The bar is designed to never render blank and never block first paint:
249
+
250
+ - **Endpoint outage or timeout** — the fetch has a hard 2.5s timeout; on any failure
251
+ (network error, timeout, non-2xx, unparseable body) the bar falls back to its
252
+ compiled-in config and keeps whatever it was already showing. No visible error, no
253
+ console noise by default.
254
+ - **Unknown fields** — anything in the payload the renderer doesn't recognize is dropped
255
+ during normalization, never carried through and never thrown on.
256
+ - **A higher `schema_version` than this renderer understands** — ignored the same way;
257
+ the renderer keeps working on the fields it does know.
258
+
259
+ ## The `schema_version` contract
260
+
261
+ Changes to the wire payload are **additive within a version**: fields are only ever
262
+ added, never removed or repurposed, for a given `schema_version`. A renderer must ignore
263
+ fields it doesn't recognize rather than throw. That's what lets the suite's
264
+ asynchronously-deployed satellite fleet run mixed renderer versions against one shared
265
+ payload — an older `@artstorefronts/arthelper-nav` keeps working after a newer field ships on the
266
+ config row. The version number itself is bumped only for a genuinely breaking change.
@@ -0,0 +1,23 @@
1
+ import type { NavConfigPayload } from './types.js';
2
+ /**
3
+ * Coerces an arbitrary JSON body into the payload shape. Never throws: a missing,
4
+ * malformed or newer-versioned field falls back to the embedded default so the
5
+ * bar degrades instead of blanking.
6
+ *
7
+ * `null` is reserved for a body that could not be used, because that is the signal
8
+ * the caller reads as "keep showing what you have". An empty `apps` array is NOT such
9
+ * a body: it is what the endpoint serves when an operator has disabled every
10
+ * destination, and treating it as a failure discarded the operator's `theme` and
11
+ * `logo` edits along with it and reverted every host to the compiled-in fallback —
12
+ * the exact inverse of the instruction.
13
+ */
14
+ declare function normalize(raw: unknown): NavConfigPayload | null;
15
+ declare function cached(url: string): NavConfigPayload | undefined;
16
+ /**
17
+ * Fetches the config with a hard timeout. Resolves to null on any failure —
18
+ * network, timeout, non-2xx, or an unparseable body — and the caller keeps
19
+ * whatever it is already showing.
20
+ */
21
+ declare function fetchConfig(url: string, timeoutMs?: number): Promise<NavConfigPayload | null>;
22
+ export { cached, fetchConfig, normalize };
23
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAwB,gBAAgB,EAAgB,MAAM,YAAY,CAAC;AAsEvF;;;;;;;;;;;GAWG;AACH,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAuBxD;AAED,iBAAS,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEzD;AAED;;;;GAIG;AACH,iBAAe,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,GAAE,MAAmB,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA0BxG;AAED,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,134 @@
1
+ import { FALLBACK_CONFIG } from './fallback.js';
2
+ /** Short enough that a slow endpoint never delays the bar past first paint. */
3
+ const TIMEOUT_MS = 2500;
4
+ /** Last good payload per URL, so a remount paints the fresh config, not the fallback. */
5
+ const memo = new Map();
6
+ function isRecord(value) {
7
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
8
+ }
9
+ function text(value, fallback) {
10
+ return typeof value === 'string' ? value : fallback;
11
+ }
12
+ function mode(value) {
13
+ return value === 'light' ? 'light' : 'dark';
14
+ }
15
+ function alignment(value) {
16
+ return value === 'centered' ? 'centered' : 'left';
17
+ }
18
+ function accent(value) {
19
+ return typeof value === 'string' && value.trim().length > 0 ? value : null;
20
+ }
21
+ // Unknown fields are dropped rather than carried, so a newer payload can never
22
+ // reach the renderer as a shape it does not understand.
23
+ function app(raw) {
24
+ if (!isRecord(raw)) {
25
+ return null;
26
+ }
27
+ if (typeof raw.id !== 'string' || raw.id.length === 0) {
28
+ return null;
29
+ }
30
+ return {
31
+ id: raw.id,
32
+ label: text(raw.label, raw.id),
33
+ icon: text(raw.icon, ''),
34
+ href: text(raw.href, ''),
35
+ enabled: raw.enabled === true,
36
+ };
37
+ }
38
+ /**
39
+ * Parses the catalog, keeping an EMPTY-BUT-VALID list distinguishable from an
40
+ * unusable body. `[]` is a legitimate instruction — an operator who disabled every
41
+ * destination — and must be honoured; `null` means the body could not be read at all
42
+ * and the caller should keep whatever it is already showing.
43
+ *
44
+ * A non-empty array from which not one entry parsed counts as unusable, not as empty:
45
+ * the operator sent apps, we understood none of them, and rendering an empty bar off
46
+ * that is guessing.
47
+ */
48
+ function apps(raw) {
49
+ if (!Array.isArray(raw)) {
50
+ return null;
51
+ }
52
+ const parsed = raw.reduce((memoized, entry) => {
53
+ const item = app(entry);
54
+ return item ? [...memoized, item] : memoized;
55
+ }, []);
56
+ if (raw.length > 0 && parsed.length === 0) {
57
+ return null;
58
+ }
59
+ return parsed;
60
+ }
61
+ /**
62
+ * Coerces an arbitrary JSON body into the payload shape. Never throws: a missing,
63
+ * malformed or newer-versioned field falls back to the embedded default so the
64
+ * bar degrades instead of blanking.
65
+ *
66
+ * `null` is reserved for a body that could not be used, because that is the signal
67
+ * the caller reads as "keep showing what you have". An empty `apps` array is NOT such
68
+ * a body: it is what the endpoint serves when an operator has disabled every
69
+ * destination, and treating it as a failure discarded the operator's `theme` and
70
+ * `logo` edits along with it and reverted every host to the compiled-in fallback —
71
+ * the exact inverse of the instruction.
72
+ */
73
+ function normalize(raw) {
74
+ if (!isRecord(raw)) {
75
+ return null;
76
+ }
77
+ const theme = isRecord(raw.theme) ? raw.theme : {};
78
+ const logo = isRecord(raw.logo) ? raw.logo : {};
79
+ const parsed = apps(raw.apps);
80
+ if (!parsed) {
81
+ return null;
82
+ }
83
+ return {
84
+ schema_version: typeof raw.schema_version === 'number' ? raw.schema_version : FALLBACK_CONFIG.schema_version,
85
+ theme: {
86
+ mode: mode(theme.mode),
87
+ accent_color: accent(theme.accent_color),
88
+ alignment: alignment(theme.alignment),
89
+ },
90
+ logo: {
91
+ label: text(logo.label, FALLBACK_CONFIG.logo.label),
92
+ href: text(logo.href, FALLBACK_CONFIG.logo.href),
93
+ },
94
+ apps: parsed,
95
+ };
96
+ }
97
+ function cached(url) {
98
+ return memo.get(url);
99
+ }
100
+ /**
101
+ * Fetches the config with a hard timeout. Resolves to null on any failure —
102
+ * network, timeout, non-2xx, or an unparseable body — and the caller keeps
103
+ * whatever it is already showing.
104
+ */
105
+ async function fetchConfig(url, timeoutMs = TIMEOUT_MS) {
106
+ const controller = new AbortController();
107
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
108
+ try {
109
+ // NEVER set credentials to 'include' or 'same-origin'. The endpoint is served
110
+ // through a bare cors() and returns the LITERAL `Access-Control-Allow-Origin: *`,
111
+ // not a reflected origin. `*` and credentials are mutually exclusive in the
112
+ // browser, so credentialed requests would have every cross-origin response
113
+ // rejected — stranding the bar on the embedded fallback forever, silently, with
114
+ // no console error and no visible failure. 'omit' is the only safe value.
115
+ const response = await fetch(url, { signal: controller.signal, credentials: 'omit' });
116
+ if (!response.ok) {
117
+ return null;
118
+ }
119
+ const next = normalize(await response.json());
120
+ if (!next) {
121
+ return null;
122
+ }
123
+ memo.set(url, next);
124
+ return next;
125
+ }
126
+ catch {
127
+ return null;
128
+ }
129
+ finally {
130
+ clearTimeout(timer);
131
+ }
132
+ }
133
+ export { cached, fetchConfig, normalize };
134
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { NavConfigPayload, ResolvedNavApp } from './types.js';
2
+ interface NavContextValue {
3
+ /** The config currently being rendered — the embedded fallback until the fetch lands. */
4
+ config: NavConfigPayload;
5
+ /** The catalog id of the app the host itself is. Always supplied by the host. */
6
+ activeAppId: string;
7
+ /** Enabled-and-has-destination entries, plus the host's own app. */
8
+ apps: ResolvedNavApp[];
9
+ /** The host's own entry, always present unless the catalog has never heard of it. */
10
+ activeApp: ResolvedNavApp | undefined;
11
+ collapsed: boolean;
12
+ setCollapsed: (collapsed: boolean) => void;
13
+ toggle: () => void;
14
+ /** Fires the host's onAppClick seam. Navigation itself is the anchor's own. */
15
+ activate: (app: ResolvedNavApp) => void;
16
+ }
17
+ declare const NavContext: import("react").Context<NavContextValue | null>;
18
+ declare function useNav(): NavContextValue;
19
+ export { NavContext, useNav };
20
+ export type { NavContextValue };
21
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEnE,UAAU,eAAe;IACvB,yFAAyF;IACzF,MAAM,EAAE,gBAAgB,CAAC;IACzB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,qFAAqF;IACrF,SAAS,EAAE,cAAc,GAAG,SAAS,CAAC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,+EAA+E;IAC/E,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAC;CACzC;AAED,QAAA,MAAM,UAAU,iDAA8C,CAAC;AAE/D,iBAAS,MAAM,IAAI,eAAe,CAMjC;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AAC9B,YAAY,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+ import { createContext, useContext } from 'react';
3
+ const NavContext = createContext(null);
4
+ function useNav() {
5
+ const value = useContext(NavContext);
6
+ if (!value) {
7
+ throw new Error('useNav must be used inside <NavProvider>');
8
+ }
9
+ return value;
10
+ }
11
+ export { NavContext, useNav };
12
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAmBlD,MAAM,UAAU,GAAG,aAAa,CAAyB,IAAI,CAAC,CAAC;AAE/D,SAAS,MAAM;IACb,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The collapsed flag lives in a cookie, not localStorage, so a server-rendered
3
+ * host can read it and paint the first byte already collapsed — no expanded ->
4
+ * collapsed 56px jump after hydration.
5
+ *
6
+ * Per-origin by design: app.arthelper.ai and www.arthelper.com are different
7
+ * registrable domains and share no cookie scope. There is no cross-origin sync.
8
+ */
9
+ declare const COLLAPSED_COOKIE = "arthelper_suite_nav_collapsed";
10
+ declare function readCollapsed(): boolean | null;
11
+ declare function writeCollapsed(collapsed: boolean): void;
12
+ export { COLLAPSED_COOKIE, readCollapsed, writeCollapsed };
13
+ //# sourceMappingURL=cookie.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookie.d.ts","sourceRoot":"","sources":["../src/cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,QAAA,MAAM,gBAAgB,kCAAkC,CAAC;AAIzD,iBAAS,aAAa,IAAI,OAAO,GAAG,IAAI,CASvC;AAED,iBAAS,cAAc,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAKhD;AAED,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC"}
package/dist/cookie.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The collapsed flag lives in a cookie, not localStorage, so a server-rendered
3
+ * host can read it and paint the first byte already collapsed — no expanded ->
4
+ * collapsed 56px jump after hydration.
5
+ *
6
+ * Per-origin by design: app.arthelper.ai and www.arthelper.com are different
7
+ * registrable domains and share no cookie scope. There is no cross-origin sync.
8
+ */
9
+ const COLLAPSED_COOKIE = 'arthelper_suite_nav_collapsed';
10
+ const ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
11
+ function readCollapsed() {
12
+ if (typeof document === 'undefined') {
13
+ return null;
14
+ }
15
+ const match = document.cookie.split('; ').find((entry) => entry.startsWith(`${COLLAPSED_COOKIE}=`));
16
+ if (!match) {
17
+ return null;
18
+ }
19
+ return match.slice(COLLAPSED_COOKIE.length + 1) === 'true';
20
+ }
21
+ function writeCollapsed(collapsed) {
22
+ if (typeof document === 'undefined') {
23
+ return;
24
+ }
25
+ document.cookie = `${COLLAPSED_COOKIE}=${collapsed}; path=/; max-age=${ONE_YEAR_SECONDS}; SameSite=Lax`;
26
+ }
27
+ export { COLLAPSED_COOKIE, readCollapsed, writeCollapsed };
28
+ //# sourceMappingURL=cookie.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookie.js","sourceRoot":"","sources":["../src/cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,gBAAgB,GAAG,+BAA+B,CAAC;AAEzD,MAAM,gBAAgB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC;AAE5C,SAAS,aAAa;IACpB,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,gBAAgB,GAAG,CAAC,CAAC,CAAC;IACpG,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC;AAC7D,CAAC;AAED,SAAS,cAAc,CAAC,SAAkB;IACxC,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,OAAO;IACT,CAAC;IACD,QAAQ,CAAC,MAAM,GAAG,GAAG,gBAAgB,IAAI,SAAS,qBAAqB,gBAAgB,gBAAgB,CAAC;AAC1G,CAAC;AAED,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { NavConfigPayload } from './types.js';
2
+ /**
3
+ * The static config compiled into the package. Used whenever the fetch fails,
4
+ * times out, or has not resolved yet, so the bar reserves its final 56px from
5
+ * first paint and never renders blank.
6
+ *
7
+ * It carries ONLY entries that have a destination. Changing it requires a
8
+ * package release, which is exactly why it holds the stable first-party
9
+ * destination and never the satellites — those arrive over the wire.
10
+ */
11
+ declare const FALLBACK_CONFIG: NavConfigPayload;
12
+ export { FALLBACK_CONFIG };
13
+ //# sourceMappingURL=fallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallback.d.ts","sourceRoot":"","sources":["../src/fallback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;;;;GAQG;AACH,QAAA,MAAM,eAAe,EAAE,gBAKtB,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The static config compiled into the package. Used whenever the fetch fails,
3
+ * times out, or has not resolved yet, so the bar reserves its final 56px from
4
+ * first paint and never renders blank.
5
+ *
6
+ * It carries ONLY entries that have a destination. Changing it requires a
7
+ * package release, which is exactly why it holds the stable first-party
8
+ * destination and never the satellites — those arrive over the wire.
9
+ */
10
+ const FALLBACK_CONFIG = {
11
+ schema_version: 1,
12
+ theme: { mode: 'dark', accent_color: '#2aa34f', alignment: 'left' },
13
+ logo: { label: 'ArtHelper', href: 'https://www.arthelper.com' },
14
+ apps: [{ id: 'home', label: 'Home', icon: 'house', href: 'https://www.arthelper.com', enabled: true }],
15
+ };
16
+ export { FALLBACK_CONFIG };
17
+ //# sourceMappingURL=fallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallback.js","sourceRoot":"","sources":["../src/fallback.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,eAAe,GAAqB;IACxC,cAAc,EAAE,CAAC;IACjB,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE;IACnE,IAAI,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,2BAA2B,EAAE;IAC/D,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,2BAA2B,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;CACvG,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,14 @@
1
+ interface NavIconProps {
2
+ name: string;
3
+ size?: number;
4
+ className?: string;
5
+ }
6
+ /** An unrecognised icon name degrades to a neutral circle rather than an empty chip. */
7
+ declare function NavIcon({ name, size, className }: NavIconProps): import("react/jsx-runtime").JSX.Element;
8
+ /** The waffle: hide control in the bar, restore affordance in NavTrigger. */
9
+ declare function WaffleIcon({ size, className }: {
10
+ size?: number;
11
+ className?: string;
12
+ }): import("react/jsx-runtime").JSX.Element;
13
+ export { NavIcon, WaffleIcon };
14
+ //# sourceMappingURL=icons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../src/icons.tsx"],"names":[],"mappings":"AA+BA,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wFAAwF;AACxF,iBAAS,OAAO,CAAC,EAAE,IAAI,EAAE,IAAS,EAAE,SAA0B,EAAE,EAAE,YAAY,2CAG7E;AAED,6EAA6E;AAC7E,iBAAS,UAAU,CAAC,EAAE,IAAS,EAAE,SAA0B,EAAE,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,2CAEnG;AAED,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC"}
package/dist/icons.js ADDED
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { BookOpen, Circle, Frame, House, LayoutGrid, Megaphone, Sparkles, Star, Sunrise, } from 'lucide-react';
4
+ /**
5
+ * The catalog's icon names, keyed exactly as the seed spells them. lucide emits
6
+ * inline stroke SVGs with viewBox="0 0 24 24", stroke-width 2 and round caps,
7
+ * which is the icon contract verbatim — nothing here draws its own paths.
8
+ */
9
+ const ICONS = {
10
+ house: House,
11
+ sunrise: Sunrise,
12
+ star: Star,
13
+ sparkles: Sparkles,
14
+ frame: Frame,
15
+ 'book-open': BookOpen,
16
+ megaphone: Megaphone,
17
+ 'layout-grid': LayoutGrid,
18
+ };
19
+ /** An unrecognised icon name degrades to a neutral circle rather than an empty chip. */
20
+ function NavIcon({ name, size = 16, className = 'ahsuite-icon' }) {
21
+ const Icon = ICONS[name] ?? Circle;
22
+ return _jsx(Icon, { "aria-hidden": "true", className: className, width: size, height: size, strokeWidth: 2 });
23
+ }
24
+ /** The waffle: hide control in the bar, restore affordance in NavTrigger. */
25
+ function WaffleIcon({ size = 24, className = 'ahsuite-icon' }) {
26
+ return _jsx(LayoutGrid, { "aria-hidden": "true", className: className, width: size, height: size, strokeWidth: 2 });
27
+ }
28
+ export { NavIcon, WaffleIcon };
29
+ //# sourceMappingURL=icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.js","sourceRoot":"","sources":["../src/icons.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EACL,QAAQ,EACR,MAAM,EACN,KAAK,EACL,KAAK,EACL,UAAU,EACV,SAAS,EACT,QAAQ,EACR,IAAI,EACJ,OAAO,GAER,MAAM,cAAc,CAAC;AAEtB;;;;GAIG;AACH,MAAM,KAAK,GAA+B;IACxC,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE,OAAO;IAChB,IAAI,EAAE,IAAI;IACV,QAAQ,EAAE,QAAQ;IAClB,KAAK,EAAE,KAAK;IACZ,WAAW,EAAE,QAAQ;IACrB,SAAS,EAAE,SAAS;IACpB,aAAa,EAAE,UAAU;CAC1B,CAAC;AAQF,wFAAwF;AACxF,SAAS,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,EAAE,SAAS,GAAG,cAAc,EAAgB;IAC5E,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC;IACnC,OAAO,KAAC,IAAI,mBAAa,MAAM,EAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,GAAI,CAAC;AACtG,CAAC;AAED,6EAA6E;AAC7E,SAAS,UAAU,CAAC,EAAE,IAAI,GAAG,EAAE,EAAE,SAAS,GAAG,cAAc,EAAyC;IAClG,OAAO,KAAC,UAAU,mBAAa,MAAM,EAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,GAAI,CAAC;AAC5G,CAAC;AAED,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC"}