@nswds/app 1.100.0 → 1.102.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/dist/index.cjs CHANGED
@@ -40,8 +40,6 @@ var LabelPrimitive = require('@radix-ui/react-label');
40
40
  var RadioGroupPrimitive = require('@radix-ui/react-radio-group');
41
41
  var TabsPrimitives = require('@radix-ui/react-tabs');
42
42
  var CollapsiblePrimitive = require('@radix-ui/react-collapsible');
43
- var ToggleGroupPrimitive = require('@radix-ui/react-toggle-group');
44
- var TogglePrimitive = require('@radix-ui/react-toggle');
45
43
  var cmdk = require('cmdk');
46
44
  var DialogPrimitive = require('@radix-ui/react-dialog');
47
45
  var ContextMenuPrimitive = require('@radix-ui/react-context-menu');
@@ -49,6 +47,8 @@ var reactTable = require('@tanstack/react-table');
49
47
  var SeparatorPrimitive = require('@radix-ui/react-separator');
50
48
  var vaul = require('vaul');
51
49
  var reactHookForm = require('react-hook-form');
50
+ var ToggleGroupPrimitive = require('@radix-ui/react-toggle-group');
51
+ var TogglePrimitive = require('@radix-ui/react-toggle');
52
52
  var Image2 = require('next/image');
53
53
  var HoverCardPrimitive = require('@radix-ui/react-hover-card');
54
54
  var inputOtp = require('input-otp');
@@ -110,11 +110,11 @@ var LabelPrimitive__namespace = /*#__PURE__*/_interopNamespace(LabelPrimitive);
110
110
  var RadioGroupPrimitive__namespace = /*#__PURE__*/_interopNamespace(RadioGroupPrimitive);
111
111
  var TabsPrimitives__namespace = /*#__PURE__*/_interopNamespace(TabsPrimitives);
112
112
  var CollapsiblePrimitive__namespace = /*#__PURE__*/_interopNamespace(CollapsiblePrimitive);
113
- var ToggleGroupPrimitive__namespace = /*#__PURE__*/_interopNamespace(ToggleGroupPrimitive);
114
- var TogglePrimitive__namespace = /*#__PURE__*/_interopNamespace(TogglePrimitive);
115
113
  var DialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(DialogPrimitive);
116
114
  var ContextMenuPrimitive__namespace = /*#__PURE__*/_interopNamespace(ContextMenuPrimitive);
117
115
  var SeparatorPrimitive__namespace = /*#__PURE__*/_interopNamespace(SeparatorPrimitive);
116
+ var ToggleGroupPrimitive__namespace = /*#__PURE__*/_interopNamespace(ToggleGroupPrimitive);
117
+ var TogglePrimitive__namespace = /*#__PURE__*/_interopNamespace(TogglePrimitive);
118
118
  var Image2__default = /*#__PURE__*/_interopDefault(Image2);
119
119
  var HoverCardPrimitive__namespace = /*#__PURE__*/_interopNamespace(HoverCardPrimitive);
120
120
  var MenubarPrimitive__namespace = /*#__PURE__*/_interopNamespace(MenubarPrimitive);
@@ -7761,7 +7761,7 @@ function CardDescription({ className, ...props }) {
7761
7761
  "div",
7762
7762
  {
7763
7763
  "data-slot": "card-description",
7764
- className: cn("text-sm text-muted-foreground", className),
7764
+ className: cn("text-base text-muted-foreground", className),
7765
7765
  ...props
7766
7766
  }
7767
7767
  );
@@ -15035,8 +15035,7 @@ function getPairRating(contrastRatio) {
15035
15035
  }
15036
15036
  function getPreferredForegroundTone(backgroundTone) {
15037
15037
  if (backgroundTone <= 200) return 800;
15038
- if (backgroundTone <= 500) return 700;
15039
- if (backgroundTone <= 650) return 250;
15038
+ if (backgroundTone <= 500) return 600;
15040
15039
  return 200;
15041
15040
  }
15042
15041
  function isPreferredDirection(backgroundTone, foregroundTone, preferredForegroundTone) {
@@ -15208,154 +15207,95 @@ function getWhiteForegroundPair(background) {
15208
15207
  function supportsWhiteForegroundPreview(background) {
15209
15208
  return background.tone >= MIN_DARK_BACKGROUND_TONE_FOR_WHITE;
15210
15209
  }
15211
- function getPairingColorValue(color2, format) {
15212
- return getColorValue(color2, format);
15213
- }
15214
- var styles3 = {
15215
- base: [
15216
- // Base
15217
- "inline-flex items-center justify-center gap-2 rounded-sm text-sm font-medium bg-transparent transition-all whitespace-nowrap cursor-pointer border-(--toggle-border) [--toggle-border:var(--color-grey-200)] text-grey-800",
15218
- // States
15219
- "data-[state=on]:bg-grey-100 data-[state=on]:text-grey-850",
15220
- // Hover
15221
- "hover:bg-grey-100 hover:text-grey-850",
15222
- // Focus
15223
- "focus:outline focus:outline-2 focus:outline-offset-0 focus:outline-(--toggle-border)",
15224
- // Dark mode
15225
- "dark:text-white",
15226
- // Dark mode states
15227
- "dark:data-[state=on]:bg-white/10 dark:data-[state=on]:text-white",
15228
- // Dark mode hover
15229
- "dark:hover:bg-white/10 dark:hover:text-white",
15230
- // Disabled
15231
- "disabled:pointer-events-none disabled:opacity-50",
15232
- // Icon
15233
- '[&_svg]:pointer-events-none [&_svg:not([class*="size-"])]:size-4 [&_svg]:shrink-0',
15234
- // Aria invalid
15235
- "aria-invalid:ring-destructive/20 aria-invalid:border-destructive",
15236
- // Aria invalid dark mode
15237
- "dark:aria-invalid:ring-destructive/40"
15238
- ],
15239
- outline: [
15240
- // Base
15241
- "text-grey-800 border [--toggle-border:var(--color-grey-300)]",
15242
- // States
15243
- "hover:[--toggle-border:var(--color-grey-400)]",
15244
- // Dark mode
15245
- "dark:[--toggle-border:white]/40",
15246
- // Dark mode states
15247
- "dark:hover:[--toggle-border:white]/50",
15248
- // Data on
15249
- "data-[state=on]:bg-primary-800/10"
15250
- ]
15251
- };
15252
- var toggleVariants = classVarianceAuthority.cva(styles3.base, {
15253
- variants: {
15254
- variant: {
15255
- ghost: "",
15256
- outline: clsx12__default.default(styles3.outline)
15257
- },
15258
- size: {
15259
- default: "h-9 px-2 min-w-9",
15260
- sm: "h-8 px-1.5 min-w-8",
15261
- lg: "h-10 px-2.5 min-w-10"
15262
- }
15263
- },
15264
- defaultVariants: {
15265
- variant: "ghost",
15266
- size: "default"
15210
+ var SluggerContext = React5__namespace.default.createContext(null);
15211
+ function flattenText(nodes) {
15212
+ if (nodes == null || typeof nodes === "boolean") return "";
15213
+ if (typeof nodes === "string" || typeof nodes === "number" || typeof nodes === "bigint")
15214
+ return String(nodes);
15215
+ if (Array.isArray(nodes)) return nodes.map(flattenText).join("");
15216
+ if (React5__namespace.default.isValidElement(nodes)) {
15217
+ return flattenText(nodes.props.children);
15267
15218
  }
15268
- });
15269
- function Toggle({
15270
- className,
15271
- variant,
15272
- size,
15273
- ...props
15274
- }) {
15275
- return /* @__PURE__ */ jsxRuntime.jsx(
15276
- TogglePrimitive__namespace.Root,
15277
- {
15278
- "data-slot": "toggle",
15279
- className: cn(toggleVariants({ variant, size, className })),
15280
- ...props
15281
- }
15282
- );
15219
+ return "";
15283
15220
  }
15284
- var ToggleGroupContext = React5__namespace.createContext({
15285
- size: "default",
15286
- variant: "ghost"
15287
- });
15288
- function ToggleGroup({
15289
- className,
15290
- variant,
15291
- size,
15292
- children,
15293
- ...props
15294
- }) {
15295
- return /* @__PURE__ */ jsxRuntime.jsx(
15296
- ToggleGroupPrimitive__namespace.Root,
15297
- {
15298
- "data-slot": "toggle-group",
15299
- "data-variant": variant,
15300
- "data-size": size,
15301
- className: cn(
15302
- "group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
15303
- className
15304
- ),
15305
- ...props,
15306
- children: /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupContext.Provider, { value: { variant, size }, children })
15307
- }
15308
- );
15221
+ function baseSlug(input) {
15222
+ return input.toLowerCase().trim().replace(/[\s\W]+/g, "-").replace(/^-+|-+$/g, "");
15309
15223
  }
15310
- function ToggleGroupItem({
15224
+ function Heading({
15311
15225
  className,
15226
+ trim = "normal",
15227
+ size = 1,
15228
+ level = 1,
15229
+ display = false,
15230
+ id: idProp,
15312
15231
  children,
15313
- variant,
15314
- size,
15315
15232
  ...props
15316
15233
  }) {
15317
- const context = React5__namespace.useContext(ToggleGroupContext);
15234
+ const Tag = `h${level}`;
15235
+ const slugger = React5.useContext(SluggerContext);
15236
+ const headingSizeClasses = {
15237
+ 1: "text-[calc(var(--heading-font-size-1)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-52)] tracking-[calc(var(--heading-letter-spacing-2)_+_var(--heading-letter-spacing))]",
15238
+ 2: "text-[calc(var(--heading-font-size-2)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-44)] tracking-[calc(var(--heading-letter-spacing-2)_+_var(--heading-letter-spacing))]",
15239
+ 3: "text-[calc(var(--heading-font-size-3)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-40)] tracking-[calc(var(--heading-letter-spacing-2)_+_var(--heading-letter-spacing))]",
15240
+ 4: "text-[calc(var(--heading-font-size-4)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-36)] tracking-[calc(var(--heading-letter-spacing-1)_+_var(--heading-letter-spacing))]",
15241
+ 5: "text-[calc(var(--heading-font-size-5)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-32)] tracking-[calc(var(--heading-letter-spacing-1)_+_var(--heading-letter-spacing))]",
15242
+ 6: "text-[calc(var(--heading-font-size-6)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-28)] tracking-[calc(var(--letter-spacing-0)_+_var(--heading-letter-spacing))]"
15243
+ };
15244
+ const displaySizeClasses = {
15245
+ 1: "text-[calc(var(--display-font-size-1)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-96)] tracking-[calc(var(--heading-letter-spacing-3)_+_var(--heading-letter-spacing))]",
15246
+ 2: "text-[calc(var(--display-font-size-2)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-60)] tracking-[calc(var(--heading-letter-spacing-2)_+_var(--heading-letter-spacing))]",
15247
+ 3: "text-[calc(var(--display-font-size-3)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-52)] tracking-[calc(var(--heading-letter-spacing-2)_+_var(--heading-letter-spacing))]",
15248
+ 4: "text-[calc(var(--display-font-size-4)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-44)] tracking-[calc(var(--heading-letter-spacing-2)_+_var(--heading-letter-spacing))]"
15249
+ };
15250
+ const sizeClass = display ? displaySizeClasses[size] : headingSizeClasses[size];
15251
+ const trimClasses = {
15252
+ normal: ["before:content-none after:content-none"],
15253
+ start: [
15254
+ 'before:content-[""] before:table after:content-none',
15255
+ "before:mb-[calc(var(--leading-trim-start,var(--default-leading-trim-start))-var(--line-height,calc(1em*var(--default-line-height)))/2)]"
15256
+ ],
15257
+ end: [
15258
+ 'before:content-none after:content-[""] after:table',
15259
+ "after:mt-[calc(var(--leading-trim-end,var(--default-leading-trim-end))-var(--line-height,calc(1em*var(--default-line-height)))/2)]"
15260
+ ],
15261
+ both: [
15262
+ 'before:content-[""] before:table after:content-[""] after:table',
15263
+ "before:mb-[calc(var(--leading-trim-start,var(--default-leading-trim-start))-var(--line-height,calc(1em*var(--default-line-height)))/2)]",
15264
+ "after:mt-[calc(var(--leading-trim-end,var(--default-leading-trim-end))-var(--line-height,calc(1em*var(--default-line-height)))/2)]"
15265
+ ]
15266
+ };
15267
+ const computedId = React5.useMemo(() => {
15268
+ if (idProp) return idProp;
15269
+ const text = flattenText(children);
15270
+ if (!text) return void 0;
15271
+ const base = baseSlug(text);
15272
+ return slugger ? slugger.slug(base) : base;
15273
+ }, [idProp, children, slugger]);
15318
15274
  return /* @__PURE__ */ jsxRuntime.jsx(
15319
- ToggleGroupPrimitive__namespace.Item,
15275
+ Tag,
15320
15276
  {
15321
- "data-slot": "toggle-group-item",
15322
- "data-variant": context.variant || variant,
15323
- "data-size": context.size || size,
15324
- className: cn(
15325
- toggleVariants({
15326
- variant: context.variant || variant,
15327
- size: context.size || size
15328
- }),
15329
- "min-w-0 shrink-0 rounded-none shadow-none first:rounded-l-sm last:rounded-r-sm focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
15330
- className
15331
- ),
15332
15277
  ...props,
15278
+ id: computedId,
15279
+ "data-anchor": true,
15280
+ className: clsx12__default.default(
15281
+ className,
15282
+ trimClasses[trim],
15283
+ "m-0",
15284
+ "leading-[var(--line-height)] font-[var(--heading-font-family)] font-bold",
15285
+ "[--leading-trim-end:var(--heading-leading-trim-end)] [--leading-trim-start:var(--heading-leading-trim-start)]",
15286
+ "text-primary-800 dark:text-white",
15287
+ sizeClass
15288
+ ),
15333
15289
  children
15334
15290
  }
15335
15291
  );
15336
15292
  }
15337
- function FormatToggle({ format, setFormat }) {
15338
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
15339
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Format:" }),
15340
- /* @__PURE__ */ jsxRuntime.jsxs(
15341
- ToggleGroup,
15342
- {
15343
- type: "single",
15344
- value: format,
15345
- onValueChange: (value) => value && setFormat(value),
15346
- children: [
15347
- /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupItem, { value: "hex", "aria-label": "HEX format", children: "HEX" }),
15348
- /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupItem, { value: "rgb", "aria-label": "RGB format", children: "RGB" }),
15349
- /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupItem, { value: "hsl", "aria-label": "HSL format", children: "HSL" }),
15350
- /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupItem, { value: "oklch", "aria-label": "OKLCH format", children: "OKLCH" })
15351
- ]
15352
- }
15353
- )
15354
- ] });
15355
- }
15356
- var AAA_NORMAL_TEXT_THRESHOLD = "7.0:1 or higher for text below 18pt, or below 14pt bold";
15357
- var AAA_LARGE_TEXT_THRESHOLD = "4.5:1 or higher for text at 18pt and above, or 14pt and above bold";
15293
+ var AAA_NORMAL_TEXT_THRESHOLD = "7.0:1 for text below the WCAG large-text threshold. Sentence-case body copy should generally stay at 16px+ unless it is microcopy.";
15294
+ var AAA_LARGE_TEXT_THRESHOLD = "4.5:1 for WCAG large text: 24px+, or 18.5px+ bold";
15358
15295
  var PREFERRED_BACKGROUND_TONES = [400, 600, 200, 800, 100, 50];
