@doneisbetter/gds-core 2.6.3 → 2.6.5

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,905 @@
1
+ import {
2
+ ActionBar,
3
+ FormField,
4
+ GdsIcons,
5
+ ListingCard,
6
+ ReferenceSection,
7
+ StateBlock,
8
+ getSemanticActionLabel,
9
+ resolveSemanticActionConfig
10
+ } from "./chunk-6LOTZ3IZ.mjs";
11
+
12
+ // src/SemanticButton.tsx
13
+ import { useEffect, useState } from "react";
14
+ import { Button } from "@mantine/core";
15
+ import { useGdsTranslation } from "@doneisbetter/gds-theme";
16
+ import { IconCheck, IconX } from "@tabler/icons-react";
17
+ import { jsx } from "react/jsx-runtime";
18
+ function SemanticButton({
19
+ action,
20
+ loading,
21
+ feedbackState,
22
+ feedbackText,
23
+ prerenderLabelOnly = true,
24
+ vocabularyPacks = [],
25
+ ...props
26
+ }) {
27
+ const { t } = useGdsTranslation();
28
+ const config = resolveSemanticActionConfig(action, vocabularyPacks);
29
+ const [mounted, setMounted] = useState(!prerenderLabelOnly);
30
+ const [internalFeedback, setInternalFeedback] = useState(null);
31
+ useEffect(() => {
32
+ if (prerenderLabelOnly) {
33
+ setMounted(true);
34
+ }
35
+ }, [prerenderLabelOnly]);
36
+ useEffect(() => {
37
+ if (feedbackState) {
38
+ setInternalFeedback(feedbackState);
39
+ const timer = setTimeout(() => setInternalFeedback(null), 2e3);
40
+ return () => clearTimeout(timer);
41
+ }
42
+ }, [feedbackState]);
43
+ let Icon = config.icon;
44
+ let label = getSemanticActionLabel(action, t, vocabularyPacks);
45
+ let color = props.color;
46
+ if (!mounted) {
47
+ const { leftSection, ...buttonProps } = props;
48
+ return /* @__PURE__ */ jsx(Button, { loading, color, ...buttonProps, children: getSemanticActionLabel(action, void 0, vocabularyPacks) });
49
+ }
50
+ if (internalFeedback === "success") {
51
+ const defaultFeedback = config.feedback ?? { icon: IconCheck, color: "teal", messageId: "gds.feedback.saved" };
52
+ Icon = defaultFeedback.icon;
53
+ label = feedbackText || t(defaultFeedback.messageId, "Success");
54
+ color = defaultFeedback.color;
55
+ } else if (internalFeedback === "error") {
56
+ Icon = IconX;
57
+ label = feedbackText || t("gds.feedback.error", "Something went wrong");
58
+ color = "red";
59
+ }
60
+ return /* @__PURE__ */ jsx(
61
+ Button,
62
+ {
63
+ leftSection: /* @__PURE__ */ jsx(Icon, { size: "1rem" }),
64
+ loading,
65
+ color,
66
+ ...props,
67
+ children: label
68
+ }
69
+ );
70
+ }
71
+
72
+ // src/ConfirmDialog.tsx
73
+ import { Modal, Group, Text } from "@mantine/core";
74
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
75
+ function ConfirmDialog({
76
+ opened,
77
+ onClose,
78
+ onConfirm,
79
+ title,
80
+ children,
81
+ confirmAction = "confirm",
82
+ cancelAction = "cancel",
83
+ isDanger = true,
84
+ loading = false
85
+ }) {
86
+ return /* @__PURE__ */ jsxs(Modal, { opened, onClose, title, centered: true, trapFocus: true, children: [
87
+ /* @__PURE__ */ jsx2(Text, { size: "sm", mb: "xl", children }),
88
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
89
+ /* @__PURE__ */ jsx2(SemanticButton, { action: cancelAction, variant: "default", onClick: onClose, disabled: loading }),
90
+ /* @__PURE__ */ jsx2(SemanticButton, { action: confirmAction, color: isDanger ? "red" : "violet", onClick: onConfirm, loading })
91
+ ] })
92
+ ] });
93
+ }
94
+
95
+ // src/ThemeToggle.tsx
96
+ import { ActionIcon, useMantineColorScheme, useComputedColorScheme } from "@mantine/core";
97
+ import { useGdsTranslation as useGdsTranslation2 } from "@doneisbetter/gds-theme";
98
+ import { jsx as jsx3 } from "react/jsx-runtime";
99
+ function ThemeToggle({ size = "md" }) {
100
+ const { setColorScheme } = useMantineColorScheme();
101
+ const computedColorScheme = useComputedColorScheme("light", { getInitialValueInEffect: true });
102
+ const { t } = useGdsTranslation2();
103
+ const toggleColorScheme = () => {
104
+ setColorScheme(computedColorScheme === "dark" ? "light" : "dark");
105
+ };
106
+ const isDark = computedColorScheme === "dark";
107
+ return /* @__PURE__ */ jsx3(
108
+ ActionIcon,
109
+ {
110
+ onClick: toggleColorScheme,
111
+ variant: "default",
112
+ size,
113
+ "aria-label": t("gds.aria.themeToggle", "Toggle color scheme"),
114
+ children: isDark ? /* @__PURE__ */ jsx3(GdsIcons.Sun, { size: "1.2rem" }) : /* @__PURE__ */ jsx3(GdsIcons.Moon, { size: "1.2rem" })
115
+ }
116
+ );
117
+ }
118
+
119
+ // src/ReferenceThemeExplorer.tsx
120
+ import { useMemo, useState as useState2 } from "react";
121
+ import {
122
+ Badge,
123
+ Button as Button2,
124
+ Checkbox,
125
+ Code,
126
+ Group as Group2,
127
+ NativeSelect,
128
+ Paper,
129
+ SimpleGrid,
130
+ Stack,
131
+ Text as Text2,
132
+ TextInput,
133
+ Title
134
+ } from "@mantine/core";
135
+ import {
136
+ GdsProvider,
137
+ createPublicBrandTheme,
138
+ gdsDarkPublicTheme,
139
+ gdsEditorialPublicTheme,
140
+ gdsFlatSurfaceTheme,
141
+ gdsTheme
142
+ } from "@doneisbetter/gds-theme";
143
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
144
+ var themePresetCatalog = {
145
+ default: {
146
+ label: "Default runtime theme",
147
+ bestFor: "Balanced multi-surface products that need the baseline GDS system.",
148
+ summary: "The default shared runtime lane for most adopters.",
149
+ themeKey: "gdsTheme"
150
+ },
151
+ "dark-public": {
152
+ label: "Dark public theme",
153
+ bestFor: "Dark-first public experiences and campaign-style landing surfaces.",
154
+ summary: "A darker public presentation lane with the shipped runtime rhythm intact.",
155
+ themeKey: "gdsDarkPublicTheme"
156
+ },
157
+ "flat-surface": {
158
+ label: "Flat surface theme",
159
+ bestFor: "Operational products that prefer quieter elevation and flatter surface contrast.",
160
+ summary: "Removes some visual weight without creating a second token authority.",
161
+ themeKey: "gdsFlatSurfaceTheme"
162
+ },
163
+ editorial: {
164
+ label: "Editorial serif theme",
165
+ bestFor: "Documentation, editorial, and content-led experiences.",
166
+ summary: "Adds a more expressive public reading tone while staying inside GDS contracts.",
167
+ themeKey: "gdsEditorialPublicTheme"
168
+ },
169
+ brand: {
170
+ label: "Brand theme generator",
171
+ bestFor: "Consumer teams that need controlled brand expression without forking the system.",
172
+ summary: "Composes the shipped helpers into a governed product-authored theme lane.",
173
+ themeKey: "createPublicBrandTheme(...)"
174
+ }
175
+ };
176
+ function ThemePreviewSurface({
177
+ preset,
178
+ colorScheme
179
+ }) {
180
+ return /* @__PURE__ */ jsx4(Paper, { withBorder: true, radius: "xl", p: "lg", children: /* @__PURE__ */ jsxs2(Stack, { gap: "lg", children: [
181
+ /* @__PURE__ */ jsxs2(Group2, { justify: "space-between", align: "flex-start", wrap: "wrap", children: [
182
+ /* @__PURE__ */ jsxs2(Stack, { gap: 4, children: [
183
+ /* @__PURE__ */ jsx4(Text2, { fw: 700, children: preset.label }),
184
+ /* @__PURE__ */ jsx4(Text2, { size: "sm", c: "dimmed", children: preset.summary }),
185
+ /* @__PURE__ */ jsxs2(Text2, { size: "sm", children: [
186
+ /* @__PURE__ */ jsx4("strong", { children: "Best for:" }),
187
+ " ",
188
+ preset.bestFor
189
+ ] }),
190
+ /* @__PURE__ */ jsxs2(Text2, { size: "sm", children: [
191
+ /* @__PURE__ */ jsx4("strong", { children: "Color scheme:" }),
192
+ " ",
193
+ colorScheme
194
+ ] })
195
+ ] }),
196
+ /* @__PURE__ */ jsx4(ThemeToggle, {})
197
+ ] }),
198
+ /* @__PURE__ */ jsx4(
199
+ ActionBar,
200
+ {
201
+ primary: { action: "save", size: "sm" },
202
+ secondary: [{ action: "cancel", size: "sm" }],
203
+ tertiary: [{ action: "preview", size: "sm" }]
204
+ }
205
+ ),
206
+ /* @__PURE__ */ jsxs2(SimpleGrid, { cols: { base: 1, md: 2 }, spacing: "md", children: [
207
+ /* @__PURE__ */ jsx4(Paper, { withBorder: true, radius: "lg", p: "md", children: /* @__PURE__ */ jsxs2(Stack, { gap: "sm", children: [
208
+ /* @__PURE__ */ jsx4(Text2, { fw: 700, size: "sm", children: "Token-backed controls" }),
209
+ /* @__PURE__ */ jsx4(FormField, { label: "Reference input", description: "Inputs stay inside the theme lane instead of drifting into route-local styling.", children: /* @__PURE__ */ jsx4(TextInput, { placeholder: "Search or type a route name" }) }),
210
+ /* @__PURE__ */ jsxs2(Group2, { gap: "xs", wrap: "wrap", children: [
211
+ /* @__PURE__ */ jsx4(Badge, { color: "teal", variant: "light", children: "Success" }),
212
+ /* @__PURE__ */ jsx4(Badge, { color: "orange", variant: "light", children: "Warning" }),
213
+ /* @__PURE__ */ jsx4(Badge, { color: "red", variant: "light", children: "Critical" })
214
+ ] })
215
+ ] }) }),
216
+ /* @__PURE__ */ jsx4(
217
+ ListingCard,
218
+ {
219
+ title: "Reference site proof surface",
220
+ description: "This preview uses the real shipped design-system runtime rather than a docs-only styling lane.",
221
+ metadata: [
222
+ { id: "runtime", label: "Runtime lane", value: preset.themeKey },
223
+ { id: "scheme", label: "Color scheme", value: colorScheme }
224
+ ],
225
+ primaryAction: /* @__PURE__ */ jsx4(Button2, { size: "sm", children: "Inspect route" })
226
+ }
227
+ )
228
+ ] })
229
+ ] }) });
230
+ }
231
+ function ReferenceThemeExplorer() {
232
+ const [preset, setPreset] = useState2("default");
233
+ const [colorScheme, setColorScheme] = useState2("light");
234
+ const [brandPrimary, setBrandPrimary] = useState2("blue");
235
+ const [brandFlatSurfaces, setBrandFlatSurfaces] = useState2(true);
236
+ const [brandEditorialSerif, setBrandEditorialSerif] = useState2(false);
237
+ const [comparisonEnabled, setComparisonEnabled] = useState2(false);
238
+ const [comparisonPreset, setComparisonPreset] = useState2("editorial");
239
+ const resolveTheme = (presetId) => {
240
+ switch (presetId) {
241
+ case "dark-public":
242
+ return gdsDarkPublicTheme;
243
+ case "flat-surface":
244
+ return gdsFlatSurfaceTheme;
245
+ case "editorial":
246
+ return gdsEditorialPublicTheme;
247
+ case "brand":
248
+ return createPublicBrandTheme({
249
+ flatSurfaces: brandFlatSurfaces,
250
+ editorialSerif: brandEditorialSerif,
251
+ overrides: { primaryColor: brandPrimary }
252
+ });
253
+ default:
254
+ return gdsTheme;
255
+ }
256
+ };
257
+ const selectionSummary = themePresetCatalog[preset];
258
+ const comparisonSummary = themePresetCatalog[comparisonPreset];
259
+ const selectedTheme = useMemo(() => resolveTheme(preset), [preset, brandPrimary, brandFlatSurfaces, brandEditorialSerif]);
260
+ const comparedTheme = useMemo(() => resolveTheme(comparisonPreset), [comparisonPreset, brandPrimary, brandFlatSurfaces, brandEditorialSerif]);
261
+ const previewKey = `${preset}-${colorScheme}-${brandPrimary}-${brandFlatSurfaces}-${brandEditorialSerif}`;
262
+ const comparisonPreviewKey = `${comparisonPreset}-${colorScheme}-${brandPrimary}-${brandFlatSurfaces}-${brandEditorialSerif}`;
263
+ const reset = () => {
264
+ setPreset("default");
265
+ setColorScheme("light");
266
+ setBrandPrimary("blue");
267
+ setBrandFlatSurfaces(true);
268
+ setBrandEditorialSerif(false);
269
+ setComparisonEnabled(false);
270
+ setComparisonPreset("editorial");
271
+ };
272
+ return /* @__PURE__ */ jsxs2(Stack, { gap: "xl", children: [
273
+ /* @__PURE__ */ jsx4(
274
+ ReferenceSection,
275
+ {
276
+ title: "Theme Lab",
277
+ description: "Test the actual shipped GDS theme presets, color-scheme behavior, and the governed brand-theme generator. This page is part of the live demo, not a separate styling experiment.",
278
+ children: /* @__PURE__ */ jsxs2(SimpleGrid, { cols: { base: 1, md: 2, xl: 3 }, spacing: "lg", children: [
279
+ /* @__PURE__ */ jsx4(Paper, { withBorder: true, radius: "xl", p: "lg", children: /* @__PURE__ */ jsxs2(Stack, { gap: "md", children: [
280
+ /* @__PURE__ */ jsx4(Title, { order: 4, children: "Theme preset" }),
281
+ /* @__PURE__ */ jsx4(FormField, { label: "Preset", children: /* @__PURE__ */ jsx4(
282
+ NativeSelect,
283
+ {
284
+ "aria-label": "Preset",
285
+ value: preset,
286
+ onChange: (event) => setPreset(event.currentTarget.value || "default"),
287
+ data: Object.entries(themePresetCatalog).map(([value, item]) => ({ value, label: item.label }))
288
+ }
289
+ ) }),
290
+ /* @__PURE__ */ jsx4(FormField, { label: "Preview color scheme", children: /* @__PURE__ */ jsx4(
291
+ NativeSelect,
292
+ {
293
+ "aria-label": "Preview color scheme",
294
+ value: colorScheme,
295
+ onChange: (event) => setColorScheme(event.currentTarget.value || "light"),
296
+ data: [
297
+ { value: "light", label: "Light" },
298
+ { value: "dark", label: "Dark" },
299
+ { value: "auto", label: "Auto" }
300
+ ]
301
+ }
302
+ ) }),
303
+ /* @__PURE__ */ jsx4(Button2, { variant: "default", size: "sm", onClick: reset, children: "Reset theme lab" }),
304
+ /* @__PURE__ */ jsx4(Text2, { size: "sm", c: "dimmed", children: "The reference site remounts an isolated provider here so visitors can test shipped presets without changing the whole docs shell." })
305
+ ] }) }),
306
+ /* @__PURE__ */ jsx4(Paper, { withBorder: true, radius: "xl", p: "lg", children: /* @__PURE__ */ jsxs2(Stack, { gap: "md", children: [
307
+ /* @__PURE__ */ jsx4(Title, { order: 4, children: "Brand builder options" }),
308
+ /* @__PURE__ */ jsx4(FormField, { label: "Brand primary color", children: /* @__PURE__ */ jsx4(
309
+ NativeSelect,
310
+ {
311
+ "aria-label": "Brand primary color",
312
+ value: brandPrimary,
313
+ onChange: (event) => setBrandPrimary(event.currentTarget.value || "blue"),
314
+ data: ["blue", "violet", "teal", "grape", "indigo", "orange"],
315
+ disabled: preset !== "brand"
316
+ }
317
+ ) }),
318
+ /* @__PURE__ */ jsx4(
319
+ Checkbox,
320
+ {
321
+ label: "Use flat surfaces",
322
+ checked: brandFlatSurfaces,
323
+ onChange: (event) => setBrandFlatSurfaces(event.currentTarget.checked),
324
+ disabled: preset !== "brand"
325
+ }
326
+ ),
327
+ /* @__PURE__ */ jsx4(
328
+ Checkbox,
329
+ {
330
+ label: "Use editorial serif headings",
331
+ checked: brandEditorialSerif,
332
+ onChange: (event) => setBrandEditorialSerif(event.currentTarget.checked),
333
+ disabled: preset !== "brand"
334
+ }
335
+ ),
336
+ /* @__PURE__ */ jsx4(Text2, { size: "sm", c: "dimmed", children: "The generator composes shipped helpers instead of creating a second theme authority inside the website." })
337
+ ] }) }),
338
+ /* @__PURE__ */ jsx4(Paper, { withBorder: true, radius: "xl", p: "lg", children: /* @__PURE__ */ jsxs2(Stack, { gap: "md", children: [
339
+ /* @__PURE__ */ jsx4(Title, { order: 4, children: "Current selection summary" }),
340
+ /* @__PURE__ */ jsxs2(Stack, { gap: 6, children: [
341
+ /* @__PURE__ */ jsx4(Text2, { fw: 700, children: selectionSummary.label }),
342
+ /* @__PURE__ */ jsx4(Text2, { size: "sm", c: "dimmed", children: selectionSummary.summary }),
343
+ /* @__PURE__ */ jsxs2(Text2, { size: "sm", children: [
344
+ /* @__PURE__ */ jsx4("strong", { children: "Best for:" }),
345
+ " ",
346
+ selectionSummary.bestFor
347
+ ] }),
348
+ /* @__PURE__ */ jsxs2(Text2, { size: "sm", children: [
349
+ /* @__PURE__ */ jsx4("strong", { children: "Runtime lane:" }),
350
+ " ",
351
+ /* @__PURE__ */ jsx4(Code, { children: selectionSummary.themeKey })
352
+ ] }),
353
+ /* @__PURE__ */ jsxs2(Text2, { size: "sm", children: [
354
+ /* @__PURE__ */ jsx4("strong", { children: "Color scheme:" }),
355
+ " ",
356
+ colorScheme
357
+ ] })
358
+ ] }),
359
+ /* @__PURE__ */ jsx4(
360
+ Checkbox,
361
+ {
362
+ "aria-label": "Compare against a second shipped preset",
363
+ label: "Compare against a second shipped preset",
364
+ checked: comparisonEnabled,
365
+ onChange: (event) => setComparisonEnabled(event.currentTarget.checked)
366
+ }
367
+ ),
368
+ /* @__PURE__ */ jsx4(FormField, { label: "Comparison preset", children: /* @__PURE__ */ jsx4(
369
+ NativeSelect,
370
+ {
371
+ "aria-label": "Comparison preset",
372
+ value: comparisonPreset,
373
+ onChange: (event) => setComparisonPreset(event.currentTarget.value || "editorial"),
374
+ disabled: !comparisonEnabled,
375
+ data: Object.entries(themePresetCatalog).filter(([value]) => value !== preset).map(([value, item]) => ({ value, label: item.label }))
376
+ }
377
+ ) })
378
+ ] }) })
379
+ ] })
380
+ }
381
+ ),
382
+ /* @__PURE__ */ jsx4(
383
+ ReferenceSection,
384
+ {
385
+ title: "Shipped theme lanes",
386
+ description: "Every lane below is part of the supported system. The website uses these exact helpers as its live runtime proof.",
387
+ children: /* @__PURE__ */ jsx4(SimpleGrid, { cols: { base: 1, md: 2, xl: 5 }, spacing: "md", children: Object.values(themePresetCatalog).map((lane) => /* @__PURE__ */ jsx4(Paper, { withBorder: true, radius: "lg", p: "md", children: /* @__PURE__ */ jsxs2(Stack, { gap: 6, children: [
388
+ /* @__PURE__ */ jsx4(Text2, { fw: 700, size: "sm", children: lane.label }),
389
+ /* @__PURE__ */ jsx4(Text2, { size: "sm", c: "dimmed", children: lane.summary }),
390
+ /* @__PURE__ */ jsx4(Code, { block: true, fz: "10px", children: lane.themeKey })
391
+ ] }) }, lane.themeKey)) })
392
+ }
393
+ ),
394
+ /* @__PURE__ */ jsx4(
395
+ ReferenceSection,
396
+ {
397
+ title: "Live Theme Preview",
398
+ description: "Visitors can test the shipped presets, compare lanes, and inspect actual GDS surfaces under each theme.",
399
+ children: /* @__PURE__ */ jsxs2(SimpleGrid, { cols: { base: 1, xl: comparisonEnabled ? 2 : 1 }, spacing: "lg", children: [
400
+ /* @__PURE__ */ jsx4(GdsProvider, { theme: selectedTheme, defaultColorScheme: colorScheme, children: /* @__PURE__ */ jsx4(ThemePreviewSurface, { preset: selectionSummary, colorScheme }) }, previewKey),
401
+ comparisonEnabled ? /* @__PURE__ */ jsx4(GdsProvider, { theme: comparedTheme, defaultColorScheme: colorScheme, children: /* @__PURE__ */ jsx4(Paper, { withBorder: true, radius: "xl", p: "lg", children: /* @__PURE__ */ jsxs2(Stack, { gap: "md", children: [
402
+ /* @__PURE__ */ jsxs2(Group2, { justify: "space-between", align: "flex-start", wrap: "wrap", children: [
403
+ /* @__PURE__ */ jsxs2(Stack, { gap: 4, children: [
404
+ /* @__PURE__ */ jsx4(Text2, { fw: 700, children: "Comparison Preview Surface" }),
405
+ /* @__PURE__ */ jsx4(Text2, { size: "sm", c: "dimmed", children: "Compare another shipped theme against the current selection before adopting it downstream." })
406
+ ] }),
407
+ /* @__PURE__ */ jsx4(Badge, { color: "violet", variant: "light", children: comparisonSummary.label })
408
+ ] }),
409
+ /* @__PURE__ */ jsx4(ThemePreviewSurface, { preset: comparisonSummary, colorScheme })
410
+ ] }) }) }, comparisonPreviewKey) : null
411
+ ] })
412
+ }
413
+ ),
414
+ /* @__PURE__ */ jsx4(
415
+ ReferenceSection,
416
+ {
417
+ title: "Creator-Authored Experience Boundary",
418
+ description: "Creator-authored expression is allowed only through the sanctioned theme helpers and narrow exception process.",
419
+ tone: "supporting",
420
+ children: /* @__PURE__ */ jsx4(
421
+ StateBlock,
422
+ {
423
+ variant: "info",
424
+ title: "Shipped first, custom second",
425
+ description: "The official site uses shipped presets and the public brand generator first. Product-authored overrides must stay reviewable, testable, and scoped.",
426
+ compact: true
427
+ }
428
+ )
429
+ }
430
+ )
431
+ ] });
432
+ }
433
+
434
+ // src/GameBoardTile.tsx
435
+ import { Center, Paper as Paper2, Text as Text3, UnstyledButton, useMantineTheme } from "@mantine/core";
436
+ import { useMediaQuery } from "@mantine/hooks";
437
+ import { jsx as jsx5 } from "react/jsx-runtime";
438
+ function GameBoardTile({
439
+ face,
440
+ revealed,
441
+ matched,
442
+ disabled,
443
+ onPress,
444
+ highlightColor
445
+ }) {
446
+ const theme = useMantineTheme();
447
+ const reduceMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
448
+ const highlighted = revealed && !matched;
449
+ const revealBg = highlightColor ?? (typeof theme.primaryColor === "string" ? `${theme.primaryColor}.5` : "violet.5");
450
+ return /* @__PURE__ */ jsx5(UnstyledButton, { w: "100%", disabled, onClick: onPress, "aria-pressed": revealed, children: /* @__PURE__ */ jsx5(
451
+ Paper2,
452
+ {
453
+ withBorder: true,
454
+ radius: "md",
455
+ p: "md",
456
+ bg: revealed ? revealBg : "dark.6",
457
+ styles: {
458
+ root: {
459
+ aspectRatio: "1",
460
+ opacity: matched ? 0.55 : 1,
461
+ cursor: disabled ? "not-allowed" : "pointer",
462
+ transition: reduceMotion ? "opacity 0.2s ease" : "transform 0.25s ease, background-color 0.25s ease, opacity 0.25s ease",
463
+ transform: reduceMotion || !highlighted ? "scale(1)" : "scale(1.02)"
464
+ }
465
+ },
466
+ children: /* @__PURE__ */ jsx5(Center, { h: "100%", children: /* @__PURE__ */ jsx5(Text3, { size: "xl", fw: 700, children: face }) })
467
+ }
468
+ ) });
469
+ }
470
+
471
+ // src/DiscoveryShell.tsx
472
+ import { AppShell as MantineAppShell, Box, Burger, Group as Group3, ScrollArea } from "@mantine/core";
473
+ import { useDisclosure } from "@mantine/hooks";
474
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
475
+ function DiscoveryShell({
476
+ header,
477
+ sidebar,
478
+ footer,
479
+ children,
480
+ mobileNavigationLabel = "Toggle navigation",
481
+ defaultSidebarOpen = false,
482
+ stickySidebar = true,
483
+ sidebarWidth = 280,
484
+ headerHeight = 60,
485
+ shellPadding = "md",
486
+ collapseBreakpoint = "sm"
487
+ }) {
488
+ const [opened, { toggle, close }] = useDisclosure(defaultSidebarOpen);
489
+ return /* @__PURE__ */ jsxs3(
490
+ MantineAppShell,
491
+ {
492
+ header: { height: headerHeight },
493
+ footer: footer ? { height: 68 } : void 0,
494
+ navbar: {
495
+ width: sidebarWidth,
496
+ breakpoint: collapseBreakpoint,
497
+ collapsed: { mobile: !opened }
498
+ },
499
+ padding: shellPadding,
500
+ children: [
501
+ /* @__PURE__ */ jsx6(MantineAppShell.Header, { children: /* @__PURE__ */ jsxs3(Group3, { h: "100%", px: "md", gap: "sm", wrap: "nowrap", children: [
502
+ /* @__PURE__ */ jsx6(
503
+ Burger,
504
+ {
505
+ opened,
506
+ onClick: toggle,
507
+ hiddenFrom: collapseBreakpoint,
508
+ size: "sm",
509
+ "aria-label": mobileNavigationLabel
510
+ }
511
+ ),
512
+ /* @__PURE__ */ jsx6(Box, { style: { flex: 1, minWidth: 0 }, children: header })
513
+ ] }) }),
514
+ /* @__PURE__ */ jsx6(MantineAppShell.Navbar, { p: "md", "data-sticky-sidebar": stickySidebar || void 0, children: /* @__PURE__ */ jsx6(ScrollArea, { h: "100%", type: "auto", children: /* @__PURE__ */ jsx6(
515
+ Box,
516
+ {
517
+ h: "100%",
518
+ style: stickySidebar ? {
519
+ display: "flex",
520
+ flexDirection: "column"
521
+ } : void 0,
522
+ children: sidebar
523
+ }
524
+ ) }) }),
525
+ footer ? /* @__PURE__ */ jsx6(MantineAppShell.Footer, { children: /* @__PURE__ */ jsx6(Group3, { h: "100%", px: "md", justify: "space-around", wrap: "nowrap", children: footer }) }) : null,
526
+ /* @__PURE__ */ jsx6(MantineAppShell.Main, { onClick: () => close(), children })
527
+ ]
528
+ }
529
+ );
530
+ }
531
+
532
+ // src/SidebarNav.tsx
533
+ import { forwardRef } from "react";
534
+ import { Box as Box2, NavLink, Stack as Stack2, Text as Text4, createPolymorphicComponent } from "@mantine/core";
535
+ import { useGdsTranslation as useGdsTranslation3 } from "@doneisbetter/gds-theme";
536
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
537
+ function SidebarNav({ children, ariaLabel = "Primary navigation", gap = "md" }) {
538
+ return /* @__PURE__ */ jsx7(Stack2, { component: "nav", "aria-label": ariaLabel, gap, h: "100%", children });
539
+ }
540
+ function SidebarNavSection({ label, children, pushToBottom = false }) {
541
+ return /* @__PURE__ */ jsxs4(Stack2, { gap: "xs", mt: pushToBottom ? "auto" : void 0, children: [
542
+ label ? /* @__PURE__ */ jsx7(Text4, { size: "xs", fw: 700, c: "dimmed", children: label }) : null,
543
+ /* @__PURE__ */ jsx7(Stack2, { gap: 4, children })
544
+ ] });
545
+ }
546
+ var _SidebarNavItem = forwardRef(
547
+ ({
548
+ action,
549
+ label,
550
+ description,
551
+ badge,
552
+ icon,
553
+ "aria-label": ariaLabel,
554
+ "aria-current": ariaCurrent,
555
+ vocabularyPacks = [],
556
+ ...props
557
+ }, ref) => {
558
+ const { t } = useGdsTranslation3();
559
+ const config = action ? resolveSemanticActionConfig(action, vocabularyPacks) : null;
560
+ const Icon = config?.icon;
561
+ const resolvedLabel = label ?? (action ? getSemanticActionLabel(action, t, vocabularyPacks) : void 0);
562
+ return /* @__PURE__ */ jsx7(
563
+ NavLink,
564
+ {
565
+ ref,
566
+ label: resolvedLabel,
567
+ description,
568
+ leftSection: icon ?? (Icon ? /* @__PURE__ */ jsx7(Icon, { size: "1rem", stroke: 1.5 }) : void 0),
569
+ rightSection: badge ? /* @__PURE__ */ jsx7(Box2, { children: badge }) : props.rightSection,
570
+ "aria-label": ariaLabel ?? (typeof resolvedLabel === "string" ? resolvedLabel : void 0),
571
+ "aria-current": props.active ? "page" : ariaCurrent,
572
+ ...props
573
+ }
574
+ );
575
+ }
576
+ );
577
+ var SidebarNavItem = createPolymorphicComponent(_SidebarNavItem);
578
+
579
+ // src/DocsCodeBlock.tsx
580
+ import { useState as useState3 } from "react";
581
+ import { ActionIcon as ActionIcon2, Code as Code2, Group as Group4, Paper as Paper3, Stack as Stack3, Text as Text5 } from "@mantine/core";
582
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
583
+ function DocsCodeBlock({ code, language, title, withCopy = true }) {
584
+ const [copied, setCopied] = useState3(false);
585
+ const handleCopy = async () => {
586
+ if (!navigator?.clipboard) {
587
+ return;
588
+ }
589
+ await navigator.clipboard.writeText(code);
590
+ setCopied(true);
591
+ window.setTimeout(() => setCopied(false), 1200);
592
+ };
593
+ return /* @__PURE__ */ jsx8(Paper3, { withBorder: true, radius: "lg", p: "md", children: /* @__PURE__ */ jsxs5(Stack3, { gap: "sm", children: [
594
+ title || withCopy ? /* @__PURE__ */ jsxs5(Group4, { justify: "space-between", align: "center", children: [
595
+ /* @__PURE__ */ jsxs5(Stack3, { gap: 0, children: [
596
+ title ? /* @__PURE__ */ jsx8(Text5, { fw: 600, children: title }) : null,
597
+ language ? /* @__PURE__ */ jsx8(Text5, { size: "xs", c: "dimmed", children: language }) : null
598
+ ] }),
599
+ withCopy ? /* @__PURE__ */ jsx8(
600
+ ActionIcon2,
601
+ {
602
+ variant: "subtle",
603
+ "aria-label": copied ? "Copied code block" : "Copy code block",
604
+ onClick: () => {
605
+ void handleCopy();
606
+ },
607
+ children: /* @__PURE__ */ jsx8(GdsIcons.Copy, { size: "1rem" })
608
+ }
609
+ ) : null
610
+ ] }) : null,
611
+ /* @__PURE__ */ jsx8(Code2, { block: true, children: code })
612
+ ] }) });
613
+ }
614
+
615
+ // src/ShareButtonGroup.tsx
616
+ import { useState as useState4 } from "react";
617
+ import { ActionIcon as ActionIcon3, Button as Button3, Group as Group5, Stack as Stack4, Text as Text6 } from "@mantine/core";
618
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
619
+ var channelLabels = {
620
+ native: "Share",
621
+ copy: "Copy link",
622
+ mail: "Email",
623
+ message: "Message",
624
+ x: "Share on X",
625
+ facebook: "Share on Facebook",
626
+ linkedin: "Share on LinkedIn",
627
+ whatsapp: "Share on WhatsApp",
628
+ telegram: "Share on Telegram"
629
+ };
630
+ function encodeShare(url, title, text) {
631
+ const safeUrl = encodeURIComponent(url);
632
+ const safeTitle = encodeURIComponent(title ?? "");
633
+ const safeText = encodeURIComponent(text ?? title ?? "");
634
+ return {
635
+ mail: `mailto:?subject=${safeTitle}&body=${safeText ? `${safeText}%0A%0A` : ""}${safeUrl}`,
636
+ message: `sms:?&body=${safeText ? `${safeText}%20` : ""}${safeUrl}`,
637
+ x: `https://x.com/intent/tweet?text=${safeText ? `${safeText}%20` : ""}${safeUrl}`,
638
+ facebook: `https://www.facebook.com/sharer/sharer.php?u=${safeUrl}`,
639
+ linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${safeUrl}`,
640
+ whatsapp: `https://wa.me/?text=${safeText ? `${safeText}%20` : ""}${safeUrl}`,
641
+ telegram: `https://t.me/share/url?url=${safeUrl}&text=${safeText}`
642
+ };
643
+ }
644
+ function ShareAction({
645
+ channel,
646
+ compact,
647
+ href,
648
+ onClick
649
+ }) {
650
+ const label = channelLabels[channel];
651
+ const Icon = channel === "copy" ? GdsIcons.Copy : channel === "mail" ? GdsIcons.Mail : channel === "message" ? GdsIcons.Message : GdsIcons.Refer;
652
+ if (compact) {
653
+ return href ? /* @__PURE__ */ jsx9(ActionIcon3, { component: "a", href, target: "_blank", rel: "noreferrer", variant: "subtle", size: "lg", "aria-label": label, children: /* @__PURE__ */ jsx9(Icon, { size: "1rem", stroke: 1.75 }) }) : /* @__PURE__ */ jsx9(ActionIcon3, { variant: "subtle", size: "lg", "aria-label": label, onClick, children: /* @__PURE__ */ jsx9(Icon, { size: "1rem", stroke: 1.75 }) });
654
+ }
655
+ return href ? /* @__PURE__ */ jsx9(Button3, { component: "a", href, target: "_blank", rel: "noreferrer", variant: "default", leftSection: /* @__PURE__ */ jsx9(Icon, { size: "1rem", stroke: 1.75 }), children: label }) : /* @__PURE__ */ jsx9(Button3, { variant: "default", leftSection: /* @__PURE__ */ jsx9(Icon, { size: "1rem", stroke: 1.75 }), onClick, children: label });
656
+ }
657
+ function ShareButtonGroup({
658
+ url,
659
+ title,
660
+ text,
661
+ channels = ["native", "copy", "mail"],
662
+ compact = false,
663
+ label = "Share this",
664
+ description
665
+ }) {
666
+ const [copied, setCopied] = useState4(false);
667
+ const [shared, setShared] = useState4(false);
668
+ const hrefs = encodeShare(url, title, text);
669
+ async function handleCopy() {
670
+ await navigator.clipboard?.writeText?.(url);
671
+ setCopied(true);
672
+ setTimeout(() => setCopied(false), 1800);
673
+ }
674
+ async function handleNativeShare() {
675
+ if (navigator.share) {
676
+ await navigator.share({ url, title, text });
677
+ setShared(true);
678
+ setTimeout(() => setShared(false), 1800);
679
+ return;
680
+ }
681
+ await handleCopy();
682
+ }
683
+ return /* @__PURE__ */ jsxs6(Stack4, { gap: "sm", children: [
684
+ /* @__PURE__ */ jsxs6(Stack4, { gap: 2, children: [
685
+ /* @__PURE__ */ jsx9(Text6, { fw: 600, children: label }),
686
+ description ? /* @__PURE__ */ jsx9(Text6, { size: "sm", c: "dimmed", children: description }) : null
687
+ ] }),
688
+ /* @__PURE__ */ jsx9(Group5, { gap: "sm", wrap: "wrap", children: channels.map((channel) => {
689
+ if (channel === "copy") {
690
+ return /* @__PURE__ */ jsx9(ShareAction, { channel, compact, onClick: () => void handleCopy() }, channel);
691
+ }
692
+ if (channel === "native") {
693
+ return /* @__PURE__ */ jsx9(ShareAction, { channel, compact, onClick: () => void handleNativeShare() }, channel);
694
+ }
695
+ return /* @__PURE__ */ jsx9(ShareAction, { channel, compact, href: hrefs[channel] }, channel);
696
+ }) }),
697
+ copied ? /* @__PURE__ */ jsx9(Text6, { size: "sm", c: "teal", children: "Link copied." }) : null,
698
+ shared ? /* @__PURE__ */ jsx9(Text6, { size: "sm", c: "teal", children: "Share sheet opened." }) : null
699
+ ] });
700
+ }
701
+
702
+ // src/UploadDropzone.tsx
703
+ import { useRef, useState as useState5 } from "react";
704
+ import { Box as Box3, Button as Button4, Group as Group6, Stack as Stack5, Text as Text7 } from "@mantine/core";
705
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
706
+ function UploadDropzone({
707
+ title,
708
+ description,
709
+ onFilesSelected,
710
+ accept,
711
+ multiple = true,
712
+ actionLabel = "Choose files",
713
+ mode = "panel"
714
+ }) {
715
+ const inputRef = useRef(null);
716
+ const [dragging, setDragging] = useState5(false);
717
+ const UploadIcon = GdsIcons.Upload;
718
+ const forwardFiles = (files) => {
719
+ if (!files?.length || !onFilesSelected) {
720
+ return;
721
+ }
722
+ onFilesSelected(Array.from(files));
723
+ };
724
+ return /* @__PURE__ */ jsxs7(
725
+ Box3,
726
+ {
727
+ onDragOver: (event) => {
728
+ event.preventDefault();
729
+ setDragging(true);
730
+ },
731
+ onDragLeave: () => setDragging(false),
732
+ onDrop: (event) => {
733
+ event.preventDefault();
734
+ setDragging(false);
735
+ forwardFiles(event.dataTransfer.files);
736
+ },
737
+ p: mode === "inline" ? "md" : "xl",
738
+ style: {
739
+ border: `1px dashed var(${dragging ? "--mantine-color-violet-6" : "--mantine-color-default-border"})`,
740
+ borderRadius: "var(--mantine-radius-lg)",
741
+ background: dragging ? "var(--mantine-color-violet-light)" : "transparent"
742
+ },
743
+ children: [
744
+ /* @__PURE__ */ jsx10(
745
+ "input",
746
+ {
747
+ ref: inputRef,
748
+ type: "file",
749
+ hidden: true,
750
+ accept,
751
+ multiple,
752
+ onChange: (event) => forwardFiles(event.currentTarget.files)
753
+ }
754
+ ),
755
+ /* @__PURE__ */ jsxs7(Stack5, { align: mode === "inline" ? "flex-start" : "center", ta: mode === "inline" ? "left" : "center", gap: "sm", children: [
756
+ /* @__PURE__ */ jsx10(UploadIcon, { size: "1.5rem" }),
757
+ /* @__PURE__ */ jsx10(Text7, { fw: 600, children: title }),
758
+ description ? /* @__PURE__ */ jsx10(Text7, { size: "sm", c: "dimmed", children: description }) : null,
759
+ /* @__PURE__ */ jsx10(Group6, { children: /* @__PURE__ */ jsx10(Button4, { variant: "light", onClick: () => inputRef.current?.click(), children: actionLabel }) })
760
+ ] })
761
+ ]
762
+ }
763
+ );
764
+ }
765
+
766
+ // src/AccessRecoveryPanel.tsx
767
+ import { Group as Group7 } from "@mantine/core";
768
+ import { useGdsTranslation as useGdsTranslation4 } from "@doneisbetter/gds-theme";
769
+ import { jsx as jsx11 } from "react/jsx-runtime";
770
+ var stateBlockVariantByState = {
771
+ unauthenticated: "permission",
772
+ "expired-session": "info",
773
+ forbidden: "permission",
774
+ missing: "error",
775
+ unavailable: "error"
776
+ };
777
+ var defaultCopyByState = {
778
+ unauthenticated: {
779
+ title: "Sign in required",
780
+ description: "Please sign in to continue to this content."
781
+ },
782
+ "expired-session": {
783
+ title: "Session expired",
784
+ description: "Sign in again or retry to continue where you left off."
785
+ },
786
+ forbidden: {
787
+ title: "You do not have access",
788
+ description: "This content is outside your current permissions or scope."
789
+ },
790
+ missing: {
791
+ title: "Content not found",
792
+ description: "The resource may have moved, been deleted, or never existed in this scope."
793
+ },
794
+ unavailable: {
795
+ title: "Content is temporarily unavailable",
796
+ description: "Try again in a moment or return to a safe destination."
797
+ }
798
+ };
799
+ function defaultActionsForState(state, {
800
+ onRetry,
801
+ onSignIn,
802
+ onBack,
803
+ supportAction
804
+ }) {
805
+ const signInAction = onSignIn ? { action: "login", onClick: onSignIn } : null;
806
+ const retryAction = onRetry ? { action: "refresh", onClick: onRetry, variant: "light" } : null;
807
+ const backAction = onBack ? { action: "back", onClick: onBack, variant: "default" } : null;
808
+ switch (state) {
809
+ case "unauthenticated":
810
+ return { primary: signInAction, secondary: backAction, tertiary: supportAction ?? null };
811
+ case "expired-session":
812
+ return {
813
+ primary: signInAction ?? retryAction,
814
+ secondary: retryAction && signInAction ? retryAction : backAction,
815
+ tertiary: supportAction ?? null
816
+ };
817
+ case "forbidden":
818
+ return { primary: backAction, secondary: supportAction ?? null, tertiary: null };
819
+ case "missing":
820
+ return { primary: backAction, secondary: supportAction ?? null, tertiary: null };
821
+ case "unavailable":
822
+ return {
823
+ primary: retryAction ?? backAction,
824
+ secondary: retryAction && backAction ? backAction : supportAction ?? null,
825
+ tertiary: retryAction && backAction ? supportAction ?? null : null
826
+ };
827
+ }
828
+ }
829
+ function ActionGroup({
830
+ primaryAction,
831
+ secondaryAction,
832
+ tertiaryAction
833
+ }) {
834
+ const actions = [primaryAction, secondaryAction, tertiaryAction].filter(Boolean);
835
+ if (actions.length === 0) {
836
+ return null;
837
+ }
838
+ return /* @__PURE__ */ jsx11(Group7, { gap: "sm", justify: "center", wrap: "wrap", children: actions.map((actionConfig, index) => /* @__PURE__ */ jsx11(
839
+ SemanticButton,
840
+ {
841
+ action: actionConfig.action,
842
+ onClick: actionConfig.onClick,
843
+ loading: actionConfig.loading,
844
+ disabled: actionConfig.disabled,
845
+ color: actionConfig.color,
846
+ variant: actionConfig.variant ?? (index === 0 ? "filled" : "default")
847
+ },
848
+ `${actionConfig.action}-${index}`
849
+ )) });
850
+ }
851
+ function AccessRecoveryPanel({
852
+ state,
853
+ title,
854
+ description,
855
+ primaryAction,
856
+ secondaryAction,
857
+ tertiaryAction,
858
+ onRetry,
859
+ onSignIn,
860
+ onBack,
861
+ supportAction,
862
+ compact = false
863
+ }) {
864
+ const { t } = useGdsTranslation4();
865
+ const defaultCopy = defaultCopyByState[state];
866
+ const defaults = defaultActionsForState(state, {
867
+ onRetry,
868
+ onSignIn,
869
+ onBack,
870
+ supportAction
871
+ });
872
+ return /* @__PURE__ */ jsx11(
873
+ StateBlock,
874
+ {
875
+ variant: stateBlockVariantByState[state],
876
+ compact,
877
+ title: title ?? t(`gds.accessRecovery.${state}.title`, defaultCopy.title),
878
+ description: description ?? t(`gds.accessRecovery.${state}.description`, defaultCopy.description),
879
+ action: /* @__PURE__ */ jsx11(
880
+ ActionGroup,
881
+ {
882
+ primaryAction: primaryAction ?? defaults.primary,
883
+ secondaryAction: secondaryAction ?? defaults.secondary,
884
+ tertiaryAction: tertiaryAction ?? defaults.tertiary
885
+ }
886
+ )
887
+ }
888
+ );
889
+ }
890
+
891
+ export {
892
+ SemanticButton,
893
+ ConfirmDialog,
894
+ ThemeToggle,
895
+ ReferenceThemeExplorer,
896
+ GameBoardTile,
897
+ DiscoveryShell,
898
+ SidebarNav,
899
+ SidebarNavSection,
900
+ SidebarNavItem,
901
+ DocsCodeBlock,
902
+ ShareButtonGroup,
903
+ UploadDropzone,
904
+ AccessRecoveryPanel
905
+ };