@solutiondesk/changelog 0.0.0 → 1.0.0-2cf6d3e

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.
@@ -0,0 +1,340 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ /**
5
+ * The answers of the public changelog API (`GET /v1/changelog/{workspace}/…`).
6
+ * Written by hand so the package has no dependencies; a drift test in the
7
+ * SolutionDesk repository keeps them identical to the API's own contracts.
8
+ */
9
+ type ChangelogCategory = "new" | "improved" | "fixed" | "announcement";
10
+ type ChangelogWidgetPosition = "bottom-right" | "bottom-left";
11
+ /** A published entry. */
12
+ interface PublicChangelogEntry {
13
+ slug: string;
14
+ title: string;
15
+ /** One or two sentences — what a "What's new" panel shows. May be empty. */
16
+ summary: string;
17
+ category: ChangelogCategory;
18
+ /** The release this change ships in (`2.3.0`); empty when it isn't tied to one. */
19
+ releaseVersion: string;
20
+ /** Where the release can be downloaded (an app store page, GitHub Releases …), or null. */
21
+ downloadUrl: string | null;
22
+ /** ISO 8601. The list is sorted by it, newest first. */
23
+ publishedAt: string;
24
+ /** The details as the team wrote them (Markdown). */
25
+ markdown: string;
26
+ /**
27
+ * The details as safe HTML: raw HTML in the text is escaped, links are only
28
+ * http(s)/mailto/relative, images only the ones uploaded to the entry (with
29
+ * `srcset`). Ready for `innerHTML` / `dangerouslySetInnerHTML`. Empty when
30
+ * there are no details.
31
+ */
32
+ html: string;
33
+ /** The entry on the workspace's hosted changelog page. */
34
+ url: string;
35
+ }
36
+ /** A product with its own changelog. */
37
+ interface PublicChangelogProduct {
38
+ name: string;
39
+ slug: string;
40
+ /** One sentence about the product. May be empty. */
41
+ description: string;
42
+ /** The product's hosted changelog page. */
43
+ pageUrl: string;
44
+ /** The default product: addresses without a product show this one. */
45
+ isDefault: boolean;
46
+ }
47
+ /** `GET /v1/changelog/{workspace}/products` — every product, the default first. */
48
+ interface PublicChangelogProductsPayload {
49
+ workspace: {
50
+ name: string;
51
+ slug: string;
52
+ logoUrl: string | null;
53
+ };
54
+ products: PublicChangelogProduct[];
55
+ }
56
+ /**
57
+ * `GET /v1/changelog/{workspace}/{product}` — a product's settings and one
58
+ * page of its entries (`GET /v1/changelog/{workspace}`: the default product).
59
+ */
60
+ interface PublicChangelogPayload {
61
+ /** The workspace as the hosted page shows it; `logoUrl` is relative to the API. */
62
+ workspace: {
63
+ name: string;
64
+ slug: string;
65
+ logoUrl: string | null;
66
+ };
67
+ /** The product these entries belong to. */
68
+ product: PublicChangelogProduct;
69
+ /** The heading the team chose; empty ⇒ use your own ("What's new"). */
70
+ title: string;
71
+ /** The team's settings for the "What's new" button (empty label ⇒ your own). */
72
+ widget: {
73
+ label: string;
74
+ position: ChangelogWidgetPosition;
75
+ };
76
+ /** The product's hosted changelog page. */
77
+ pageUrl: string;
78
+ entries: PublicChangelogEntry[];
79
+ /** Pass as `cursor` for the next page; null on the last one. */
80
+ nextCursor: string | null;
81
+ }
82
+ /** `GET /v1/changelog/{workspace}/{product}/entries/{slug}`. */
83
+ interface PublicChangelogEntryPayload {
84
+ /** The product whose changelog shows the entry. */
85
+ product: PublicChangelogProduct;
86
+ entry: PublicChangelogEntry;
87
+ }
88
+
89
+ /** The response parts the SDK reads — what every `fetch` implementation returns. */
90
+ interface FetchResponseLike {
91
+ readonly status: number;
92
+ readonly ok: boolean;
93
+ readonly headers: {
94
+ get(name: string): string | null;
95
+ };
96
+ text(): Promise<string>;
97
+ }
98
+ /** What the SDK passes to `fetch` (plus whatever you set in `fetchOptions`). */
99
+ interface FetchInitLike {
100
+ method: string;
101
+ headers: Record<string, string>;
102
+ body?: string;
103
+ }
104
+ /**
105
+ * A `fetch` implementation. The global `fetch` fits, so do `undici`, `node-fetch`
106
+ * and wrappers (e.g. one that adds tracing). Declared bivariantly so a fetch with
107
+ * a richer `init` type is accepted.
108
+ */
109
+ type FetchLike = {
110
+ bivarianceHack(input: string, init?: FetchInitLike): Promise<FetchResponseLike>;
111
+ }["bivarianceHack"];
112
+ /** A response kept for `If-None-Match` revalidation. */
113
+ interface CachedResponse {
114
+ etag: string;
115
+ body: unknown;
116
+ }
117
+ /**
118
+ * Where the client keeps answers for ETag revalidation. Any `Map` fits; bring
119
+ * your own to share it between clients or to bound it differently.
120
+ */
121
+ interface EtagCache {
122
+ get(key: string): CachedResponse | undefined;
123
+ set(key: string, value: CachedResponse): unknown;
124
+ }
125
+ /** Options every client factory takes. */
126
+ interface BaseClientOptions {
127
+ /** The API origin, without `/v1`. Defaults to `https://api.solutiondesk.io`. */
128
+ baseUrl?: string;
129
+ /** A `fetch` implementation; defaults to the global one. */
130
+ fetch?: FetchLike;
131
+ /** Headers sent with every request (e.g. `accept-language` forwarded from your visitor). */
132
+ headers?: Record<string, string>;
133
+ /**
134
+ * Revalidate with `If-None-Match`: the client remembers each answer's `ETag`
135
+ * and, when the API answers `304 Not Modified`, returns the remembered body.
136
+ * `true` keeps up to 500 answers in memory; or pass your own {@link EtagCache}.
137
+ * Off by default (your framework's fetch cache may already do this).
138
+ */
139
+ etagCache?: boolean | EtagCache;
140
+ }
141
+ /** Per-call options. */
142
+ interface RequestOptions {
143
+ /** Cancels the request (an `AbortSignal`). */
144
+ signal?: unknown;
145
+ /** Extra headers for this request. */
146
+ headers?: Record<string, string>;
147
+ /**
148
+ * Extra `fetch` options for this request, passed through unchanged — e.g.
149
+ * Next.js `{ next: { revalidate: 60, tags: ["legal"] } }` or `{ cache: "no-store" }`.
150
+ */
151
+ fetchOptions?: Record<string, unknown>;
152
+ }
153
+
154
+ interface ChangelogClientOptions extends BaseClientOptions {
155
+ /** Your workspace's slug — the `acme` in `https://api.solutiondesk.io/v1/changelog/acme`. */
156
+ workspace: string;
157
+ /**
158
+ * The product whose changelog to read (its web address, e.g. `acme-mobile`).
159
+ * Without it, the workspace's default product.
160
+ */
161
+ product?: string;
162
+ }
163
+ interface ListEntriesParams {
164
+ /** Only entries of this category. */
165
+ category?: ChangelogCategory;
166
+ /** Page size, 1–50 (default 20). */
167
+ limit?: number;
168
+ /** `nextCursor` of the previous page. */
169
+ cursor?: string | null;
170
+ }
171
+ interface ChangelogClient {
172
+ /** The workspace this client reads. */
173
+ readonly workspace: string;
174
+ /** The product this client reads, or undefined for the default product. */
175
+ readonly product: string | undefined;
176
+ /**
177
+ * The product's settings and one page of its published entries, newest first
178
+ * (`GET /v1/changelog/{workspace}/{product}`). Throws a `SolutionDeskError`
179
+ * with status 404 when the workspace or product doesn't exist or the
180
+ * changelog is turned off.
181
+ */
182
+ list(params?: ListEntriesParams, options?: RequestOptions): Promise<PublicChangelogPayload>;
183
+ /** Every published entry, page by page — e.g. for a sitemap, an RSS feed or a static build. */
184
+ allEntries(params?: Omit<ListEntriesParams, "cursor">, options?: RequestOptions): AsyncGenerator<PublicChangelogEntry, void, undefined>;
185
+ /**
186
+ * One published entry of the product (`…/{product}/entries/{slug}`; without
187
+ * a product, of any product). Throws a `SolutionDeskError` with status 404
188
+ * for an unknown or unpublished entry.
189
+ */
190
+ getEntry(slug: string, options?: RequestOptions): Promise<PublicChangelogEntryPayload>;
191
+ /** Every product of the workspace, the default first (`GET /v1/changelog/{workspace}/products`). */
192
+ listProducts(options?: RequestOptions): Promise<PublicChangelogProductsPayload>;
193
+ }
194
+
195
+ /** Where "last seen" is kept — `window.localStorage`, `sessionStorage` or your own. */
196
+ interface SeenStorage {
197
+ getItem(key: string): string | null;
198
+ setItem(key: string, value: string): void;
199
+ }
200
+
201
+ interface UseChangelogOptions extends Partial<Omit<ChangelogClientOptions, "workspace">> {
202
+ /** Your workspace's slug. */
203
+ workspace: string;
204
+ /** The product whose changelog to show (its web address); else the default product. */
205
+ product?: string;
206
+ /** Use this client instead of making one (e.g. one shared by your whole app). */
207
+ client?: ChangelogClient;
208
+ /** Entries per page (1–50, default 10). */
209
+ limit?: number;
210
+ /** Only entries of this category. */
211
+ category?: ChangelogCategory;
212
+ /**
213
+ * Count entries the visitor hasn't seen yet (`unseen`), kept in `storage`.
214
+ * Default true. On the first visit nothing counts: the newest entry becomes
215
+ * the starting point.
216
+ */
217
+ trackSeen?: boolean;
218
+ /** Where "last seen" lives (default the browser's `localStorage`). */
219
+ storage?: SeenStorage | null;
220
+ /** Server-rendered or cached first page: shown at once, refreshed in the background. */
221
+ initialData?: PublicChangelogPayload;
222
+ }
223
+ interface UseChangelogResult {
224
+ status: "loading" | "ready" | "error";
225
+ /** Every entry loaded so far, newest first. */
226
+ entries: PublicChangelogEntry[];
227
+ /** The last page's answer (title, button settings, page address), or null while loading. */
228
+ data: PublicChangelogPayload | null;
229
+ /**
230
+ * Why loading failed (a `SolutionDeskError`; 404 = unknown workspace or changelog off).
231
+ * A failed {@link UseChangelogResult.loadMore} sets it too, but keeps `status` "ready".
232
+ */
233
+ error: unknown;
234
+ /** Entries newer than what the visitor last saw. */
235
+ unseen: number;
236
+ /** Remember everything loaded as seen (call it when the visitor opens your panel). */
237
+ markAllSeen(): void;
238
+ /** Whether there are older entries to load. */
239
+ hasMore: boolean;
240
+ /** Whether an older page is loading right now. */
241
+ loadingMore: boolean;
242
+ /** Load the next (older) page and append it. Never rejects; failures land in `error`. */
243
+ loadMore(): Promise<void>;
244
+ /** Load the first page again. */
245
+ reload(): Promise<void>;
246
+ }
247
+ /**
248
+ * The published changelog entries of a workspace, with an unseen count for a
249
+ * "new" badge. Fetches in the browser (no key needed); pass `initialData` from
250
+ * your server for a first paint without loading.
251
+ */
252
+ declare function useChangelog(options: UseChangelogOptions): UseChangelogResult;
253
+
254
+ /** "October 28, 2026" / "28. Oktober 2026" in the visitor's language. */
255
+ declare function formatChangelogDate(iso: string, locale?: string): string;
256
+ /**
257
+ * An entry's details. `html` comes sanitised from SolutionDesk (raw HTML in the
258
+ * team's text is escaped, links and images are allow-listed), so it can be set
259
+ * as HTML directly.
260
+ */
261
+ declare function ChangelogEntryBody({ html, className }: {
262
+ html: string;
263
+ className?: string;
264
+ }): react.JSX.Element | null;
265
+ interface ChangelogEntryClassNames {
266
+ entry?: string;
267
+ meta?: string;
268
+ date?: string;
269
+ category?: string;
270
+ /** The release (`Version 2.3.0`). */
271
+ version?: string;
272
+ /** The download link. */
273
+ download?: string;
274
+ title?: string;
275
+ summary?: string;
276
+ body?: string;
277
+ }
278
+ /**
279
+ * One entry: date, category, release, title (linking to the hosted page),
280
+ * summary, details and the download link.
281
+ */
282
+ declare function ChangelogEntryView({ entry, locale, showBody, classNames, titleAs: Title, }: {
283
+ entry: PublicChangelogEntry;
284
+ /** Language of the date and the category name (`de*` → German, else English). */
285
+ locale?: string;
286
+ /** Show the details too (default) or only title and summary. */
287
+ showBody?: boolean;
288
+ classNames?: ChangelogEntryClassNames;
289
+ titleAs?: "h2" | "h3" | "h4";
290
+ }): react.JSX.Element;
291
+ interface ChangelogFeedProps extends UseChangelogOptions {
292
+ /** Language of dates, categories and the few fixed words. */
293
+ locale?: string;
294
+ /** Show each entry's details (default) or only title and summary. */
295
+ showBody?: boolean;
296
+ className?: string;
297
+ entryClassNames?: ChangelogEntryClassNames;
298
+ /** Render an entry yourself instead of {@link ChangelogEntryView}. */
299
+ renderEntry?: (entry: PublicChangelogEntry) => ReactNode;
300
+ /** Shown while the first page loads. */
301
+ loading?: ReactNode;
302
+ /** Shown when there are no entries yet. */
303
+ empty?: ReactNode;
304
+ /** Shown when the changelog can't be loaded (default: nothing). */
305
+ fallback?: ReactNode;
306
+ /** The "load older" button's text (default "Older updates" / "Ältere Neuigkeiten"). */
307
+ moreLabel?: string;
308
+ moreClassName?: string;
309
+ }
310
+ /** A full changelog feed: the published entries, newest first, with "load older". */
311
+ declare function ChangelogFeed(props: ChangelogFeedProps): react.JSX.Element;
312
+ interface WhatsNewClassNames {
313
+ root?: string;
314
+ button?: string;
315
+ badge?: string;
316
+ panel?: string;
317
+ header?: string;
318
+ list?: string;
319
+ footer?: string;
320
+ }
321
+ interface WhatsNewProps extends UseChangelogOptions {
322
+ locale?: string;
323
+ /** The button's content (default: the team's label, else "What's new"). */
324
+ label?: ReactNode;
325
+ /** The panel's heading (default: the team's title, else "What's new"). */
326
+ title?: ReactNode;
327
+ classNames?: WhatsNewClassNames;
328
+ entryClassNames?: ChangelogEntryClassNames;
329
+ /** Render the badge yourself (default: the number in a `<span>`). */
330
+ renderBadge?: (unseen: number) => ReactNode;
331
+ }
332
+ /**
333
+ * A "What's new" button for your app's header or menu: shows how many entries
334
+ * the visitor hasn't seen and opens a panel with the latest ones (title and
335
+ * summary, linking to the hosted page). Closes on Escape and outside clicks.
336
+ * Renders nothing when the changelog can't be loaded.
337
+ */
338
+ declare function WhatsNew(props: WhatsNewProps): react.JSX.Element | null;
339
+
340
+ export { ChangelogEntryBody, type ChangelogEntryClassNames, ChangelogEntryView, ChangelogFeed, type ChangelogFeedProps, type UseChangelogOptions, type UseChangelogResult, WhatsNew, type WhatsNewClassNames, type WhatsNewProps, formatChangelogDate, useChangelog };
@@ -0,0 +1,340 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ /**
5
+ * The answers of the public changelog API (`GET /v1/changelog/{workspace}/…`).
6
+ * Written by hand so the package has no dependencies; a drift test in the
7
+ * SolutionDesk repository keeps them identical to the API's own contracts.
8
+ */
9
+ type ChangelogCategory = "new" | "improved" | "fixed" | "announcement";
10
+ type ChangelogWidgetPosition = "bottom-right" | "bottom-left";
11
+ /** A published entry. */
12
+ interface PublicChangelogEntry {
13
+ slug: string;
14
+ title: string;
15
+ /** One or two sentences — what a "What's new" panel shows. May be empty. */
16
+ summary: string;
17
+ category: ChangelogCategory;
18
+ /** The release this change ships in (`2.3.0`); empty when it isn't tied to one. */
19
+ releaseVersion: string;
20
+ /** Where the release can be downloaded (an app store page, GitHub Releases …), or null. */
21
+ downloadUrl: string | null;
22
+ /** ISO 8601. The list is sorted by it, newest first. */
23
+ publishedAt: string;
24
+ /** The details as the team wrote them (Markdown). */
25
+ markdown: string;
26
+ /**
27
+ * The details as safe HTML: raw HTML in the text is escaped, links are only
28
+ * http(s)/mailto/relative, images only the ones uploaded to the entry (with
29
+ * `srcset`). Ready for `innerHTML` / `dangerouslySetInnerHTML`. Empty when
30
+ * there are no details.
31
+ */
32
+ html: string;
33
+ /** The entry on the workspace's hosted changelog page. */
34
+ url: string;
35
+ }
36
+ /** A product with its own changelog. */
37
+ interface PublicChangelogProduct {
38
+ name: string;
39
+ slug: string;
40
+ /** One sentence about the product. May be empty. */
41
+ description: string;
42
+ /** The product's hosted changelog page. */
43
+ pageUrl: string;
44
+ /** The default product: addresses without a product show this one. */
45
+ isDefault: boolean;
46
+ }
47
+ /** `GET /v1/changelog/{workspace}/products` — every product, the default first. */
48
+ interface PublicChangelogProductsPayload {
49
+ workspace: {
50
+ name: string;
51
+ slug: string;
52
+ logoUrl: string | null;
53
+ };
54
+ products: PublicChangelogProduct[];
55
+ }
56
+ /**
57
+ * `GET /v1/changelog/{workspace}/{product}` — a product's settings and one
58
+ * page of its entries (`GET /v1/changelog/{workspace}`: the default product).
59
+ */
60
+ interface PublicChangelogPayload {
61
+ /** The workspace as the hosted page shows it; `logoUrl` is relative to the API. */
62
+ workspace: {
63
+ name: string;
64
+ slug: string;
65
+ logoUrl: string | null;
66
+ };
67
+ /** The product these entries belong to. */
68
+ product: PublicChangelogProduct;
69
+ /** The heading the team chose; empty ⇒ use your own ("What's new"). */
70
+ title: string;
71
+ /** The team's settings for the "What's new" button (empty label ⇒ your own). */
72
+ widget: {
73
+ label: string;
74
+ position: ChangelogWidgetPosition;
75
+ };
76
+ /** The product's hosted changelog page. */
77
+ pageUrl: string;
78
+ entries: PublicChangelogEntry[];
79
+ /** Pass as `cursor` for the next page; null on the last one. */
80
+ nextCursor: string | null;
81
+ }
82
+ /** `GET /v1/changelog/{workspace}/{product}/entries/{slug}`. */
83
+ interface PublicChangelogEntryPayload {
84
+ /** The product whose changelog shows the entry. */
85
+ product: PublicChangelogProduct;
86
+ entry: PublicChangelogEntry;
87
+ }
88
+
89
+ /** The response parts the SDK reads — what every `fetch` implementation returns. */
90
+ interface FetchResponseLike {
91
+ readonly status: number;
92
+ readonly ok: boolean;
93
+ readonly headers: {
94
+ get(name: string): string | null;
95
+ };
96
+ text(): Promise<string>;
97
+ }
98
+ /** What the SDK passes to `fetch` (plus whatever you set in `fetchOptions`). */
99
+ interface FetchInitLike {
100
+ method: string;
101
+ headers: Record<string, string>;
102
+ body?: string;
103
+ }
104
+ /**
105
+ * A `fetch` implementation. The global `fetch` fits, so do `undici`, `node-fetch`
106
+ * and wrappers (e.g. one that adds tracing). Declared bivariantly so a fetch with
107
+ * a richer `init` type is accepted.
108
+ */
109
+ type FetchLike = {
110
+ bivarianceHack(input: string, init?: FetchInitLike): Promise<FetchResponseLike>;
111
+ }["bivarianceHack"];
112
+ /** A response kept for `If-None-Match` revalidation. */
113
+ interface CachedResponse {
114
+ etag: string;
115
+ body: unknown;
116
+ }
117
+ /**
118
+ * Where the client keeps answers for ETag revalidation. Any `Map` fits; bring
119
+ * your own to share it between clients or to bound it differently.
120
+ */
121
+ interface EtagCache {
122
+ get(key: string): CachedResponse | undefined;
123
+ set(key: string, value: CachedResponse): unknown;
124
+ }
125
+ /** Options every client factory takes. */
126
+ interface BaseClientOptions {
127
+ /** The API origin, without `/v1`. Defaults to `https://api.solutiondesk.io`. */
128
+ baseUrl?: string;
129
+ /** A `fetch` implementation; defaults to the global one. */
130
+ fetch?: FetchLike;
131
+ /** Headers sent with every request (e.g. `accept-language` forwarded from your visitor). */
132
+ headers?: Record<string, string>;
133
+ /**
134
+ * Revalidate with `If-None-Match`: the client remembers each answer's `ETag`
135
+ * and, when the API answers `304 Not Modified`, returns the remembered body.
136
+ * `true` keeps up to 500 answers in memory; or pass your own {@link EtagCache}.
137
+ * Off by default (your framework's fetch cache may already do this).
138
+ */
139
+ etagCache?: boolean | EtagCache;
140
+ }
141
+ /** Per-call options. */
142
+ interface RequestOptions {
143
+ /** Cancels the request (an `AbortSignal`). */
144
+ signal?: unknown;
145
+ /** Extra headers for this request. */
146
+ headers?: Record<string, string>;
147
+ /**
148
+ * Extra `fetch` options for this request, passed through unchanged — e.g.
149
+ * Next.js `{ next: { revalidate: 60, tags: ["legal"] } }` or `{ cache: "no-store" }`.
150
+ */
151
+ fetchOptions?: Record<string, unknown>;
152
+ }
153
+
154
+ interface ChangelogClientOptions extends BaseClientOptions {
155
+ /** Your workspace's slug — the `acme` in `https://api.solutiondesk.io/v1/changelog/acme`. */
156
+ workspace: string;
157
+ /**
158
+ * The product whose changelog to read (its web address, e.g. `acme-mobile`).
159
+ * Without it, the workspace's default product.
160
+ */
161
+ product?: string;
162
+ }
163
+ interface ListEntriesParams {
164
+ /** Only entries of this category. */
165
+ category?: ChangelogCategory;
166
+ /** Page size, 1–50 (default 20). */
167
+ limit?: number;
168
+ /** `nextCursor` of the previous page. */
169
+ cursor?: string | null;
170
+ }
171
+ interface ChangelogClient {
172
+ /** The workspace this client reads. */
173
+ readonly workspace: string;
174
+ /** The product this client reads, or undefined for the default product. */
175
+ readonly product: string | undefined;
176
+ /**
177
+ * The product's settings and one page of its published entries, newest first
178
+ * (`GET /v1/changelog/{workspace}/{product}`). Throws a `SolutionDeskError`
179
+ * with status 404 when the workspace or product doesn't exist or the
180
+ * changelog is turned off.
181
+ */
182
+ list(params?: ListEntriesParams, options?: RequestOptions): Promise<PublicChangelogPayload>;
183
+ /** Every published entry, page by page — e.g. for a sitemap, an RSS feed or a static build. */
184
+ allEntries(params?: Omit<ListEntriesParams, "cursor">, options?: RequestOptions): AsyncGenerator<PublicChangelogEntry, void, undefined>;
185
+ /**
186
+ * One published entry of the product (`…/{product}/entries/{slug}`; without
187
+ * a product, of any product). Throws a `SolutionDeskError` with status 404
188
+ * for an unknown or unpublished entry.
189
+ */
190
+ getEntry(slug: string, options?: RequestOptions): Promise<PublicChangelogEntryPayload>;
191
+ /** Every product of the workspace, the default first (`GET /v1/changelog/{workspace}/products`). */
192
+ listProducts(options?: RequestOptions): Promise<PublicChangelogProductsPayload>;
193
+ }
194
+
195
+ /** Where "last seen" is kept — `window.localStorage`, `sessionStorage` or your own. */
196
+ interface SeenStorage {
197
+ getItem(key: string): string | null;
198
+ setItem(key: string, value: string): void;
199
+ }
200
+
201
+ interface UseChangelogOptions extends Partial<Omit<ChangelogClientOptions, "workspace">> {
202
+ /** Your workspace's slug. */
203
+ workspace: string;
204
+ /** The product whose changelog to show (its web address); else the default product. */
205
+ product?: string;
206
+ /** Use this client instead of making one (e.g. one shared by your whole app). */
207
+ client?: ChangelogClient;
208
+ /** Entries per page (1–50, default 10). */
209
+ limit?: number;
210
+ /** Only entries of this category. */
211
+ category?: ChangelogCategory;
212
+ /**
213
+ * Count entries the visitor hasn't seen yet (`unseen`), kept in `storage`.
214
+ * Default true. On the first visit nothing counts: the newest entry becomes
215
+ * the starting point.
216
+ */
217
+ trackSeen?: boolean;
218
+ /** Where "last seen" lives (default the browser's `localStorage`). */
219
+ storage?: SeenStorage | null;
220
+ /** Server-rendered or cached first page: shown at once, refreshed in the background. */
221
+ initialData?: PublicChangelogPayload;
222
+ }
223
+ interface UseChangelogResult {
224
+ status: "loading" | "ready" | "error";
225
+ /** Every entry loaded so far, newest first. */
226
+ entries: PublicChangelogEntry[];
227
+ /** The last page's answer (title, button settings, page address), or null while loading. */
228
+ data: PublicChangelogPayload | null;
229
+ /**
230
+ * Why loading failed (a `SolutionDeskError`; 404 = unknown workspace or changelog off).
231
+ * A failed {@link UseChangelogResult.loadMore} sets it too, but keeps `status` "ready".
232
+ */
233
+ error: unknown;
234
+ /** Entries newer than what the visitor last saw. */
235
+ unseen: number;
236
+ /** Remember everything loaded as seen (call it when the visitor opens your panel). */
237
+ markAllSeen(): void;
238
+ /** Whether there are older entries to load. */
239
+ hasMore: boolean;
240
+ /** Whether an older page is loading right now. */
241
+ loadingMore: boolean;
242
+ /** Load the next (older) page and append it. Never rejects; failures land in `error`. */
243
+ loadMore(): Promise<void>;
244
+ /** Load the first page again. */
245
+ reload(): Promise<void>;
246
+ }
247
+ /**
248
+ * The published changelog entries of a workspace, with an unseen count for a
249
+ * "new" badge. Fetches in the browser (no key needed); pass `initialData` from
250
+ * your server for a first paint without loading.
251
+ */
252
+ declare function useChangelog(options: UseChangelogOptions): UseChangelogResult;
253
+
254
+ /** "October 28, 2026" / "28. Oktober 2026" in the visitor's language. */
255
+ declare function formatChangelogDate(iso: string, locale?: string): string;
256
+ /**
257
+ * An entry's details. `html` comes sanitised from SolutionDesk (raw HTML in the
258
+ * team's text is escaped, links and images are allow-listed), so it can be set
259
+ * as HTML directly.
260
+ */
261
+ declare function ChangelogEntryBody({ html, className }: {
262
+ html: string;
263
+ className?: string;
264
+ }): react.JSX.Element | null;
265
+ interface ChangelogEntryClassNames {
266
+ entry?: string;
267
+ meta?: string;
268
+ date?: string;
269
+ category?: string;
270
+ /** The release (`Version 2.3.0`). */
271
+ version?: string;
272
+ /** The download link. */
273
+ download?: string;
274
+ title?: string;
275
+ summary?: string;
276
+ body?: string;
277
+ }
278
+ /**
279
+ * One entry: date, category, release, title (linking to the hosted page),
280
+ * summary, details and the download link.
281
+ */
282
+ declare function ChangelogEntryView({ entry, locale, showBody, classNames, titleAs: Title, }: {
283
+ entry: PublicChangelogEntry;
284
+ /** Language of the date and the category name (`de*` → German, else English). */
285
+ locale?: string;
286
+ /** Show the details too (default) or only title and summary. */
287
+ showBody?: boolean;
288
+ classNames?: ChangelogEntryClassNames;
289
+ titleAs?: "h2" | "h3" | "h4";
290
+ }): react.JSX.Element;
291
+ interface ChangelogFeedProps extends UseChangelogOptions {
292
+ /** Language of dates, categories and the few fixed words. */
293
+ locale?: string;
294
+ /** Show each entry's details (default) or only title and summary. */
295
+ showBody?: boolean;
296
+ className?: string;
297
+ entryClassNames?: ChangelogEntryClassNames;
298
+ /** Render an entry yourself instead of {@link ChangelogEntryView}. */
299
+ renderEntry?: (entry: PublicChangelogEntry) => ReactNode;
300
+ /** Shown while the first page loads. */
301
+ loading?: ReactNode;
302
+ /** Shown when there are no entries yet. */
303
+ empty?: ReactNode;
304
+ /** Shown when the changelog can't be loaded (default: nothing). */
305
+ fallback?: ReactNode;
306
+ /** The "load older" button's text (default "Older updates" / "Ältere Neuigkeiten"). */
307
+ moreLabel?: string;
308
+ moreClassName?: string;
309
+ }
310
+ /** A full changelog feed: the published entries, newest first, with "load older". */
311
+ declare function ChangelogFeed(props: ChangelogFeedProps): react.JSX.Element;
312
+ interface WhatsNewClassNames {
313
+ root?: string;
314
+ button?: string;
315
+ badge?: string;
316
+ panel?: string;
317
+ header?: string;
318
+ list?: string;
319
+ footer?: string;
320
+ }
321
+ interface WhatsNewProps extends UseChangelogOptions {
322
+ locale?: string;
323
+ /** The button's content (default: the team's label, else "What's new"). */
324
+ label?: ReactNode;
325
+ /** The panel's heading (default: the team's title, else "What's new"). */
326
+ title?: ReactNode;
327
+ classNames?: WhatsNewClassNames;
328
+ entryClassNames?: ChangelogEntryClassNames;
329
+ /** Render the badge yourself (default: the number in a `<span>`). */
330
+ renderBadge?: (unseen: number) => ReactNode;
331
+ }
332
+ /**
333
+ * A "What's new" button for your app's header or menu: shows how many entries
334
+ * the visitor hasn't seen and opens a panel with the latest ones (title and
335
+ * summary, linking to the hosted page). Closes on Escape and outside clicks.
336
+ * Renders nothing when the changelog can't be loaded.
337
+ */
338
+ declare function WhatsNew(props: WhatsNewProps): react.JSX.Element | null;
339
+
340
+ export { ChangelogEntryBody, type ChangelogEntryClassNames, ChangelogEntryView, ChangelogFeed, type ChangelogFeedProps, type UseChangelogOptions, type UseChangelogResult, WhatsNew, type WhatsNewClassNames, type WhatsNewProps, formatChangelogDate, useChangelog };