15296
+ var DEFAULT_VISIBLE_FORMATS = ["hex", "rgb", "hsl", "oklch"];
15297
+ var DEFAULT_INITIAL_BACKGROUND_TOKEN = "nsw-blue-800";
15298
+ var DEFAULT_INITIAL_PAIR_ID = "nsw-blue-800:nsw-blue-200";
15359
15299
  function getToneFromToken(token) {
15360
15300
  if (!token) return null;
15361
15301
  const match = token.match(/-(\d+)$/);
@@ -15378,9 +15318,80 @@ function getFamilySelectorLabel(family, themeCategory, selectionRole) {
15378
15318
  const preferredTone = selectionRole === "primary colour" ? 800 : 600;
15379
15319
  return family.colors.find((color2) => color2.tone === preferredTone)?.name ?? family.label;
15380
15320
  }
15321
+ function getActivePaletteFamilyLabel(family, themeCategory, paletteRole) {
15322
+ if (themeCategory !== "aboriginal") {
15323
+ return family.label;
15324
+ }
15325
+ const preferredTone = paletteRole === "primary" ? 800 : paletteRole === "accent" ? 600 : 800;
15326
+ return family.colors.find((color2) => color2.tone === preferredTone)?.name ?? family.label;
15327
+ }
15381
15328
  function isWhiteForegroundPair(pair) {
15382
15329
  return pair.foreground.token === "white";
15383
15330
  }
15331
+ function getWhiteForegroundGuidance(pair) {
15332
+ if (pair.passes.aaaText) {
15333
+ return "White is approved for headings, body copy, and calls to action on this background.";
15334
+ }
15335
+ if (pair.passes.aaaLarge) {
15336
+ return "Use white only for WCAG large text on this background, such as headings at 24px+ or bold text at 18.5px+. Keep sentence-case body copy at 16px+ and use a darker recommended foreground instead.";
15337
+ }
15338
+ return "Do not use white on this background. Choose one of the recommended foregrounds below instead.";
15339
+ }
15340
+ function getPreviewGuidance(pair, isRecommended) {
15341
+ if (!isWhiteForegroundPair(pair)) {
15342
+ return "Use only AAA-recommended combinations across your selected primary, accent, and grey families.";
15343
+ }
15344
+ if (isRecommended) {
15345
+ return "Use white text on dark colour only when it meets AAA for headings, body copy, and calls to action.";
15346
+ }
15347
+ return getWhiteForegroundGuidance(pair);
15348
+ }
15349
+ function getPairingColorDisplayName(color2) {
15350
+ return color2.name ?? color2.token;
15351
+ }
15352
+ function WorkflowStep({
15353
+ step,
15354
+ title,
15355
+ description,
15356
+ level = 2,
15357
+ variant = "section",
15358
+ className
15359
+ }) {
15360
+ if (variant === "card") {
15361
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-4", className), children: [
15362
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex size-9 items-center justify-center rounded-full bg-primary-800 text-sm font-semibold text-white dark:bg-primary-700", children: step }),
15363
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
15364
+ /* @__PURE__ */ jsxRuntime.jsx(
15365
+ Heading,
15366
+ {
15367
+ level,
15368
+ size: 5,
15369
+ className: "text-primary-800 dark:text-white",
15370
+ trim: "normal",
15371
+ children: title
15372
+ }
15373
+ ),
15374
+ description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-base/6 text-muted-foreground", children: description }) : null
15375
+ ] })
15376
+ ] });
15377
+ }
15378
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("space-y-4", className), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-4", children: [
15379
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-0.5 flex size-9 shrink-0 items-center justify-center rounded-full bg-primary-800 text-sm font-semibold text-white dark:bg-primary-700", children: step }),
15380
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 space-y-3", children: [
15381
+ /* @__PURE__ */ jsxRuntime.jsx(
15382
+ Heading,
15383
+ {
15384
+ level,
15385
+ size: 4,
15386
+ className: "text-primary-800 dark:text-white",
15387
+ trim: "normal",
15388
+ children: title
15389
+ }
15390
+ ),
15391
+ description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-base/6 text-muted-foreground", children: description }) : null
15392
+ ] })
15393
+ ] }) });
15394
+ }
15384
15395
  function getPreferredPairForBackground(pairs, preferredPairId) {
15385
15396
  if (preferredPairId) {
15386
15397
  const preferredPair = pairs.find((pair) => pair.id === preferredPairId);
@@ -15446,15 +15457,22 @@ function getInitialPairingState(searchParams) {
15446
15457
  const backgroundParam = searchParams.get("background");
15447
15458
  const themeCategory = paletteParam === "brand" || paletteParam === "aboriginal" ? paletteParam : "brand";
15448
15459
  const context = getPairingContext(themeCategory, primaryParam, accentParam);
15460
+ const shouldUseDefaultBrandExample = !backgroundParam && !pairParam && themeCategory === "brand" && context.primary.key === "blue" && context.accent.key === "red";
15461
+ const defaultBackgroundToken = shouldUseDefaultBrandExample ? context.backgrounds.some(
15462
+ (background) => background.token === DEFAULT_INITIAL_BACKGROUND_TOKEN
15463
+ ) ? DEFAULT_INITIAL_BACKGROUND_TOKEN : null : null;
15464
+ const defaultPairId = shouldUseDefaultBrandExample && defaultBackgroundToken && context.pairsByBackground[defaultBackgroundToken]?.some(
15465
+ (pair) => pair.id === DEFAULT_INITIAL_PAIR_ID
15466
+ ) ? DEFAULT_INITIAL_PAIR_ID : null;
15449
15467
  const pairBackgroundToken = context.recommendedPairs.find((pair) => pair.id === pairParam)?.background.token ?? null;
15450
15468
  const selectedBackgroundToken = resolveBackgroundToken(
15451
15469
  context,
15452
- backgroundParam ?? pairBackgroundToken,
15453
- getToneFromToken(backgroundParam ?? pairBackgroundToken)
15470
+ backgroundParam ?? pairBackgroundToken ?? defaultBackgroundToken,
15471
+ getToneFromToken(backgroundParam ?? pairBackgroundToken ?? defaultBackgroundToken)
15454
15472
  );
15455
15473
  const selectedPairId = getPreferredPairForBackground(
15456
15474
  context.pairsByBackground[selectedBackgroundToken] ?? [],
15457
- pairParam
15475
+ pairParam ?? defaultPairId
15458
15476
  )?.id ?? "";
15459
15477
  return {
15460
15478
  accentKey: context.accent.key,
@@ -15472,6 +15490,13 @@ function PairPreview({
15472
15490
  const whiteForeground = isWhiteForegroundPair(pair);
15473
15491
  const statusLabel = isRecommended ? "Pass" : pair.passes.aaaLarge ? "Large text only" : "Example only";
15474
15492
  const StatusIcon = isRecommended ? Icons.check : Icons.info;
15493
+ const fauxButtonStyle = {
15494
+ "--btn-bg": pair.foreground.hex,
15495
+ "--btn-border": pair.foreground.hex,
15496
+ "--btn-text": pair.background.hex,
15497
+ "--btn-icon": pair.background.hex,
15498
+ "--btn-hover-overlay": pair.background.hex
15499
+ };
15475
15500
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "gap-0 overflow-hidden py-0", children: /* @__PURE__ */ jsxRuntime.jsx(
15476
15501
  "div",
15477
15502
  {
@@ -15517,7 +15542,20 @@ function PairPreview({
15517
15542
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold tracking-[0.22em] uppercase", children: whiteForeground && !isRecommended ? "White on colour example" : whiteForeground ? "White on colour" : "Colour on colour" }),
15518
15543
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
15519
15544
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "max-w-lg text-4xl leading-none font-bold text-current sm:text-5xl", children: "Pair colour with confidence." }),
15520
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-md text-sm/6 sm:text-base/7", children: whiteForeground ? isRecommended ? "Use white text on dark colour only when it meets AAA for headings, body copy, and calls to action." : "This white text example is included for reference on approved dark backgrounds. Check the result card below before using it for normal or large text." : "Use only AAA-recommended combinations across your selected primary, accent, and grey families." })
15545
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-md text-base/6 sm:text-base/7", children: getPreviewGuidance(pair, isRecommended) }),
15546
+ /* @__PURE__ */ jsxRuntime.jsx(
15547
+ "span",
15548
+ {
15549
+ "aria-hidden": "true",
15550
+ "data-variant": "solid",
15551
+ className: cn(
15552
+ buttonVariants({ variant: "solid", size: "default" }),
15553
+ "pointer-events-none cursor-default px-6 text-[16px] font-[700] select-none sm:px-6 sm:text-[16px] sm:font-[700]"
15554
+ ),
15555
+ style: fauxButtonStyle,
15556
+ children: "Get started"
15557
+ }
15558
+ )
15521
15559
  ] })
15522
15560
  ] }),
15523
15561
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
@@ -15575,7 +15613,7 @@ function PreviewFallbackCard({
15575
15613
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold tracking-[0.22em] uppercase", children: "Approved background" }),
15576
15614
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
15577
15615
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "max-w-lg text-4xl leading-none font-bold text-current sm:text-5xl", children: "Pair colour with confidence." }),
15578
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-md text-sm/6 sm:text-base/7", children: "This approved background tone does not currently have a recommended AAA foreground in this tool." })
15616
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-md text-base/6 sm:text-base/7", children: "This approved background tone does not currently have a recommended AAA foreground in this tool. Choose another approved background tone to continue." })
15579
15617
  ] })
