@marktiderman/genesis-ui 2.13.0 → 2.14.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/SKILL.md CHANGED
@@ -135,6 +135,7 @@ and use it the same way.
135
135
  | Record detail side panel | `DetailPanel` | `/data` |
136
136
  | Dashboard / settings / form / detail page shape | `DashboardPage`, `SettingsPage`, `FormPage`, `DetailPage` | `/dashboard-page`, `/settings-page`, `/form-page`, `/detail-page` |
137
137
  | KPI tile | `StatCard` | `/data` |
138
+ | The page around one publishable thing (a form, a course, a lesson): header, readiness chip, headline numbers, summary cards, follow-ups, settings with jump links, Share | `ResourceHeader`, `ReadinessChip`, `HeadlineNumbers`, `SummaryCard`, `FollowUpList`, `ResponsesToolbar`, `ResourceSettingsPage`, `SettingsSection`, `ShareDialog` (+ `readinessOf`, `dayLabel`, `prefilledUrl` on the server) | `/resource` (`/resource/rules`) |
138
139
  | Table cells: dates, numbers, money, badges, links, thumbnails | `DateCell`, `NumberCell`, `CurrencyCell`, `BadgeCell`, `LinkCell`, `ThumbnailCell` | `/data` |
139
140
  | Search, sort, filters, status chips/tabs, saved views | `DataFilters` | `/data` |
140
141
  | Create/edit form bound to a resource | `ResourceForm` / `useResourceForm` | `/data` / `@marktiderman/genesis-core/hooks` |