15580
15618
  ] }),
15581
15619
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
@@ -15591,22 +15629,57 @@ function PreviewFallbackCard({
15591
15629
  }
15592
15630
  function PairDetailCard({
15593
15631
  color: color2,
15594
- format,
15595
- role
15632
+ role,
15633
+ visibleFormats
15596
15634
  }) {
15597
15635
  const [, copyToClipboardRaw] = usehooks.useCopyToClipboard();
15598
15636
  const [copiedField, setCopiedField] = React5.useState(null);
15599
- const valueLabel = format.toUpperCase();
15600
- const formattedValue = getPairingColorValue(color2, format);
15637
+ const copiedFieldTimeoutRef = React5.useRef(null);
15638
+ const hasDisplayTone = color2.token !== "white";
15639
+ const formatRows = visibleFormats.map((format) => ({
15640
+ key: format,
15641
+ label: format.toUpperCase(),
15642
+ value: color2[format],
15643
+ mono: true,
15644
+ copyable: true
15645
+ }));
15646
+ const valueRows = [
15647
+ { key: "token", label: "Token", value: color2.token, mono: true, copyable: true },
15648
+ {
15649
+ key: "tone",
15650
+ label: "Tone",
15651
+ value: hasDisplayTone ? String(color2.tone) : "Not applicable",
15652
+ mono: false,
15653
+ copyable: hasDisplayTone
15654
+ },
15655
+ ...formatRows
15656
+ ];
15657
+ React5.useEffect(() => {
15658
+ return () => {
15659
+ if (copiedFieldTimeoutRef.current) {
15660
+ clearTimeout(copiedFieldTimeoutRef.current);
15661
+ }
15662
+ };
15663
+ }, []);
15601
15664
  const copyField = (field) => {
15602
- const fieldValue = field === "token" ? color2.token : field === "tone" ? String(color2.tone) : formattedValue;
15665
+ if (field === "tone" && !hasDisplayTone) {
15666
+ return;
15667
+ }
15668
+ const fieldValue = valueRows.find((row) => row.key === field)?.value;
15669
+ if (!fieldValue) return;
15603
15670
  copyToClipboardRaw(fieldValue);
15604
15671
  setCopiedField(field);
15605
- const toastLabel = field === "token" ? "Token" : field === "tone" ? "Tone" : `${valueLabel} value`;
15672
+ const toastLabel = valueRows.find((row) => row.key === field)?.label ?? "Value";
15606
15673
  sonner.toast(`${toastLabel} copied to clipboard`, {
15607
15674
  duration: 2e3
15608
15675
  });
15609
- setTimeout(() => setCopiedField(null), 2e3);
15676
+ if (copiedFieldTimeoutRef.current) {
15677
+ clearTimeout(copiedFieldTimeoutRef.current);
15678
+ }
15679
+ copiedFieldTimeoutRef.current = setTimeout(() => {
15680
+ setCopiedField(null);
15681
+ copiedFieldTimeoutRef.current = null;
15682
+ }, 2e3);
15610
15683
  };
15611
15684
  const renderCopyButton = (field, srLabel, className) => /* @__PURE__ */ jsxRuntime.jsxs(
15612
15685
  Button2,
@@ -15621,43 +15694,40 @@ function PairDetailCard({
15621
15694
  ]
15622
15695
  }
15623
15696
  );
15624
- return /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: "gap-4", children: [
15625
- /* @__PURE__ */ jsxRuntime.jsx(CardHeader, { className: "gap-3 border-b", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
15697
+ return /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: "gap-0 pb-0", children: [
15698
+ /* @__PURE__ */ jsxRuntime.jsxs(CardHeader, { className: "gap-4 border-b", children: [
15699
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
15700
+ /* @__PURE__ */ jsxRuntime.jsx(CardTitle, { children: role }),
15701
+ /* @__PURE__ */ jsxRuntime.jsx(CardDescription, { children: color2.name ?? color2.token })
15702
+ ] }),
15626
15703
  /* @__PURE__ */ jsxRuntime.jsx(
15627
15704
  "div",
15628
15705
  {
15629
- className: "size-14 rounded-sm border border-grey-200 shadow-sm dark:border-grey-700",
15706
+ className: "h-14 w-full rounded-sm border border-grey-200 shadow-sm dark:border-grey-700",
15630
15707
  style: { backgroundColor: color2.hex }
15631
15708
  }
15632
- ),
15633
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
15634
- /* @__PURE__ */ jsxRuntime.jsx(CardTitle, { children: role }),
15635
- /* @__PURE__ */ jsxRuntime.jsx(CardDescription, { children: color2.name ?? color2.token })
15636
- ] })
15637
- ] }) }),
15638
- /* @__PURE__ */ jsxRuntime.jsxs(CardContent, { className: "grid gap-4", children: [
15639
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
15640
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
15641
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold tracking-[0.16em] text-muted-foreground uppercase", children: "Token" }),
15642
- renderCopyButton("token", `Copy ${role.toLowerCase()} token`)
15643
- ] }),
15644
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-mono text-base break-all", children: color2.token })
15645
- ] }),
15646
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
15647
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
15648
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold tracking-[0.16em] text-muted-foreground uppercase", children: "Tone" }),
15649
- renderCopyButton("tone", `Copy ${role.toLowerCase()} tone`)
15650
- ] }),
15651
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: color2.tone })
15709
+ )
15710
+ ] }),
15711
+ /* @__PURE__ */ jsxRuntime.jsx(CardContent, { className: "px-0 pb-3", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-grey-100 dark:divide-grey-800", children: valueRows.map((row) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-4 px-6 py-3", children: [
15712
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
15713
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold tracking-[0.16em] text-muted-foreground uppercase", children: row.label }),
15714
+ /* @__PURE__ */ jsxRuntime.jsx(
15715
+ "p",
15716
+ {
15717
+ className: cn(
15718
+ "mt-2 min-w-0 text-base text-foreground",
15719
+ row.mono && "font-mono text-sm break-all sm:text-base"
15720
+ ),
15721
+ children: row.value
15722
+ }
15723
+ )
15652
15724
  ] }),
15653
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
15654
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
15655
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold tracking-[0.16em] text-muted-foreground uppercase", children: valueLabel }),
15656
- renderCopyButton("value", `Copy ${role.toLowerCase()} ${valueLabel} value`)
15657
- ] }),
15658
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-mono text-sm break-all", children: formattedValue })
15659
- ] })
15660
- ] })
15725
+ row.copyable ? renderCopyButton(
15726
+ row.key,
15727
+ `Copy ${role.toLowerCase()} ${row.label.toLowerCase()}`,
15728
+ "mt-0.5"
15729
+ ) : null
15730
+ ] }, row.key)) }) })
15661
15731
  ] });
15662
15732
  }
15663
15733
  function ComplianceRow({
@@ -15665,31 +15735,40 @@ function ComplianceRow({
15665
15735
  passes,
15666
15736
  threshold
15667
15737
  }) {
15668
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-4 rounded-sm border border-grey-200 px-4 py-3 dark:border-grey-700", children: [
15669
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
15670
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: label }),
15671
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: threshold })
15672
- ] }),
15673
- /* @__PURE__ */ jsxRuntime.jsxs(
15674
- "span",
15675
- {
15676
- className: cn(
15677
- "inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-xs font-semibold",
15678
- passes ? "bg-success-50 text-success-800 dark:bg-success-950/30 dark:text-success-200" : "bg-danger-50 text-danger-800 dark:bg-danger-950/30 dark:text-danger-200"
15679
- ),
15680
- children: [
15681
- passes ? /* @__PURE__ */ jsxRuntime.jsx(Icons.check, { "data-slot": "icon", className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(Icons.close, { "data-slot": "icon", className: "size-4" }),
15682
- passes ? "Pass" : "Fail"
15683
- ]
15684
- }
15685
- )
15686
- ] });
15738
+ return /* @__PURE__ */ jsxRuntime.jsxs(
15739
+ "div",
15740
+ {
15741
+ className: cn(
15742
+ "flex items-center justify-between gap-4 rounded-sm border px-4 py-3",
15743
+ passes ? "border-success-200 bg-success-50 dark:border-success-900/60 dark:bg-success-950/20" : "border-danger-200 bg-danger-50 dark:border-danger-900/60 dark:bg-danger-950/20"
15744
+ ),
15745
+ children: [
15746
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
15747
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: label }),
15748
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: threshold })
15749
+ ] }),
15750
+ /* @__PURE__ */ jsxRuntime.jsxs(
15751
+ "span",
15752
+ {
15753
+ className: cn(
15754
+ "inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-xs font-semibold",
15755
+ passes ? "bg-success-50 text-success-800 dark:bg-success-950/30 dark:text-success-200" : "bg-danger-50 text-danger-800 dark:bg-danger-950/30 dark:text-danger-200"
15756
+ ),
15757
+ children: [
15758
+ passes ? /* @__PURE__ */ jsxRuntime.jsx(Icons.check, { "data-slot": "icon", className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(Icons.close, { "data-slot": "icon", className: "size-4" }),
15759
+ passes ? "Pass" : "Fail"
15760
+ ]
15761
+ }
15762
+ )
15763
+ ]
15764
+ }
15765
+ );
15687
15766
  }
15688
15767
  function PairComplianceCard({ pair }) {
15689
15768
  return /* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
15690
15769
  /* @__PURE__ */ jsxRuntime.jsxs(CardHeader, { className: "gap-2 border-b", children: [
15691
15770
  /* @__PURE__ */ jsxRuntime.jsx(CardTitle, { className: "text-base", children: "AAA compliance" }),
15692
- /* @__PURE__ */ jsxRuntime.jsx(CardDescription, { children: "Only pairs that pass AAA normal-text contrast are suggested here. Evaluation uses the raw contrast ratio, not the rounded display value." })
15771
+ /* @__PURE__ */ jsxRuntime.jsx(CardDescription, { children: "This card shows the contrast result for the example above. Evaluation uses the raw contrast ratio, not the rounded display value." })
15693
15772
  ] }),
15694
15773
  /* @__PURE__ */ jsxRuntime.jsxs(CardContent, { className: "space-y-3", children: [
15695
15774
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-sm border border-grey-200 bg-grey-50 px-4 py-3 dark:border-grey-700 dark:bg-grey-900/60", children: [
@@ -15718,71 +15797,6 @@ function PairComplianceCard({ pair }) {
15718
15797
  ] })
15719
15798
  ] });
15720
15799
  }
15721
- function WhiteTextExampleCard({ pair }) {
15722
- return /* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
15723
- /* @__PURE__ */ jsxRuntime.jsxs(CardHeader, { className: "gap-2 border-b", children: [
15724
- /* @__PURE__ */ jsxRuntime.jsx(CardTitle, { className: "text-base", children: "White text example" }),
15725
- /* @__PURE__ */ jsxRuntime.jsx(CardDescription, { children: "For approved dark backgrounds, white text may be suitable for large text. It is only recommended in this tool when it also passes AAA normal text." })
15726
- ] }),
15727
- /* @__PURE__ */ jsxRuntime.jsxs(CardContent, { className: "grid gap-6 xl:grid-cols-[minmax(0,1.1fr)_minmax(18rem,0.9fr)]", children: [
15728
- /* @__PURE__ */ jsxRuntime.jsx(
15729
- "div",
15730
- {
15731
- className: "rounded-sm border border-grey-200 p-6 dark:border-grey-700",
15732
- style: {
15733
- backgroundColor: pair.background.hex,
15734
- color: pair.foreground.hex
15735
- },
15736
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
15737
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
15738
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-lg leading-tight font-semibold", children: "Large text example" }),
15739
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm/6", children: [
15740
- "White text on ",
15741
- pair.background.token
15742
- ] })
15743
- ] }),
15744
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 text-sm/6", children: [
15745
- /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
15746
- "AAA normal text: ",
15747
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: pair.passes.aaaText ? "pass" : "fail" })
15748
- ] }),
15749
- /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
15750
- "AAA large text: ",
15751
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: pair.passes.aaaLarge ? "pass" : "fail" })
15752
- ] })
15753
- ] }),
15754
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm/6", children: "Normal text means body text and smaller headings below 18pt, or below 14pt when bold. Large text means 18pt and above, or 14pt and above when bold." })
15755
- ] })
15756
- }
15757
- ),
15758
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
15759
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-sm border border-grey-200 bg-grey-50 px-4 py-3 dark:border-grey-700 dark:bg-grey-900/60", children: [
15760
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold tracking-[0.16em] text-muted-foreground uppercase", children: "Contrast ratio" }),
15761
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-1 text-2xl font-semibold", children: [
15762
- pair.contrastRatio.toFixed(2),
15763
- ":1"
15764
- ] })
15765
- ] }),
15766
- /* @__PURE__ */ jsxRuntime.jsx(
15767
- ComplianceRow,
15768
- {
15769
- label: "AAA normal text",
15770
- passes: pair.passes.aaaText,
15771
- threshold: AAA_NORMAL_TEXT_THRESHOLD
15772
- }
15773
- ),
15774
- /* @__PURE__ */ jsxRuntime.jsx(
15775
- ComplianceRow,
15776
- {
15777
- label: "AAA large text",
15778
- passes: pair.passes.aaaLarge,
15779
- threshold: AAA_LARGE_TEXT_THRESHOLD
15780
- }
15781
- )
15782
- ] })
15783
- ] })
15784
- ] });
15785
- }
15786
15800
  function ColorFamilySelector({
15787
15801
  families,
15788
15802
  label,
@@ -15792,8 +15806,8 @@ function ColorFamilySelector({
15792
15806
  onSelect
15793
15807
  }) {
15794
15808
  const swatchTone = selectionRole === "primary colour" ? 800 : 600;
15795
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
15796
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-medium", children: label }),
15809
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0", children: [
15810
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 3, size: 6, className: "mb-4 text-foreground", trim: "normal", children: label }),
15797
15811
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2 lg:grid-cols-4 xl:grid-cols-5", children: families.map((family) => {
15798
15812
  const isSelected = family.key === selectedKey;
15799
15813
  const familySelectorLabel = getFamilySelectorLabel(family, themeCategory, selectionRole);
@@ -15825,38 +15839,64 @@ function ColorFamilySelector({
15825
15839
  }) })
15826
15840
  ] });
15827
15841
  }