@@ -0,0 +1,605 @@
1
+ "use client";
2
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from './chunk-VXFYETRK.js';
3
+ import { ToggleGroup, ToggleGroupItem } from './chunk-NUXW6NPP.js';
4
+ import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbSeparator, BreadcrumbPage } from './chunk-2MQKBZBJ.js';
5
+ import { SAVE_STATE_LABEL, readinessChip, prefilledUrl, embedSnippet } from './chunk-SURORBV7.js';
6
+ import { StatusBadge } from './chunk-XMK3T4TA.js';
7
+ import { Textarea } from './chunk-YETMISCE.js';
8
+ import { Separator } from './chunk-SXSCHWZU.js';
9
+ import { Skeleton } from './chunk-QFM4Z2SS.js';
10
+ import { Input } from './chunk-QSXVL5UB.js';
11
+ import { Badge } from './chunk-NPNADZHM.js';
12
+ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from './chunk-FK42ZGGV.js';
13
+ import { Label } from './chunk-F47U5KNW.js';
14
+ import { Button } from './chunk-NWSCYLPT.js';
15
+ import { toast } from './chunk-4RAZJ6LL.js';
16
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './chunk-7XMZVISM.js';
17
+ import { cn } from './chunk-NZ5WBA4R.js';
18
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
19
+ import { Check, Loader2, TriangleAlert, Undo2, Redo2, Eye, Share2, Rocket, CircleAlert, CircleCheck, Star, ExternalLink, Printer, Copy } from 'lucide-react';
20
+ import { useState, useEffect, useId } from 'react';
21
+
22
+ function Chip({
23
+ tone,
24
+ icon,
25
+ children,
26
+ className,
27
+ testID
28
+ }) {
29
+ const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
30
+ icon ? /* @__PURE__ */ jsx("span", { className: "mr-1 inline-flex [&_svg]:size-3", "aria-hidden": true, children: icon }) : null,
31
+ children
32
+ ] });
33
+ if (tone === "danger") {
34
+ return /* @__PURE__ */ jsx(StatusBadge, { status: "offTrack", className: cn("font-normal", className), "data-testid": testID, children: inner });
35
+ }
36
+ return /* @__PURE__ */ jsx(Badge, { variant: tone, className: cn("font-normal", className), "data-testid": testID, children: inner });
37
+ }
38
+ var SAVE_TONE = {
39
+ saved: "muted",
40
+ saving: "muted",
41
+ unsaved: "muted",
42
+ failed: "warning",
43
+ stopped: "danger",
44
+ conflict: "danger"
45
+ };
46
+ function ResourceHeader({
47
+ title,
48
+ untitled = "Untitled",
49
+ parent,
50
+ status,
51
+ saveState,
52
+ onUndo,
53
+ onRedo,
54
+ canUndo = true,
55
+ canRedo = true,
56
+ onPreview,
57
+ onShare,
58
+ publish,
59
+ actions,
60
+ className
61
+ }) {
62
+ const history = onUndo || onRedo;
63
+ const name = title.trim() || untitled;
64
+ return /* @__PURE__ */ jsxs(
65
+ "header",
66
+ {
67
+ "data-slot": "resource-header",
68
+ className: cn("flex flex-col gap-3 md:flex-row md:items-start md:justify-between", className),
69
+ children: [
70
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
71
+ parent ? /* @__PURE__ */ jsx(Breadcrumb, { children: /* @__PURE__ */ jsxs(BreadcrumbList, { className: "text-xs", children: [
72
+ /* @__PURE__ */ jsx(BreadcrumbItem, { className: "[&_a:hover]:text-foreground [&_a]:transition-colors", children: parent }),
73
+ /* @__PURE__ */ jsx(BreadcrumbSeparator, {}),
74
+ /* @__PURE__ */ jsx(BreadcrumbItem, { className: "min-w-0", children: /* @__PURE__ */ jsx(BreadcrumbPage, { className: "truncate", children: name }) })
75
+ ] }) }) : null,
76
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1", children: [
77
+ /* @__PURE__ */ jsx("h1", { className: "min-w-0 break-words text-xl font-semibold tracking-tight sm:text-2xl", children: name }),
78
+ status ? /* @__PURE__ */ jsx(Chip, { tone: status.tone, testID: "resource-status", children: status.label }) : null,
79
+ saveState ? /* @__PURE__ */ jsx("span", { "aria-live": "polite", children: /* @__PURE__ */ jsx(
80
+ Chip,
81
+ {
82
+ tone: SAVE_TONE[saveState],
83
+ testID: "resource-save-state",
84
+ icon: saveState === "saved" ? /* @__PURE__ */ jsx(Check, {}) : saveState === "saving" ? /* @__PURE__ */ jsx(Loader2, { className: "animate-spin" }) : saveState === "unsaved" ? null : /* @__PURE__ */ jsx(TriangleAlert, {}),
85
+ children: SAVE_STATE_LABEL[saveState]
86
+ }
87
+ ) }) : null
88
+ ] })
89
+ ] }),
90
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: [
91
+ history ? /* @__PURE__ */ jsxs(Fragment, { children: [
92
+ /* @__PURE__ */ jsx(
93
+ Button,
94
+ {
95
+ variant: "ghost",
96
+ size: "icon",
97
+ "aria-label": "Undo",
98
+ title: "Undo",
99
+ disabled: !onUndo || !canUndo,
100
+ onClick: onUndo,
101
+ "data-testid": "resource-undo",
102
+ children: /* @__PURE__ */ jsx(Undo2, { className: "size-4" })
103
+ }
104
+ ),
105
+ /* @__PURE__ */ jsx(
106
+ Button,
107
+ {
108
+ variant: "ghost",
109
+ size: "icon",
110
+ "aria-label": "Redo",
111
+ title: "Redo",
112
+ disabled: !onRedo || !canRedo,
113
+ onClick: onRedo,
114
+ "data-testid": "resource-redo",
115
+ children: /* @__PURE__ */ jsx(Redo2, { className: "size-4" })
116
+ }
117
+ ),
118
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "mx-1 h-6" })
119
+ ] }) : null,
120
+ actions,
121
+ onPreview ? /* @__PURE__ */ jsx(Button, { variant: "ghost", startIcon: /* @__PURE__ */ jsx(Eye, { className: "size-4" }), onClick: onPreview, "data-testid": "resource-preview", children: "Preview" }) : null,
122
+ onShare ? /* @__PURE__ */ jsx(Button, { variant: "outline", startIcon: /* @__PURE__ */ jsx(Share2, { className: "size-4" }), onClick: onShare, "data-testid": "resource-share", children: "Share" }) : null,
123
+ publish ? /* @__PURE__ */ jsx(
124
+ Button,
125
+ {
126
+ startIcon: /* @__PURE__ */ jsx(Rocket, { className: "size-4" }),
127
+ loading: publish.loading,
128
+ disabled: publish.disabled,
129
+ onClick: publish.onClick,
130
+ "data-testid": "resource-publish",
131
+ children: publish.label ?? "Publish"
132
+ }
133
+ ) : null
134
+ ] })
135
+ ]
136
+ }
137
+ );
138
+ }
139
+ function ReadinessChip({
140
+ readiness,
141
+ onClick,
142
+ labels,
143
+ className
144
+ }) {
145
+ const { label, tone } = readinessChip(readiness, labels);
146
+ const icon = readiness.state === "ready" ? /* @__PURE__ */ jsx(Check, {}) : readiness.state === "problems" ? /* @__PURE__ */ jsx(CircleAlert, {}) : readiness.state === "warnings" ? /* @__PURE__ */ jsx(TriangleAlert, {}) : null;
147
+ const chip = /* @__PURE__ */ jsx(Chip, { tone, icon, testID: "readiness", className, children: label });
148
+ if (!onClick) return chip;
149
+ return /* @__PURE__ */ jsx(
150
+ "button",
151
+ {
152
+ type: "button",
153
+ onClick,
154
+ className: "rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
155
+ children: chip
156
+ }
157
+ );
158
+ }
159
+ function ResponsesToolbar({
160
+ view,
161
+ onViewChange,
162
+ filters,
163
+ actions,
164
+ labels = {},
165
+ className
166
+ }) {
167
+ return /* @__PURE__ */ jsxs(
168
+ "div",
169
+ {
170
+ "data-slot": "responses-toolbar",
171
+ className: cn("flex flex-col gap-2 lg:flex-row lg:items-start lg:justify-between", className),
172
+ children: [
173
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [
174
+ /* @__PURE__ */ jsxs(
175
+ ToggleGroup,
176
+ {
177
+ type: "single",
178
+ variant: "outline",
179
+ size: "sm",
180
+ className: "h-9",
181
+ value: view,
182
+ onValueChange: (v) => v && onViewChange(v),
183
+ "aria-label": labels.group ?? "Show responses as",
184
+ children: [
185
+ /* @__PURE__ */ jsx(ToggleGroupItem, { value: "summary", className: "h-9 px-3", "data-testid": "responses-view-summary", children: labels.summary ?? "Summary" }),
186
+ /* @__PURE__ */ jsx(ToggleGroupItem, { value: "each", className: "h-9 px-3", "data-testid": "responses-view-each", children: labels.each ?? "Each response" })
187
+ ]
188
+ }
189
+ ),
190
+ filters
191
+ ] }),
192
+ actions ? /* @__PURE__ */ jsx("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: actions }) : null
193
+ ]
194
+ }
195
+ );
196
+ }
197
+ var TONE = {
198
+ positive: "text-emerald-700 dark:text-emerald-300",
199
+ negative: "text-red-700 dark:text-red-300",
200
+ warning: "text-amber-800 dark:text-amber-200",
201
+ muted: "text-muted-foreground"
202
+ };
203
+ function HeadlineNumbers({
204
+ numbers,
205
+ isLoading = false,
206
+ className
207
+ }) {
208
+ if (isLoading) {
209
+ return /* @__PURE__ */ jsx("div", { className: cn("grid grid-cols-2 gap-3 lg:grid-cols-4", className), children: [0, 1, 2, 3].map((i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-28 w-full rounded-lg" }, i)) });
210
+ }
211
+ if (!numbers.length) return null;
212
+ return /* @__PURE__ */ jsx(
213
+ "dl",
214
+ {
215
+ "data-slot": "headline-numbers",
216
+ className: cn(
217
+ "grid grid-cols-2 gap-3",
218
+ numbers.length >= 4 ? "lg:grid-cols-4" : numbers.length === 3 ? "lg:grid-cols-3" : "",
219
+ className
220
+ ),
221
+ children: numbers.map((n) => /* @__PURE__ */ jsxs(
222
+ "div",
223
+ {
224
+ className: "flex min-w-0 flex-col gap-1 rounded-lg border border-border bg-card p-4 text-card-foreground",
225
+ "data-testid": `headline-${n.key}`,
226
+ children: [
227
+ /* @__PURE__ */ jsx("dt", { className: "truncate text-sm text-muted-foreground", children: n.label }),
228
+ /* @__PURE__ */ jsx("dd", { className: "text-2xl font-semibold tabular-nums tracking-tight sm:text-3xl", children: n.value }),
229
+ n.detail ? /* @__PURE__ */ jsx("dd", { className: cn("text-xs", TONE[n.tone ?? "muted"]), children: n.detail }) : null
230
+ ]
231
+ },
232
+ n.key
233
+ ))
234
+ }
235
+ );
236
+ }
237
+ function SummaryCard({
238
+ icon,
239
+ title,
240
+ meta,
241
+ children,
242
+ className,
243
+ testID
244
+ }) {
245
+ return /* @__PURE__ */ jsxs(
246
+ "section",
247
+ {
248
+ "aria-label": title,
249
+ "data-testid": testID,
250
+ "data-slot": "summary-card",
251
+ className: cn("flex flex-col gap-4 rounded-lg border border-border bg-card p-5 text-card-foreground", className),
252
+ children: [
253
+ /* @__PURE__ */ jsxs("header", { className: "flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1", children: [
254
+ /* @__PURE__ */ jsxs("h3", { className: "flex min-w-0 items-center gap-2 font-semibold", children: [
255
+ icon ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground [&_svg]:size-4", "aria-hidden": true, children: icon }) : null,
256
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 break-words", children: title })
257
+ ] }),
258
+ meta ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: meta }) : null
259
+ ] }),
260
+ children
261
+ ]
262
+ }
263
+ );
264
+ }
265
+ function FollowUpList({
266
+ entries,
267
+ actions,
268
+ title = "Needs a follow-up",
269
+ isLoading = false,
270
+ empty = "Nothing to follow up right now.",
271
+ noComment = "No comment",
272
+ className
273
+ }) {
274
+ return /* @__PURE__ */ jsxs(
275
+ "section",
276
+ {
277
+ "aria-label": title,
278
+ "data-testid": "follow-up-list",
279
+ className: cn("flex flex-col gap-3 rounded-lg border border-border bg-card p-4 text-card-foreground", className),
280
+ children: [
281
+ /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between gap-2", children: [
282
+ /* @__PURE__ */ jsxs("h3", { className: "flex items-center gap-2 font-semibold", children: [
283
+ /* @__PURE__ */ jsx(TriangleAlert, { className: "size-4 text-amber-800 dark:text-amber-200", "aria-hidden": true }),
284
+ title
285
+ ] }),
286
+ !isLoading ? /* @__PURE__ */ jsx("span", { className: "rounded-md bg-muted px-2 py-0.5 text-xs tabular-nums text-muted-foreground", children: entries.length }) : null
287
+ ] }),
288
+ isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
289
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-24 w-full rounded-md" }),
290
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-24 w-full rounded-md" })
291
+ ] }) : entries.length === 0 ? /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
292
+ /* @__PURE__ */ jsx(CircleCheck, { className: "size-4 shrink-0", "aria-hidden": true }),
293
+ empty
294
+ ] }) : /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-3", children: entries.map((entry) => {
295
+ const buttons = actions?.(entry);
296
+ return /* @__PURE__ */ jsxs(
297
+ "li",
298
+ {
299
+ className: "flex flex-col gap-1.5 rounded-md border border-border p-3",
300
+ "data-testid": `follow-up-${entry.id}`,
301
+ children: [
302
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
303
+ /* @__PURE__ */ jsxs("p", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [
304
+ /* @__PURE__ */ jsx("span", { className: "break-words font-medium", children: entry.name }),
305
+ entry.tag ? /* @__PURE__ */ jsx(Chip, { tone: entry.tag.tone ?? "muted", children: entry.tag.label }) : null
306
+ ] }),
307
+ entry.rating ? /* @__PURE__ */ jsxs(
308
+ "span",
309
+ {
310
+ className: "flex shrink-0 items-center gap-0.5 text-sm font-medium tabular-nums text-amber-800 dark:text-amber-200",
311
+ role: "img",
312
+ "aria-label": `Rated ${entry.rating.value} out of ${entry.rating.max}`,
313
+ children: [
314
+ entry.rating.value,
315
+ /* @__PURE__ */ jsx(Star, { className: "size-3.5 fill-current", "aria-hidden": true })
316
+ ]
317
+ }
318
+ ) : null
319
+ ] }),
320
+ entry.meta ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: entry.meta }) : null,
321
+ /* @__PURE__ */ jsx("p", { className: cn("line-clamp-3 break-words text-sm", !entry.comment && "text-muted-foreground"), children: entry.comment || noComment }),
322
+ buttons ? /* @__PURE__ */ jsx("div", { className: "mt-1 flex flex-wrap gap-2", children: buttons }) : null
323
+ ]
324
+ },
325
+ entry.id
326
+ );
327
+ }) })
328
+ ]
329
+ }
330
+ );
331
+ }
332
+ function SettingsSection({
333
+ id,
334
+ icon,
335
+ title,
336
+ description,
337
+ children,
338
+ className
339
+ }) {
340
+ const headingId = id ? `${id}-title` : void 0;
341
+ return /* @__PURE__ */ jsxs(
342
+ "section",
343
+ {
344
+ id,
345
+ "aria-labelledby": headingId,
346
+ "data-slot": "settings-section",
347
+ className: cn("scroll-mt-6 rounded-lg border border-border bg-card text-card-foreground", className),
348
+ children: [
349
+ /* @__PURE__ */ jsxs("header", { className: "flex gap-3 border-b border-border px-5 py-4", children: [
350
+ icon ? /* @__PURE__ */ jsx("span", { className: "mt-0.5 text-primary [&_svg]:size-5", "aria-hidden": true, children: icon }) : null,
351
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
352
+ /* @__PURE__ */ jsx("h2", { id: headingId, className: "font-semibold", children: title }),
353
+ description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description }) : null
354
+ ] })
355
+ ] }),
356
+ /* @__PURE__ */ jsx(
357
+ "div",
358
+ {
359
+ className: cn(
360
+ "divide-y divide-border",
361
+ // Rows: genesis-ui SettingsRow and ToggleRow, padded to the card.
362
+ "[&>*]:px-5 [&>[data-slot=settings-row]]:py-3.5",
363
+ "[&>[data-slot=settings-row]:not(:has([role=switch]))]:max-sm:flex-col",
364
+ "[&>[data-slot=settings-row]:not(:has([role=switch]))]:max-sm:items-stretch",
365
+ "[&>[data-slot=settings-row]>div:last-child]:max-sm:flex-wrap"
366
+ ),
367
+ children
368
+ }
369
+ )
370
+ ]
371
+ }
372
+ );
373
+ }
374
+ function ResourceSettingsPage({
375
+ sections,
376
+ navLabel = "On this page",
377
+ className
378
+ }) {
379
+ const [active, setActive] = useState(sections[0]?.id);
380
+ const ids = sections.map((s) => s.id).join(" ");
381
+ useEffect(() => {
382
+ if (typeof window === "undefined") return;
383
+ let frame = 0;
384
+ const update = () => {
385
+ frame = 0;
386
+ const line = window.innerHeight / 3;
387
+ let current = sections[0]?.id;
388
+ let last;
389
+ for (const s of sections) {
390
+ const el = document.getElementById(s.id);
391
+ if (!el) continue;
392
+ last = s.id;
393
+ if (el.getBoundingClientRect().top <= line) current = s.id;
394
+ }
395
+ const bottom = window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 2;
396
+ if (bottom && last && window.scrollY > 0) current = last;
397
+ setActive(current);
398
+ };
399
+ const onScroll = () => {
400
+ if (!frame) frame = window.requestAnimationFrame(update);
401
+ };
402
+ update();
403
+ window.addEventListener("scroll", onScroll, { passive: true, capture: true });
404
+ return () => {
405
+ window.removeEventListener("scroll", onScroll, { capture: true });
406
+ if (frame) window.cancelAnimationFrame(frame);
407
+ };
408
+ }, [ids]);
409
+ return /* @__PURE__ */ jsxs(
410
+ "div",
411
+ {
412
+ "data-slot": "resource-settings-page",
413
+ className: cn("grid grid-cols-[minmax(0,1fr)] gap-6 md:grid-cols-[12rem_minmax(0,1fr)] lg:gap-8", className),
414
+ children: [
415
+ /* @__PURE__ */ jsxs("nav", { "aria-label": navLabel, className: "min-w-0 md:sticky md:top-4 md:self-start", children: [
416
+ /* @__PURE__ */ jsx("p", { className: "mb-2 hidden text-xs font-medium uppercase tracking-wide text-muted-foreground md:block", children: navLabel }),
417
+ /* @__PURE__ */ jsx("ul", { className: "-mx-1 flex gap-1 overflow-x-auto px-1 md:mx-0 md:flex-col md:overflow-visible md:px-0", children: sections.map((s) => /* @__PURE__ */ jsx("li", { className: "shrink-0", children: /* @__PURE__ */ jsxs(
418
+ "a",
419
+ {
420
+ href: `#${s.id}`,
421
+ "aria-current": active === s.id ? "location" : void 0,
422
+ onClick: (e) => {
423
+ setActive(s.id);
424
+ if (s.content === void 0 && s.onSelect) {
425
+ e.preventDefault();
426
+ s.onSelect();
427
+ }
428
+ },
429
+ className: cn(
430
+ "flex items-center gap-2 whitespace-nowrap rounded-md px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&_svg]:size-4",
431
+ active === s.id && "bg-muted font-medium text-foreground"
432
+ ),
433
+ children: [
434
+ s.icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: s.icon }) : null,
435
+ s.title
436
+ ]
437
+ }
438
+ ) }, s.id)) })
439
+ ] }),
440
+ /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-col gap-5", children: sections.filter((s) => s.content !== void 0).map((s) => /* @__PURE__ */ jsx(SettingsSection, { id: s.id, icon: s.icon, title: s.title, description: s.description, children: s.content }, s.id)) })
441
+ ]
442
+ }
443
+ );
444
+ }
445
+ var ANY = "__any";
446
+ async function copy(text, what) {
447
+ try {
448
+ await navigator.clipboard.writeText(text);
449
+ toast.success(`${what} copied.`);
450
+ } catch {
451
+ toast.error("Couldn't copy. Select it and copy it instead.");
452
+ }
453
+ }
454
+ function CopyField({
455
+ id,
456
+ label,
457
+ value,
458
+ what
459
+ }) {
460
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
461
+ /* @__PURE__ */ jsx(Label, { htmlFor: id, children: label }),
462
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
463
+ /* @__PURE__ */ jsx(Input, { id, readOnly: true, value, onFocus: (e) => e.target.select() }),
464
+ /* @__PURE__ */ jsx(
465
+ Button,
466
+ {
467
+ variant: "outline",
468
+ startIcon: /* @__PURE__ */ jsx(Copy, { className: "size-4" }),
469
+ onClick: () => void copy(value, what),
470
+ children: "Copy"
471
+ }
472
+ )
473
+ ] })
474
+ ] });
475
+ }
476
+ function ShareDialog({
477
+ open,
478
+ onOpenChange,
479
+ noun = "page",
480
+ title = `Share your ${noun}`,
481
+ description = "Anyone with the link can open it.",
482
+ url,
483
+ posterUrl,
484
+ renderQr,
485
+ name,
486
+ prefill = [],
487
+ prefillNote,
488
+ defaultTab = "link"
489
+ }) {
490
+ const id = useId();
491
+ const [values, setValues] = useState({});
492
+ const chosen = Object.fromEntries(Object.entries(values).filter(([, v]) => v && v !== ANY));
493
+ const prefilled = url && Object.keys(chosen).length ? prefilledUrl(url, chosen) : null;
494
+ const poster = url ? posterUrl?.(url, {}) : null;
495
+ const prefilledPoster = prefilled && url ? posterUrl?.(url, chosen) : null;
496
+ const usable = prefill.filter((p) => p.options.length > 0);
497
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-h-[90dvh] overflow-y-auto sm:max-w-lg", children: [
498
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
499
+ /* @__PURE__ */ jsx(DialogTitle, { children: title }),
500
+ /* @__PURE__ */ jsx(DialogDescription, { children: description })
501
+ ] }),
502
+ !url ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Loading the link\u2026" }) : /* @__PURE__ */ jsxs(Tabs, { defaultValue: defaultTab === "qr" && !renderQr ? "link" : defaultTab, children: [
503
+ /* @__PURE__ */ jsxs(TabsList, { children: [
504
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "link", children: "Link" }),
505
+ renderQr && /* @__PURE__ */ jsx(TabsTrigger, { value: "qr", children: "QR code" }),
506
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "embed", children: "Embed" })
507
+ ] }),
508
+ /* @__PURE__ */ jsxs(TabsContent, { value: "link", className: "space-y-5 pt-2", children: [
509
+ /* @__PURE__ */ jsx(CopyField, { id: `${id}-link`, label: "Link", value: url, what: "Link" }),
510
+ /* @__PURE__ */ jsx(
511
+ Button,
512
+ {
513
+ asChild: true,
514
+ variant: "ghost",
515
+ size: "sm",
516
+ startIcon: /* @__PURE__ */ jsx(ExternalLink, { className: "size-4" }),
517
+ children: /* @__PURE__ */ jsxs("a", { href: url, target: "_blank", rel: "noreferrer", children: [
518
+ "Open the ",
519
+ noun
520
+ ] })
521
+ }
522
+ ),
523
+ usable.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-3 border-t border-border pt-4", children: [
524
+ /* @__PURE__ */ jsxs("div", { children: [
525
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Prefilled link" }),
526
+ prefillNote && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: prefillNote })
527
+ ] }),
528
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: usable.map((p) => /* @__PURE__ */ jsxs(
529
+ Select,
530
+ {
531
+ value: values[p.key] ?? ANY,
532
+ onValueChange: (v) => setValues((old) => ({ ...old, [p.key]: v })),
533
+ children: [
534
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "min-w-40 flex-1", "aria-label": p.label, children: /* @__PURE__ */ jsx(SelectValue, {}) }),
535
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
536
+ /* @__PURE__ */ jsx(SelectItem, { value: ANY, children: p.anyLabel }),
537
+ p.options.map((o) => /* @__PURE__ */ jsx(SelectItem, { value: o.value, children: o.label }, o.value))
538
+ ] })
539
+ ]
540
+ },
541
+ p.key
542
+ )) }),
543
+ prefilled && /* @__PURE__ */ jsxs(Fragment, { children: [
544
+ /* @__PURE__ */ jsx(
545
+ CopyField,
546
+ {
547
+ id: `${id}-prefilled`,
548
+ label: "Prefilled link",
549
+ value: prefilled,
550
+ what: "Prefilled link"
551
+ }
552
+ ),
553
+ prefilledPoster && /* @__PURE__ */ jsx(
554
+ Button,
555
+ {
556
+ asChild: true,
557
+ variant: "outline",
558
+ startIcon: /* @__PURE__ */ jsx(Printer, { className: "size-4" }),
559
+ children: /* @__PURE__ */ jsx("a", { href: prefilledPoster, target: "_blank", rel: "noreferrer", children: "Print a poster for this link" })
560
+ }
561
+ )
562
+ ] })
563
+ ] })
564
+ ] }),
565
+ renderQr && /* @__PURE__ */ jsxs(TabsContent, { value: "qr", className: "space-y-4 pt-2", children: [
566
+ /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-56", children: renderQr(url) }),
567
+ /* @__PURE__ */ jsxs("p", { className: "text-center text-sm text-muted-foreground", children: [
568
+ "Scan with a phone's camera to open the ",
569
+ noun,
570
+ "."
571
+ ] }),
572
+ poster && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", startIcon: /* @__PURE__ */ jsx(Printer, { className: "size-4" }), children: /* @__PURE__ */ jsx("a", { href: poster, target: "_blank", rel: "noreferrer", children: "Print a QR poster" }) }) })
573
+ ] }),
574
+ /* @__PURE__ */ jsxs(TabsContent, { value: "embed", className: "space-y-3 pt-2", children: [
575
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
576
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${id}-embed`, children: "Embed on your website" }),
577
+ /* @__PURE__ */ jsx(
578
+ Textarea,
579
+ {
580
+ id: `${id}-embed`,
581
+ readOnly: true,
582
+ rows: 4,
583
+ className: "font-mono text-xs",
584
+ value: embedSnippet(url, name),
585
+ onFocus: (e) => e.target.select()
586
+ }
587
+ )
588
+ ] }),
589
+ /* @__PURE__ */ jsx(
590
+ Button,
591
+ {
592
+ variant: "outline",
593
+ startIcon: /* @__PURE__ */ jsx(Copy, { className: "size-4" }),
594
+ onClick: () => void copy(embedSnippet(url, name), "Embed code"),
595
+ children: "Copy embed code"
596
+ }
597
+ )
598
+ ] })
599
+ ] })
600
+ ] }) });
601
+ }
602
+
603
+ export { Chip, FollowUpList, HeadlineNumbers, ReadinessChip, ResourceHeader, ResourceSettingsPage, ResponsesToolbar, SettingsSection, ShareDialog, SummaryCard };
604
+ //# sourceMappingURL=chunk-D3U5SBLW.js.map
605
+ //# sourceMappingURL=chunk-D3U5SBLW.js.map