15828
- function ColorPairingToolContent() {
15842
+ function ColorPairingToolContent({ visibleFormats }) {
15829
15843
  const searchParams = navigation.useSearchParams();
15830
- const {
15831
- accentKey: initialAccentKey,
15832
- primaryKey: initialPrimaryKey,
15833
- selectedBackgroundToken: initialSelectedBackgroundToken,
15834
- selectedPairId: initialSelectedPairId,
15835
- themeCategory: initialThemeCategory
15836
- } = getInitialPairingState(searchParams);
15837
- const [themeCategory, setThemeCategory] = React5.useState(initialThemeCategory);
15838
- const [format, setFormat] = React5.useState("hex");
15839
- const [primaryFamilyKey, setPrimaryFamilyKey] = React5.useState(initialPrimaryKey);
15840
- const [accentFamilyKey, setAccentFamilyKey] = React5.useState(initialAccentKey);
15844
+ const [initialState] = React5.useState(() => getInitialPairingState(searchParams));
15845
+ const [themeCategory, setThemeCategory] = React5.useState(initialState.themeCategory);
15846
+ const [primaryFamilyKey, setPrimaryFamilyKey] = React5.useState(initialState.primaryKey);
15847
+ const [accentFamilyKey, setAccentFamilyKey] = React5.useState(initialState.accentKey);
15848
+ const [, copyShareLinkRaw] = usehooks.useCopyToClipboard();
15849
+ const [copiedShareLink, setCopiedShareLink] = React5.useState(false);
15850
+ const copiedShareLinkTimeoutRef = React5.useRef(null);
15841
15851
  const [selectedBackgroundToken, setSelectedBackgroundToken] = React5.useState(
15842
- initialSelectedBackgroundToken
15843
- );
15844
- const [selectedPairId, setSelectedPairId] = React5.useState(initialSelectedPairId);
15845
- const themeFamilies = getPairingFamilies(themeCategory);
15846
- const context = getPairingContext(themeCategory, primaryFamilyKey, accentFamilyKey);
15847
- const selectableFamilies = themeFamilies.filter((family) => family.key !== context.grey.key);
15848
- const selectedBackground = context.backgrounds.find((background) => background.token === selectedBackgroundToken) ?? context.backgrounds[0] ?? null;
15849
- const selectedBackgroundPairs = selectedBackground ? context.pairsByBackground[selectedBackground.token] ?? [] : [];
15850
- const selectedPair = getPreferredPairForBackground(selectedBackgroundPairs, selectedPairId);
15851
- const whiteForegroundExample = selectedBackground && supportsWhiteForegroundPreview(selectedBackground) ? getWhiteForegroundPair(selectedBackground) : null;
15852
+ initialState.selectedBackgroundToken
15853
+ );
15854
+ const [selectedPairId, setSelectedPairId] = React5.useState(initialState.selectedPairId);
15855
+ const themeFamilies = React5.useMemo(() => getPairingFamilies(themeCategory), [themeCategory]);
15856
+ const context = React5.useMemo(
15857
+ () => getPairingContext(themeCategory, primaryFamilyKey, accentFamilyKey),
15858
+ [themeCategory, primaryFamilyKey, accentFamilyKey]
15859
+ );
15860
+ const selectableFamilies = React5.useMemo(
15861
+ () => themeFamilies.filter((family) => family.key !== context.grey.key),
15862
+ [themeFamilies, context.grey.key]
15863
+ );
15864
+ const selectableAccentFamilies = React5.useMemo(
15865
+ () => selectableFamilies.filter((family) => family.key !== context.primary.key),
15866
+ [selectableFamilies, context.primary.key]
15867
+ );
15868
+ const selectedBackground = React5.useMemo(
15869
+ () => context.backgrounds.find((background) => background.token === selectedBackgroundToken) ?? context.backgrounds[0] ?? null,
15870
+ [context.backgrounds, selectedBackgroundToken]
15871
+ );
15872
+ const selectedBackgroundPairs = React5.useMemo(
15873
+ () => selectedBackground ? context.pairsByBackground[selectedBackground.token] ?? [] : [],
15874
+ [context.pairsByBackground, selectedBackground]
15875
+ );
15876
+ const selectedPair = React5.useMemo(
15877
+ () => getPreferredPairForBackground(selectedBackgroundPairs, selectedPairId),
15878
+ [selectedBackgroundPairs, selectedPairId]
15879
+ );
15880
+ const whiteForegroundExample = React5.useMemo(
15881
+ () => selectedBackground && supportsWhiteForegroundPreview(selectedBackground) ? getWhiteForegroundPair(selectedBackground) : null,
15882
+ [selectedBackground]
15883
+ );
15852
15884
  const previewPair = selectedPair ?? whiteForegroundExample ?? null;
15853
- const showWhiteForegroundExample = Boolean(whiteForegroundExample) && (!selectedPair || selectedPair.foreground.token !== "white");
15854
15885
  const detailForeground = selectedPair?.foreground ?? whiteForegroundExample?.foreground ?? null;
15855
- const familySummary = [context.primary.label, context.accent.label, context.grey.label].join(
15856
- " + "
15886
+ const familySummary = React5.useMemo(
15887
+ () => [context.primary.label, context.accent.label, context.grey.label].join(" + "),
15888
+ [context.primary.label, context.accent.label, context.grey.label]
15857
15889
  );
15890
+ React5.useEffect(() => {
15891
+ return () => {
15892
+ if (copiedShareLinkTimeoutRef.current) {
15893
+ clearTimeout(copiedShareLinkTimeoutRef.current);
15894
+ }
15895
+ };
15896
+ }, []);
15858
15897
  const updateUrlParams = (nextThemeCategory, nextPrimaryKey, nextAccentKey, nextSelectedBackgroundToken, nextSelectedPairId) => {
15859
15898
  const params = new URLSearchParams(window.location.search);
15899
+ params.delete("family");
15860
15900
  params.set("palette", nextThemeCategory);
15861
15901
  params.set("primary", nextPrimaryKey);
15862
15902
  params.set("accent", nextAccentKey);
@@ -15958,125 +15998,212 @@ function ColorPairingToolContent() {
15958
15998
  /* @__PURE__ */ jsxRuntime.jsx(CardDescription, { children: "No approved tones are available for the current palette." })
15959
15999
  ] }) });
15960
16000
  }
15961
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
15962
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between", children: [
15963
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
15964
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Palette:" }),
15965
- /* @__PURE__ */ jsxRuntime.jsx(
15966
- SegmentedControl,
15967
- {
15968
- value: themeCategory,
15969
- onValueChange: (value) => handleThemeCategoryChange(value),
15970
- children: /* @__PURE__ */ jsxRuntime.jsxs(SegmentedControlList, { className: "w-full sm:w-fit", children: [
15971
- /* @__PURE__ */ jsxRuntime.jsx(SegmentedControlTrigger, { value: "brand", children: "Brand palette" }),
15972
- /* @__PURE__ */ jsxRuntime.jsx(SegmentedControlTrigger, { value: "aboriginal", children: "Aboriginal palette" })
15973
- ] })
15974
- }
15975
- )
15976
- ] }),
15977
- /* @__PURE__ */ jsxRuntime.jsx(FormatToggle, { format, setFormat })
15978
- ] }),
15979
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6", children: [
16001
+ const handleCopyShareLink = () => {
16002
+ copyShareLinkRaw(window.location.href);
16003
+ setCopiedShareLink(true);
16004
+ sonner.toast("Colour pairing link copied to clipboard", {
16005
+ duration: 2e3
16006
+ });
16007
+ if (copiedShareLinkTimeoutRef.current) {
16008
+ clearTimeout(copiedShareLinkTimeoutRef.current);
16009
+ }
16010
+ copiedShareLinkTimeoutRef.current = setTimeout(() => {
16011
+ setCopiedShareLink(false);
16012
+ copiedShareLinkTimeoutRef.current = null;
16013
+ }, 2e3);
16014
+ };
16015
+ const activePaletteEntries = [
16016
+ {
16017
+ key: `primary-${context.primary.key}`,
16018
+ label: "Primary",
16019
+ family: context.primary,
16020
+ familyLabel: getActivePaletteFamilyLabel(context.primary, themeCategory, "primary"),
16021
+ swatchTone: 800
16022
+ },
16023
+ {
16024
+ key: `accent-${context.accent.key}`,
16025
+ label: "Accent",
16026
+ family: context.accent,
16027
+ familyLabel: getActivePaletteFamilyLabel(context.accent, themeCategory, "accent"),
16028
+ swatchTone: 600
16029
+ },
16030
+ {
16031
+ key: `grey-${context.grey.key}`,
16032
+ label: "Grey",
16033
+ family: context.grey,
16034
+ familyLabel: getActivePaletteFamilyLabel(context.grey, themeCategory, "grey"),
16035
+ swatchTone: 800
16036
+ }
16037
+ ];
16038
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-8", children: [
16039
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
15980
16040
  /* @__PURE__ */ jsxRuntime.jsx(
15981
- ColorFamilySelector,
15982
- {
15983
- label: "Primary colour",
15984
- families: selectableFamilies,
15985
- selectedKey: context.primary.key,
15986
- selectionRole: "primary colour",
15987
- themeCategory,
15988
- onSelect: handlePrimaryColorChange
16041
+ WorkflowStep,
16042
+ {
16043
+ step: 1,
16044
+ title: "Select your palette",
16045
+ description: "Choose the palette family you want to work from.",
16046
+ level: 2
15989
16047
  }
15990
16048
  ),
16049
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between", children: [
16050
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
16051
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Palette:" }),
16052
+ /* @__PURE__ */ jsxRuntime.jsx(
16053
+ SegmentedControl,
16054
+ {
16055
+ value: themeCategory,
16056
+ onValueChange: (value) => handleThemeCategoryChange(value),
16057
+ children: /* @__PURE__ */ jsxRuntime.jsxs(SegmentedControlList, { className: "w-full sm:w-fit", children: [
16058
+ /* @__PURE__ */ jsxRuntime.jsx(SegmentedControlTrigger, { value: "brand", children: "Brand palette" }),
16059
+ /* @__PURE__ */ jsxRuntime.jsx(SegmentedControlTrigger, { value: "aboriginal", children: "Aboriginal palette" })
16060
+ ] })
16061
+ }
16062
+ )
16063
+ ] }),
16064
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-3", children: /* @__PURE__ */ jsxRuntime.jsxs(Button2, { variant: "outline", size: "sm", onClick: handleCopyShareLink, children: [
16065
+ copiedShareLink ? /* @__PURE__ */ jsxRuntime.jsx(Icons.check, { "data-slot": "icon", className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(Icons.content_copy, { "data-slot": "icon", className: "size-4" }),
16066
+ copiedShareLink ? "Link copied" : "Copy link"
16067
+ ] }) })
16068
+ ] })
16069
+ ] }),
16070
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
15991
16071
  /* @__PURE__ */ jsxRuntime.jsx(
15992
- ColorFamilySelector,
15993
- {
15994
- label: "Accent colour",
15995
- families: selectableFamilies.filter((family) => family.key !== context.primary.key),
15996
- selectedKey: context.accent.key,
15997
- selectionRole: "accent colour",
15998
- themeCategory,
15999
- onSelect: handleAccentColorChange
16072
+ WorkflowStep,
16073
+ {
16074
+ step: 2,
16075
+ title: "Choose your colours",
16076
+ description: "Pick a primary and accent colour. Grey is added automatically.",
16077
+ level: 2
16000
16078
  }
16001
- )
16079
+ ),
16080
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6", children: [
16081
+ /* @__PURE__ */ jsxRuntime.jsx(
16082
+ ColorFamilySelector,
16083
+ {
16084
+ label: "Primary colour",
16085
+ families: selectableFamilies,
16086
+ selectedKey: context.primary.key,
16087
+ selectionRole: "primary colour",
16088
+ themeCategory,
16089
+ onSelect: handlePrimaryColorChange
16090
+ }
16091
+ ),
16092
+ /* @__PURE__ */ jsxRuntime.jsx(
16093
+ ColorFamilySelector,
16094
+ {
16095
+ label: "Accent colour",
16096
+ families: selectableAccentFamilies,
16097
+ selectedKey: context.accent.key,
16098
+ selectionRole: "accent colour",
16099
+ themeCategory,
16100
+ onSelect: handleAccentColorChange
16101
+ }
16102
+ )
16103
+ ] })
16002
16104
  ] }),
16003
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 text-sm", children: [
16004
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Included families:" }),
16005
- context.allFamilies.map((family) => /* @__PURE__ */ jsxRuntime.jsxs(
16105
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-sm border border-grey-200 bg-grey-50 px-4 py-4 dark:border-grey-700 dark:bg-grey-900/40", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
16106
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold tracking-[0.14em] text-foreground uppercase", children: "Active palette" }),
16107
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-2 text-sm text-foreground", children: activePaletteEntries.map(({ family, familyLabel, key, label, swatchTone }) => /* @__PURE__ */ jsxRuntime.jsxs(
16006
16108
  "span",
16007
16109
  {
16008
- className: "inline-flex items-center gap-2 rounded-full border border-grey-200 px-3 py-1 text-muted-foreground dark:border-grey-700",
16110
+ className: "inline-flex items-center gap-2 rounded-sm border border-grey-200 bg-background px-3 py-2 dark:border-grey-700",
16009
16111
  children: [
16010
16112
  /* @__PURE__ */ jsxRuntime.jsx(
16011
16113
  "span",
16012
16114
  {
16013
16115
  className: "size-2.5 rounded-full",
16014
- style: { backgroundColor: getFamilySwatchColor(family) }
16116
+ style: { backgroundColor: getFamilySwatchColor(family, swatchTone) }
16015
16117
  }
16016
16118
  ),
16017
- family.label
16119
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium", children: [
16120
+ label,
16121
+ ":"
16122
+ ] }),
16123
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: familyLabel })
16018
16124
  ]
16019
16125
  },
16020
- family.key
16021
- ))
16022
- ] }),
16126
+ key
16127
+ )) })
16128
+ ] }) }),
16023
16129
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid items-start gap-6 xl:grid-cols-[minmax(0,1.5fr)_minmax(18rem,0.72fr)]", children: [
16024
16130
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
16025
- previewPair ? /* @__PURE__ */ jsxRuntime.jsx(
16026
- PairPreview,
16027
- {
16028
- familySummary,
16029
- isRecommended: Boolean(selectedPair),
16030
- pair: previewPair
16031
- }
16032
- ) : /* @__PURE__ */ jsxRuntime.jsx(
16033
- PreviewFallbackCard,
16034
- {
16035
- familySummary,
16036
- selectedBackground
16037
- }
16038
- ),
16039
16131
  /* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
16040
- /* @__PURE__ */ jsxRuntime.jsxs(CardHeader, { className: "gap-2 border-b", children: [
16041
- /* @__PURE__ */ jsxRuntime.jsx(CardTitle, { className: "text-base", children: "Approved backgrounds" }),
16042
- /* @__PURE__ */ jsxRuntime.jsx(CardDescription, { children: "Choose from your selected primary, accent, and grey background tones. Filled chips have at least one recommended AAA foreground. Foregrounds are limited to white and tones 50, 200, 400, 600, and 800." })
16132
+ /* @__PURE__ */ jsxRuntime.jsxs(CardHeader, { className: "gap-4 border-b", children: [
16133
+ /* @__PURE__ */ jsxRuntime.jsx(
16134
+ WorkflowStep,
16135
+ {
16136
+ step: 3,
16137
+ title: "Pick a background",
16138
+ description: "Select an approved background tone from your primary, accent, or grey families.",
16139
+ level: 3,
16140
+ variant: "card"
16141
+ }
16142
+ ),
16143
+ /* @__PURE__ */ jsxRuntime.jsx(CardDescription, { children: "The centre dot shows the recommended foreground colour. A white slash means there is no recommended AAA foreground in this tool." })
16043
16144
  ] }),
16044
16145
  /* @__PURE__ */ jsxRuntime.jsxs(CardContent, { className: "space-y-5", children: [
16045
- context.backgroundGroups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
16046
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 text-sm", children: [
16047
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: group.label }),
16048
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: group.family.label })
16146
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-3 rounded-sm border border-grey-200 bg-grey-50 p-4 sm:grid-cols-2 dark:border-grey-700 dark:bg-grey-900/40", children: [
16147
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 rounded-sm border border-grey-200 bg-background px-3 py-3 dark:border-grey-700", children: [
16148
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative block size-8 rounded-[4px] border border-grey-400 bg-background dark:border-grey-500", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute top-1/2 left-1/2 size-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-grey-800 dark:bg-grey-100" }) }),
16149
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
16150
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground", children: "Recommended foreground available" }),
16151
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "The centre dot marks the recommended foreground colour." })
16152
+ ] })
16049
16153
  ] }),
16050
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-2 sm:grid-cols-6", children: group.backgrounds.map((background) => {
16051
- const pairs = context.pairsByBackground[background.token] ?? [];
16052
- const preferredPair = selectedBackground.token === background.token ? selectedPair ?? getPreferredPairForBackground(pairs) : getPreferredPairForBackground(pairs);
16053
- const hasWhiteExample = supportsWhiteForegroundPreview(background);
16054
- const isSelected = selectedBackground.token === background.token;
16055
- const ariaLabel = pairs.length > 0 ? `Select ${background.token} background, recommended with ${preferredPair?.foreground.token ?? "an AAA foreground"}` : hasWhiteExample ? `Select ${background.token} background, white text example available` : `Select ${background.token} background, no recommended foreground in this tool`;
16056
- return /* @__PURE__ */ jsxRuntime.jsx(
16057
- "button",
16058
- {
16059
- type: "button",
16060
- "aria-label": ariaLabel,
16061
- "aria-pressed": isSelected,
16062
- onClick: () => handleBackgroundChange(background.token),
16063
- className: cn(
16064
- "group relative h-12 rounded-sm border border-grey-200 transition-transform hover:scale-[1.03] dark:border-grey-700",
16065
- isSelected && "ring-2 ring-primary-500 ring-offset-2 ring-offset-background"
16066
- ),
16067
- style: { backgroundColor: background.hex },
16068
- children: preferredPair ? /* @__PURE__ */ jsxRuntime.jsx(
16069
- "span",
16070
- {
16071
- className: "absolute top-1/2 left-1/2 size-3 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white/30 shadow-sm",
16072
- style: { backgroundColor: preferredPair.foreground.hex }
16073
- }
16074
- ) : null
16075
- },
16076
- background.token
16077
- );
16078
- }) })
16079
- ] }, group.key)),
16154
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 rounded-sm border border-dashed border-grey-300 bg-background px-3 py-3 dark:border-grey-600", children: [
16155
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative block size-8 rounded-[4px] border-2 border-dashed border-grey-400 bg-grey-500 dark:border-grey-500 dark:bg-grey-700", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute top-1/2 left-1/2 h-0.5 w-5 -translate-x-1/2 -translate-y-1/2 rotate-45 rounded-full bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.28)]" }) }),
16156
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
16157
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground", children: "No recommended AAA foreground" }),
16158
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "A white slash marks swatches that do not currently have a recommended AAA foreground in this tool." })
16159
+ ] })
16160
+ ] })
16161
+ ] }),
16162
+ context.backgroundGroups.map((group) => {
16163
+ const groupFamilyLabel = selectedBackground.familyKey === group.family.key ? selectedBackground.name ?? selectedBackground.token : group.family.label;
16164
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
16165
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 text-sm", children: [
16166
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: group.label }),
16167
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: groupFamilyLabel })
16168
+ ] }),
16169
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-2 sm:grid-cols-6", children: group.backgrounds.map((background) => {
16170
+ const pairs = context.pairsByBackground[background.token] ?? [];
16171
+ const hasRecommendedForeground = pairs.length > 0;
16172
+ const preferredPair = selectedBackground.token === background.token ? selectedPair ?? getPreferredPairForBackground(pairs) : getPreferredPairForBackground(pairs);
16173
+ const hasWhiteExample = supportsWhiteForegroundPreview(background);
16174
+ const isSelected = selectedBackground.token === background.token;
16175
+ const ariaLabel = pairs.length > 0 ? `Select ${background.token} background, recommended with ${preferredPair?.foreground.token ?? "an AAA foreground"}` : hasWhiteExample ? `Select ${background.token} background, white text example available` : `Select ${background.token} background, no recommended foreground in this tool`;
16176
+ return /* @__PURE__ */ jsxRuntime.jsxs(
16177
+ "button",
16178
+ {
16179
+ type: "button",
16180
+ "aria-label": ariaLabel,
16181
+ "aria-pressed": isSelected,
16182
+ onClick: () => handleBackgroundChange(background.token),
16183
+ className: cn(
16184
+ "group relative h-12 rounded-sm border transition-all",
16185
+ hasRecommendedForeground && "border-grey-200 hover:scale-[1.03] hover:shadow-sm dark:border-grey-700",
16186
+ !hasRecommendedForeground && hasWhiteExample && "border-grey-300/80 hover:scale-[1.01] dark:border-grey-600/80",
16187
+ !hasRecommendedForeground && !hasWhiteExample && "border-dashed border-grey-300/70 hover:scale-[1.01] dark:border-grey-600/70",
16188
+ isSelected && "ring-2 ring-primary-500 ring-offset-2 ring-offset-background"
16189
+ ),
16190
+ style: { backgroundColor: background.hex },
16191
+ children: [
16192
+ preferredPair ? /* @__PURE__ */ jsxRuntime.jsx(
16193
+ "span",
16194
+ {
16195
+ className: "absolute top-1/2 left-1/2 size-3 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white/30 shadow-sm",
16196
+ style: { backgroundColor: preferredPair.foreground.hex }
16197
+ }
16198
+ ) : null,
16199
+ !hasRecommendedForeground ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute top-1/2 left-1/2 h-0.5 w-6 -translate-x-1/2 -translate-y-1/2 rotate-45 rounded-full bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.28)]" }) : null
16200
+ ]
16201
+ },
16202
+ background.token
16203
+ );
16204
+ }) })
16205
+ ] }, group.key);
16206
+ }),
16080
16207
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-4 text-sm text-muted-foreground", children: [
16081
16208
  /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
16082
16209
  "Selected background:",
@@ -16091,9 +16218,37 @@ function ColorPairingToolContent() {
16091
16218
  ] })
16092
16219
  ] })
16093
16220
  ] }),
16221
+ previewPair ? /* @__PURE__ */ jsxRuntime.jsx(
16222
+ PairPreview,
16223
+ {
16224
+ familySummary,
16225
+ isRecommended: Boolean(selectedPair),
16226
+ pair: previewPair
16227
+ }
16228
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
16229
+ PreviewFallbackCard,
16230
+ {
16231
+ familySummary,
16232
+ selectedBackground
16233
+ }
16234
+ ),
16094
16235
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [
16095
- /* @__PURE__ */ jsxRuntime.jsx(PairDetailCard, { color: selectedBackground, format, role: "Background" }),
16096
- detailForeground ? /* @__PURE__ */ jsxRuntime.jsx(PairDetailCard, { color: detailForeground, format, role: "Foreground" }) : /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: "gap-4", children: [
16236
+ /* @__PURE__ */ jsxRuntime.jsx(
16237
+ PairDetailCard,
16238
+ {
16239
+ color: selectedBackground,
16240
+ role: "Background",
16241
+ visibleFormats
16242
+ }
16243
+ ),
16244
+ detailForeground ? /* @__PURE__ */ jsxRuntime.jsx(
16245
+ PairDetailCard,
16246
+ {
16247
+ color: detailForeground,
16248
+ role: "Foreground",
16249
+ visibleFormats
16250
+ }
16251
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: "gap-4", children: [
16097
16252
  /* @__PURE__ */ jsxRuntime.jsx(CardHeader, { className: "gap-3 border-b", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
16098
16253
  /* @__PURE__ */ jsxRuntime.jsx(CardTitle, { children: "Foreground" }),
16099
16254
  /* @__PURE__ */ jsxRuntime.jsx(CardDescription, { children: "No recommended foreground available" })
@@ -16101,21 +16256,27 @@ function ColorPairingToolContent() {
16101
16256
  /* @__PURE__ */ jsxRuntime.jsx(CardContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "Select another approved tone or review the recommended foregrounds for the same background." }) })
16102
16257
  ] })
16103
16258
  ] }),
16104
- selectedPair ? /* @__PURE__ */ jsxRuntime.jsx(PairComplianceCard, { pair: selectedPair }) : null,
16105
- showWhiteForegroundExample && whiteForegroundExample ? /* @__PURE__ */ jsxRuntime.jsx(WhiteTextExampleCard, { pair: whiteForegroundExample }) : null
16259
+ previewPair ? /* @__PURE__ */ jsxRuntime.jsx(PairComplianceCard, { pair: previewPair }) : null
16106
16260
  ] }),
16107
16261
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-6", children: /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: "h-fit", children: [
16108
- /* @__PURE__ */ jsxRuntime.jsxs(CardHeader, { className: "gap-2 border-b", children: [
16109
- /* @__PURE__ */ jsxRuntime.jsx(CardTitle, { className: "text-base", children: "Recommended foregrounds" }),
16262
+ /* @__PURE__ */ jsxRuntime.jsxs(CardHeader, { className: "gap-4 border-b", children: [
16263
+ /* @__PURE__ */ jsxRuntime.jsx(
16264
+ WorkflowStep,
16265
+ {
16266
+ step: 4,
16267
+ title: "Choose a foreground",
16268
+ description: `Review the AAA combinations available for ${selectedBackground.token}.`,
16269
+ level: 3,
16270
+ variant: "card"
16271
+ }
16272
+ ),
16110
16273
  /* @__PURE__ */ jsxRuntime.jsxs(CardDescription, { children: [
16111
- "AAA combinations for ",
16112
- selectedBackground.token,
16113
- ", drawn from",
16114
- " ",
16274
+ "Recommended foregrounds are drawn from ",
16115
16275
  context.primary.label,
16116
- ", ",
16276
+ ",",
16277
+ " ",
16117
16278
  context.accent.label,
16118
- ", Grey, and white using only foreground tones 50, 200, 400, 600, and 800."
16279
+ ", Grey, and white where it meets AAA."
16119
16280
  ] })
16120
16281
  ] }),
16121
16282
  /* @__PURE__ */ jsxRuntime.jsx(CardContent, { className: "grid gap-3", children: selectedBackgroundPairs.length > 0 ? selectedBackgroundPairs.map((pair) => {
@@ -16124,7 +16285,7 @@ function ColorPairingToolContent() {
16124
16285
  "button",
16125
16286
  {
16126
16287
  type: "button",
16127
- "aria-label": `Use ${pair.foreground.token} on ${pair.background.token}`,
16288
+ "aria-label": `Use ${getPairingColorDisplayName(pair.foreground)} on ${getPairingColorDisplayName(pair.background)}`,
16128
16289
  "aria-pressed": isActive,
16129
16290
  onClick: () => handlePairChange(pair.id),
16130
16291
  className: cn(
@@ -16133,32 +16294,43 @@ function ColorPairingToolContent() {
16133
16294
  ),
16134
16295
  children: [
16135
16296
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-4", children: [
16136
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
16137
- /* @__PURE__ */ jsxRuntime.jsx(
16138
- "div",
16139
- {
16140
- className: "size-11 rounded-sm border border-grey-200 shadow-sm dark:border-grey-700",
16141
- style: { backgroundColor: pair.background.hex }
16142
- }
16143
- ),
16144
- /* @__PURE__ */ jsxRuntime.jsx(
16145
- "div",
16146
- {
16147
- className: "size-11 rounded-sm border border-grey-200 shadow-sm dark:border-grey-700",
16148
- style: { backgroundColor: pair.foreground.hex }
16149
- }
16150
- )
16297
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
16298
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 text-[0.65rem] font-semibold tracking-[0.14em] text-muted-foreground uppercase", children: [
16299
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-11 text-center", children: "BG" }),
16300
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-11 text-center", children: "FG" })
16301
+ ] }),
16302
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
16303
+ /* @__PURE__ */ jsxRuntime.jsx(
16304
+ "div",
16305
+ {
16306
+ className: "size-11 rounded-sm border border-grey-200 shadow-sm dark:border-grey-700",
16307
+ style: {
16308
+ backgroundColor: pair.background.hex
16309
+ }
16310
+ }
16311
+ ),
16312
+ /* @__PURE__ */ jsxRuntime.jsx(
16313
+ "div",
16314
+ {
16315
+ className: "size-11 rounded-sm border border-grey-200 shadow-sm dark:border-grey-700",
16316
+ style: {
16317
+ backgroundColor: pair.foreground.hex
16318
+ }
16319
+ }
16320
+ )
16321
+ ] })
16151
16322
  ] }),
16152
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 rounded-full border border-grey-200 px-2.5 py-1 text-xs font-semibold text-muted-foreground dark:border-grey-700", children: [
16323
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 rounded-full border border-success-200 bg-success-50 px-2.5 py-1 text-xs font-semibold text-success-800 dark:border-success-900/60 dark:bg-success-950/20 dark:text-success-200", children: [
16153
16324
  /* @__PURE__ */ jsxRuntime.jsx(Icons.contrast, { "data-slot": "icon", className: "size-4" }),
16154
16325
  pair.rating
16155
16326
  ] })
16156
16327
  ] }),
16157
16328
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 space-y-1", children: [
16158
16329
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "font-medium text-foreground", children: [
16159
- pair.background.token,
16160
- " / ",
16161
- pair.foreground.token
16330
+ getPairingColorDisplayName(pair.background),
16331
+ " /",
16332
+ " ",
16333
+ getPairingColorDisplayName(pair.foreground)
16162
16334
  ] }),
16163
16335
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground", children: [
16164
16336
  pair.foreground.familyLabel,
@@ -16179,8 +16351,11 @@ function ColorPairingToolContent() {
16179
16351
  ] }) })
16180
16352
  ] });
16181
16353
  }
16182
- function ColorPairingTool() {
16183
- return /* @__PURE__ */ jsxRuntime.jsx(React5.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(ColorPairingToolLoading, {}), children: /* @__PURE__ */ jsxRuntime.jsx(ColorPairingToolContent, {}) });
16354
+ function ColorPairingTool({
16355
+ visibleFormats = DEFAULT_VISIBLE_FORMATS
16356
+ } = {}) {
16357
+ const normalizedVisibleFormats = [...new Set(visibleFormats)];
16358
+ return /* @__PURE__ */ jsxRuntime.jsx(React5.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(ColorPairingToolLoading, {}), children: /* @__PURE__ */ jsxRuntime.jsx(ColorPairingToolContent, { visibleFormats: normalizedVisibleFormats }) });
16184
16359
  }
16185
16360
  function ColorSwatches({ theme: theme2, format, viewMode }) {
16186
16361
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -18661,93 +18836,152 @@ function FormMessage({ className, ...props }) {
18661
18836
  }
18662
18837
  );
18663
18838
  }
18664
-
18665
- // package.json
18666
- var package_default = {
18667
- version: "1.99.0"};
18668
- var SluggerContext = React5__namespace.default.createContext(null);
18669
- function flattenText(nodes) {
18670
- if (nodes == null || typeof nodes === "boolean") return "";
18671
- if (typeof nodes === "string" || typeof nodes === "number" || typeof nodes === "bigint")
18672
- return String(nodes);
18673
- if (Array.isArray(nodes)) return nodes.map(flattenText).join("");
18674
- if (React5__namespace.default.isValidElement(nodes)) {
18675
- return flattenText(nodes.props.children);
18839
+ var styles3 = {
18840
+ base: [
18841
+ // Base
18842
+ "inline-flex items-center justify-center gap-2 rounded-sm text-sm font-medium bg-transparent transition-all whitespace-nowrap cursor-pointer border-(--toggle-border) [--toggle-border:var(--color-grey-200)] text-grey-800",
18843
+ // States
18844
+ "data-[state=on]:bg-grey-100 data-[state=on]:text-grey-850",
18845
+ // Hover
18846
+ "hover:bg-grey-100 hover:text-grey-850",
18847
+ // Focus
18848
+ "focus:outline focus:outline-2 focus:outline-offset-0 focus:outline-(--toggle-border)",
18849
+ // Dark mode
18850
+ "dark:text-white",
18851
+ // Dark mode states
18852
+ "dark:data-[state=on]:bg-white/10 dark:data-[state=on]:text-white",
18853
+ // Dark mode hover
18854
+ "dark:hover:bg-white/10 dark:hover:text-white",
18855
+ // Disabled
18856
+ "disabled:pointer-events-none disabled:opacity-50",
18857
+ // Icon
18858
+ '[&_svg]:pointer-events-none [&_svg:not([class*="size-"])]:size-4 [&_svg]:shrink-0',
18859
+ // Aria invalid
18860
+ "aria-invalid:ring-destructive/20 aria-invalid:border-destructive",
18861
+ // Aria invalid dark mode
18862
+ "dark:aria-invalid:ring-destructive/40"
18863
+ ],
18864
+ outline: [
18865
+ // Base
18866
+ "text-grey-800 border [--toggle-border:var(--color-grey-300)]",
18867
+ // States
18868
+ "hover:[--toggle-border:var(--color-grey-400)]",
18869
+ // Dark mode
18870
+ "dark:[--toggle-border:white]/40",
18871
+ // Dark mode states
18872
+ "dark:hover:[--toggle-border:white]/50",
18873
+ // Data on
18874
+ "data-[state=on]:bg-primary-800/10"
18875
+ ]
18876
+ };
18877
+ var toggleVariants = classVarianceAuthority.cva(styles3.base, {
18878
+ variants: {
18879
+ variant: {
18880
+ ghost: "",
18881
+ outline: clsx12__default.default(styles3.outline)
18882
+ },
18883
+ size: {
18884
+ default: "h-9 px-2 min-w-9",
18885
+ sm: "h-8 px-1.5 min-w-8",
18886
+ lg: "h-10 px-2.5 min-w-10"
18887
+ }
18888
+ },
18889
+ defaultVariants: {
18890
+ variant: "ghost",
18891
+ size: "default"
18676
18892
  }
18677
- return "";
18678
- }
18679
- function baseSlug(input) {
18680
- return input.toLowerCase().trim().replace(/[\s\W]+/g, "-").replace(/^-+|-+$/g, "");
18893
+ });
18894
+ function Toggle({
18895
+ className,
18896
+ variant,
18897
+ size,
18898
+ ...props
18899
+ }) {
18900
+ return /* @__PURE__ */ jsxRuntime.jsx(
18901
+ TogglePrimitive__namespace.Root,
18902
+ {
18903
+ "data-slot": "toggle",
18904
+ className: cn(toggleVariants({ variant, size, className })),
18905
+ ...props
18906
+ }
18907
+ );
18681
18908
  }
18682
- function Heading({
18909
+ var ToggleGroupContext = React5__namespace.createContext({
18910
+ size: "default",
18911
+ variant: "ghost"
18912
+ });
18913
+ function ToggleGroup({
18683
18914
  className,
18684
- trim = "normal",
18685
- size = 1,
18686
- level = 1,
18687
- display = false,
18688
- id: idProp,
18915
+ variant,
18916
+ size,
18689
18917
  children,
18690
18918
  ...props
18691
18919
  }) {
18692
- const Tag = `h${level}`;
18693
- const slugger = React5.useContext(SluggerContext);
18694
- const headingSizeClasses = {
18695
- 1: "text-[calc(var(--heading-font-size-1)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-52)] tracking-[calc(var(--heading-letter-spacing-2)_+_var(--heading-letter-spacing))]",
18696
- 2: "text-[calc(var(--heading-font-size-2)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-44)] tracking-[calc(var(--heading-letter-spacing-2)_+_var(--heading-letter-spacing))]",
18697
- 3: "text-[calc(var(--heading-font-size-3)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-40)] tracking-[calc(var(--heading-letter-spacing-2)_+_var(--heading-letter-spacing))]",
18698
- 4: "text-[calc(var(--heading-font-size-4)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-36)] tracking-[calc(var(--heading-letter-spacing-1)_+_var(--heading-letter-spacing))]",
18699
- 5: "text-[calc(var(--heading-font-size-5)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-32)] tracking-[calc(var(--heading-letter-spacing-1)_+_var(--heading-letter-spacing))]",
18700
- 6: "text-[calc(var(--heading-font-size-6)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-28)] tracking-[calc(var(--letter-spacing-0)_+_var(--heading-letter-spacing))]"
18701
- };
18702
- const displaySizeClasses = {
18703
- 1: "text-[calc(var(--display-font-size-1)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-96)] tracking-[calc(var(--heading-letter-spacing-3)_+_var(--heading-letter-spacing))]",
18704
- 2: "text-[calc(var(--display-font-size-2)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-60)] tracking-[calc(var(--heading-letter-spacing-2)_+_var(--heading-letter-spacing))]",
18705
- 3: "text-[calc(var(--display-font-size-3)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-52)] tracking-[calc(var(--heading-letter-spacing-2)_+_var(--heading-letter-spacing))]",
18706
- 4: "text-[calc(var(--display-font-size-4)_*_var(--heading-font-size-adjust))] leading-[var(--line-height-44)] tracking-[calc(var(--heading-letter-spacing-2)_+_var(--heading-letter-spacing))]"
18707
- };
18708
- const sizeClass = display ? displaySizeClasses[size] : headingSizeClasses[size];
18709
- const trimClasses = {
18710
- normal: ["before:content-none after:content-none"],
18711
- start: [
18712
- 'before:content-[""] before:table after:content-none',
18713
- "before:mb-[calc(var(--leading-trim-start,var(--default-leading-trim-start))-var(--line-height,calc(1em*var(--default-line-height)))/2)]"
18714
- ],
18715
- end: [
18716
- 'before:content-none after:content-[""] after:table',
18717
- "after:mt-[calc(var(--leading-trim-end,var(--default-leading-trim-end))-var(--line-height,calc(1em*var(--default-line-height)))/2)]"
18718
- ],
18719
- both: [
18720
- 'before:content-[""] before:table after:content-[""] after:table',
18721
- "before:mb-[calc(var(--leading-trim-start,var(--default-leading-trim-start))-var(--line-height,calc(1em*var(--default-line-height)))/2)]",
18722
- "after:mt-[calc(var(--leading-trim-end,var(--default-leading-trim-end))-var(--line-height,calc(1em*var(--default-line-height)))/2)]"
18723
- ]
18724
- };
18725
- const computedId = React5.useMemo(() => {
18726
- if (idProp) return idProp;
18727
- const text = flattenText(children);
18728
- if (!text) return void 0;
18729
- const base = baseSlug(text);
18730
- return slugger ? slugger.slug(base) : base;
18731
- }, [idProp, children, slugger]);
18732
18920
  return /* @__PURE__ */ jsxRuntime.jsx(
18733
- Tag,
18921
+ ToggleGroupPrimitive__namespace.Root,
18734
18922
  {
18923
+ "data-slot": "toggle-group",
18924
+ "data-variant": variant,
18925
+ "data-size": size,
18926
+ className: cn(
18927
+ "group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
18928
+ className
18929
+ ),
18735
18930
  ...props,
18736
- id: computedId,
18737
- "data-anchor": true,
18738
- className: clsx12__default.default(
18739
- className,
18740
- trimClasses[trim],
18741
- "m-0",
18742
- "leading-[var(--line-height)] font-[var(--heading-font-family)] font-bold",
18743
- "[--leading-trim-end:var(--heading-leading-trim-end)] [--leading-trim-start:var(--heading-leading-trim-start)]",
18744
- "text-primary-800 dark:text-white",
18745
- sizeClass
18931
+ children: /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupContext.Provider, { value: { variant, size }, children })
18932
+ }
18933
+ );
18934
+ }
18935
+ function ToggleGroupItem({
18936
+ className,
18937
+ children,
18938
+ variant,
18939
+ size,
18940
+ ...props
18941
+ }) {
18942
+ const context = React5__namespace.useContext(ToggleGroupContext);
18943
+ return /* @__PURE__ */ jsxRuntime.jsx(
18944
+ ToggleGroupPrimitive__namespace.Item,
18945
+ {
18946
+ "data-slot": "toggle-group-item",
18947
+ "data-variant": context.variant || variant,
18948
+ "data-size": context.size || size,
18949
+ className: cn(
18950
+ toggleVariants({
18951
+ variant: context.variant || variant,
18952
+ size: context.size || size
18953
+ }),
18954
+ "min-w-0 shrink-0 rounded-none shadow-none first:rounded-l-sm last:rounded-r-sm focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
18955
+ className
18746
18956
  ),
18957
+ ...props,
18747
18958
  children
18748
18959
  }
18749
18960
  );
18750
18961
  }
18962
+ function FormatToggle({ format, setFormat }) {
18963
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
18964
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Format:" }),
18965
+ /* @__PURE__ */ jsxRuntime.jsxs(
18966
+ ToggleGroup,
18967
+ {
18968
+ type: "single",
18969
+ value: format,
18970
+ onValueChange: (value) => value && setFormat(value),
18971
+ children: [
18972
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupItem, { value: "hex", "aria-label": "HEX format", children: "HEX" }),
18973
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupItem, { value: "rgb", "aria-label": "RGB format", children: "RGB" }),
18974
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupItem, { value: "hsl", "aria-label": "HSL format", children: "HSL" }),
18975
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupItem, { value: "oklch", "aria-label": "OKLCH format", children: "OKLCH" })
18976
+ ]
18977
+ }
18978
+ )
18979
+ ] });
18980
+ }
18981
+
18982
+ // package.json
18983
+ var package_default = {
18984
+ version: "1.101.0"};
18751
18985
  function Logo(props) {
18752
18986
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18753
18987
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "NSW Government" }),