@nswds/app 1.100.0 → 1.101.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,39 @@ 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", 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 })
15652
- ] }),
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 })
15709
+ )
15710
+ ] }),
15711
+ /* @__PURE__ */ jsxRuntime.jsx(CardContent, { className: "px-0", children: /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "divide-y divide-grey-100 dark:divide-grey-800", children: valueRows.map((row) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 px-6 py-3", children: [
15712
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-xs font-semibold tracking-[0.16em] text-muted-foreground uppercase", children: row.label }),
15713
+ /* @__PURE__ */ jsxRuntime.jsxs("dd", { className: "flex items-start justify-between gap-4", children: [
15714
+ /* @__PURE__ */ jsxRuntime.jsx(
15715
+ "span",
15716
+ {
15717
+ className: cn(
15718
+ "min-w-0 flex-1 text-base text-foreground",
15719
+ row.mono && "font-mono text-sm break-all sm:text-base"
15720
+ ),
15721
+ children: row.value
15722
+ }
15723
+ ),
15724
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 justify-end", children: row.copyable ? renderCopyButton(
15725
+ row.key,
15726
+ `Copy ${role.toLowerCase()} ${row.label.toLowerCase()}`
15727
+ ) : null })
15659
15728
  ] })
15660
- ] })
15729
+ ] }, row.key)) }) })
15661
15730
  ] });
15662
15731
  }
15663
15732
  function ComplianceRow({
@@ -15665,31 +15734,40 @@ function ComplianceRow({
15665
15734
  passes,
15666
15735
  threshold
15667
15736
  }) {
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
- ] });
15737
+ return /* @__PURE__ */ jsxRuntime.jsxs(
15738
+ "div",
15739
+ {
15740
+ className: cn(
15741
+ "flex items-center justify-between gap-4 rounded-sm border px-4 py-3",
15742
+ 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"
15743
+ ),
15744
+ children: [
15745
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
15746
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: label }),
15747
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: threshold })
15748
+ ] }),
15749
+ /* @__PURE__ */ jsxRuntime.jsxs(
15750
+ "span",
15751
+ {
15752
+ className: cn(
15753
+ "inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-xs font-semibold",
15754
+ 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"
15755
+ ),
15756
+ children: [
15757
+ passes ? /* @__PURE__ */ jsxRuntime.jsx(Icons.check, { "data-slot": "icon", className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(Icons.close, { "data-slot": "icon", className: "size-4" }),
15758
+ passes ? "Pass" : "Fail"
15759
+ ]
15760
+ }
15761
+ )
15762
+ ]
15763
+ }
15764
+ );
15687
15765
  }
15688
15766
  function PairComplianceCard({ pair }) {
15689
15767
  return /* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
15690
15768
  /* @__PURE__ */ jsxRuntime.jsxs(CardHeader, { className: "gap-2 border-b", children: [
15691
15769
  /* @__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." })
15770
+ /* @__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
15771
  ] }),
15694
15772
  /* @__PURE__ */ jsxRuntime.jsxs(CardContent, { className: "space-y-3", children: [
15695
15773
  /* @__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 +15796,6 @@ function PairComplianceCard({ pair }) {
15718
15796
  ] })
15719
15797
  ] });
15720
15798
  }
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
15799
  function ColorFamilySelector({
15787
15800
  families,
15788
15801
  label,
@@ -15792,8 +15805,8 @@ function ColorFamilySelector({
15792
15805
  onSelect
15793
15806
  }) {
15794
15807
  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 }),
15808
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0", children: [
15809
+ /* @__PURE__ */ jsxRuntime.jsx(Heading, { level: 3, size: 6, className: "mb-4 text-foreground", trim: "normal", children: label }),
15797
15810
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2 lg:grid-cols-4 xl:grid-cols-5", children: families.map((family) => {
15798
15811
  const isSelected = family.key === selectedKey;
15799
15812
  const familySelectorLabel = getFamilySelectorLabel(family, themeCategory, selectionRole);
@@ -15825,38 +15838,64 @@ function ColorFamilySelector({
15825
15838
  }) })
15826
15839
  ] });
15827
15840
  }
15828
- function ColorPairingToolContent() {
15841
+ function ColorPairingToolContent({ visibleFormats }) {
15829
15842
  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);
15843
+ const [initialState] = React5.useState(() => getInitialPairingState(searchParams));
15844
+ const [themeCategory, setThemeCategory] = React5.useState(initialState.themeCategory);
15845
+ const [primaryFamilyKey, setPrimaryFamilyKey] = React5.useState(initialState.primaryKey);
15846
+ const [accentFamilyKey, setAccentFamilyKey] = React5.useState(initialState.accentKey);
15847
+ const [, copyShareLinkRaw] = usehooks.useCopyToClipboard();
15848
+ const [copiedShareLink, setCopiedShareLink] = React5.useState(false);
15849
+ const copiedShareLinkTimeoutRef = React5.useRef(null);
15841
15850
  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;
15851
+ initialState.selectedBackgroundToken
15852
+ );
15853
+ const [selectedPairId, setSelectedPairId] = React5.useState(initialState.selectedPairId);
15854
+ const themeFamilies = React5.useMemo(() => getPairingFamilies(themeCategory), [themeCategory]);
15855
+ const context = React5.useMemo(
15856
+ () => getPairingContext(themeCategory, primaryFamilyKey, accentFamilyKey),
15857
+ [themeCategory, primaryFamilyKey, accentFamilyKey]
15858
+ );
15859
+ const selectableFamilies = React5.useMemo(
15860
+ () => themeFamilies.filter((family) => family.key !== context.grey.key),
15861
+ [themeFamilies, context.grey.key]
15862
+ );
15863
+ const selectableAccentFamilies = React5.useMemo(
15864
+ () => selectableFamilies.filter((family) => family.key !== context.primary.key),
15865
+ [selectableFamilies, context.primary.key]
15866
+ );
15867
+ const selectedBackground = React5.useMemo(
15868
+ () => context.backgrounds.find((background) => background.token === selectedBackgroundToken) ?? context.backgrounds[0] ?? null,
15869
+ [context.backgrounds, selectedBackgroundToken]
15870
+ );
15871
+ const selectedBackgroundPairs = React5.useMemo(
15872
+ () => selectedBackground ? context.pairsByBackground[selectedBackground.token] ?? [] : [],
15873
+ [context.pairsByBackground, selectedBackground]
15874
+ );
15875
+ const selectedPair = React5.useMemo(
15876
+ () => getPreferredPairForBackground(selectedBackgroundPairs, selectedPairId),
15877
+ [selectedBackgroundPairs, selectedPairId]
15878
+ );
15879
+ const whiteForegroundExample = React5.useMemo(
15880
+ () => selectedBackground && supportsWhiteForegroundPreview(selectedBackground) ? getWhiteForegroundPair(selectedBackground) : null,
15881
+ [selectedBackground]
15882
+ );
15852
15883
  const previewPair = selectedPair ?? whiteForegroundExample ?? null;
15853
- const showWhiteForegroundExample = Boolean(whiteForegroundExample) && (!selectedPair || selectedPair.foreground.token !== "white");
15854
15884
  const detailForeground = selectedPair?.foreground ?? whiteForegroundExample?.foreground ?? null;
15855
- const familySummary = [context.primary.label, context.accent.label, context.grey.label].join(
15856
- " + "
15885
+ const familySummary = React5.useMemo(
15886
+ () => [context.primary.label, context.accent.label, context.grey.label].join(" + "),
15887
+ [context.primary.label, context.accent.label, context.grey.label]
15857
15888
  );
15889
+ React5.useEffect(() => {
15890
+ return () => {
15891
+ if (copiedShareLinkTimeoutRef.current) {
15892
+ clearTimeout(copiedShareLinkTimeoutRef.current);
15893
+ }
15894
+ };
15895
+ }, []);
15858
15896
  const updateUrlParams = (nextThemeCategory, nextPrimaryKey, nextAccentKey, nextSelectedBackgroundToken, nextSelectedPairId) => {
15859
15897
  const params = new URLSearchParams(window.location.search);
15898
+ params.delete("family");
15860
15899
  params.set("palette", nextThemeCategory);
15861
15900
  params.set("primary", nextPrimaryKey);
15862
15901
  params.set("accent", nextAccentKey);
@@ -15958,125 +15997,212 @@ function ColorPairingToolContent() {
15958
15997
  /* @__PURE__ */ jsxRuntime.jsx(CardDescription, { children: "No approved tones are available for the current palette." })
15959
15998
  ] }) });
15960
15999
  }
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: [
16000
+ const handleCopyShareLink = () => {
16001
+ copyShareLinkRaw(window.location.href);
16002
+ setCopiedShareLink(true);
16003
+ sonner.toast("Colour pairing link copied to clipboard", {
16004
+ duration: 2e3
16005
+ });
16006
+ if (copiedShareLinkTimeoutRef.current) {
16007
+ clearTimeout(copiedShareLinkTimeoutRef.current);
16008
+ }
16009
+ copiedShareLinkTimeoutRef.current = setTimeout(() => {
16010
+ setCopiedShareLink(false);
16011
+ copiedShareLinkTimeoutRef.current = null;
16012
+ }, 2e3);
16013
+ };
16014
+ const activePaletteEntries = [
16015
+ {
16016
+ key: `primary-${context.primary.key}`,
16017
+ label: "Primary",
16018
+ family: context.primary,
16019
+ familyLabel: getActivePaletteFamilyLabel(context.primary, themeCategory, "primary"),
16020
+ swatchTone: 800
16021
+ },
16022
+ {
16023
+ key: `accent-${context.accent.key}`,
16024
+ label: "Accent",
16025
+ family: context.accent,
16026
+ familyLabel: getActivePaletteFamilyLabel(context.accent, themeCategory, "accent"),
16027
+ swatchTone: 600
16028
+ },
16029
+ {
16030
+ key: `grey-${context.grey.key}`,
16031
+ label: "Grey",
16032
+ family: context.grey,
16033
+ familyLabel: getActivePaletteFamilyLabel(context.grey, themeCategory, "grey"),
16034
+ swatchTone: 800
16035
+ }
16036
+ ];
16037
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-8", children: [
16038
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
15980
16039
  /* @__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
16040
+ WorkflowStep,
16041
+ {
16042
+ step: 1,
16043
+ title: "Select your palette",
16044
+ description: "Choose the palette family you want to work from.",
16045
+ level: 2
15989
16046
  }
15990
16047
  ),
16048
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between", children: [
16049
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
16050
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Palette:" }),
16051
+ /* @__PURE__ */ jsxRuntime.jsx(
16052
+ SegmentedControl,
16053
+ {
16054
+ value: themeCategory,
16055
+ onValueChange: (value) => handleThemeCategoryChange(value),
16056
+ children: /* @__PURE__ */ jsxRuntime.jsxs(SegmentedControlList, { className: "w-full sm:w-fit", children: [
16057
+ /* @__PURE__ */ jsxRuntime.jsx(SegmentedControlTrigger, { value: "brand", children: "Brand palette" }),
16058
+ /* @__PURE__ */ jsxRuntime.jsx(SegmentedControlTrigger, { value: "aboriginal", children: "Aboriginal palette" })
16059
+ ] })
16060
+ }
16061
+ )
16062
+ ] }),
16063
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-3", children: /* @__PURE__ */ jsxRuntime.jsxs(Button2, { variant: "outline", size: "sm", onClick: handleCopyShareLink, children: [
16064
+ copiedShareLink ? /* @__PURE__ */ jsxRuntime.jsx(Icons.check, { "data-slot": "icon", className: "size-4" }) : /* @__PURE__ */ jsxRuntime.jsx(Icons.content_copy, { "data-slot": "icon", className: "size-4" }),
16065
+ copiedShareLink ? "Link copied" : "Copy link"
16066
+ ] }) })
16067
+ ] })
16068
+ ] }),
16069
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
15991
16070
  /* @__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
16071
+ WorkflowStep,
16072
+ {
16073
+ step: 2,
16074
+ title: "Choose your colours",
16075
+ description: "Pick a primary and accent colour. Grey is added automatically.",
16076
+ level: 2
16000
16077
  }
16001
- )
16078
+ ),
16079
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6", children: [
16080
+ /* @__PURE__ */ jsxRuntime.jsx(
16081
+ ColorFamilySelector,
16082
+ {
16083
+ label: "Primary colour",
16084
+ families: selectableFamilies,
16085
+ selectedKey: context.primary.key,
16086
+ selectionRole: "primary colour",
16087
+ themeCategory,
16088
+ onSelect: handlePrimaryColorChange
16089
+ }
16090
+ ),
16091
+ /* @__PURE__ */ jsxRuntime.jsx(
16092
+ ColorFamilySelector,
16093
+ {
16094
+ label: "Accent colour",
16095
+ families: selectableAccentFamilies,
16096
+ selectedKey: context.accent.key,
16097
+ selectionRole: "accent colour",
16098
+ themeCategory,
16099
+ onSelect: handleAccentColorChange
16100
+ }
16101
+ )
16102
+ ] })
16002
16103
  ] }),
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(
16104
+ /* @__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: [
16105
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold tracking-[0.14em] text-foreground uppercase", children: "Active palette" }),
16106
+ /* @__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
16107
  "span",
16007
16108
  {
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",
16109
+ className: "inline-flex items-center gap-2 rounded-sm border border-grey-200 bg-background px-3 py-2 dark:border-grey-700",
16009
16110
  children: [
16010
16111
  /* @__PURE__ */ jsxRuntime.jsx(
16011
16112
  "span",
16012
16113
  {
16013
16114
  className: "size-2.5 rounded-full",
16014
- style: { backgroundColor: getFamilySwatchColor(family) }
16115
+ style: { backgroundColor: getFamilySwatchColor(family, swatchTone) }
16015
16116
  }
16016
16117
  ),
16017
- family.label
16118
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium", children: [
16119
+ label,
16120
+ ":"
16121
+ ] }),
16122
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: familyLabel })
16018
16123
  ]
16019
16124
  },
16020
- family.key
16021
- ))
16022
- ] }),
16125
+ key
16126
+ )) })
16127
+ ] }) }),
16023
16128
  /* @__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
16129
  /* @__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
16130
  /* @__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." })
16131
+ /* @__PURE__ */ jsxRuntime.jsxs(CardHeader, { className: "gap-4 border-b", children: [
16132
+ /* @__PURE__ */ jsxRuntime.jsx(
16133
+ WorkflowStep,
16134
+ {
16135
+ step: 3,
16136
+ title: "Pick a background",
16137
+ description: "Select an approved background tone from your primary, accent, or grey families.",
16138
+ level: 3,
16139
+ variant: "card"
16140
+ }
16141
+ ),
16142
+ /* @__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
16143
  ] }),
16044
16144
  /* @__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 })
16145
+ /* @__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: [
16146
+ /* @__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: [
16147
+ /* @__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" }) }),
16148
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
16149
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground", children: "Recommended foreground available" }),
16150
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "The centre dot marks the recommended foreground colour." })
16151
+ ] })
16049
16152
  ] }),
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)),
16153
+ /* @__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: [
16154
+ /* @__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)]" }) }),
16155
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
16156
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground", children: "No recommended AAA foreground" }),
16157
+ /* @__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." })
16158
+ ] })
16159
+ ] })
16160
+ ] }),
16161
+ context.backgroundGroups.map((group) => {
16162
+ const groupFamilyLabel = selectedBackground.familyKey === group.family.key ? selectedBackground.name ?? selectedBackground.token : group.family.label;
16163
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
16164
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 text-sm", children: [
16165
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: group.label }),
16166
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: groupFamilyLabel })
16167
+ ] }),
16168
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-2 sm:grid-cols-6", children: group.backgrounds.map((background) => {
16169
+ const pairs = context.pairsByBackground[background.token] ?? [];
16170
+ const hasRecommendedForeground = pairs.length > 0;
16171
+ const preferredPair = selectedBackground.token === background.token ? selectedPair ?? getPreferredPairForBackground(pairs) : getPreferredPairForBackground(pairs);
16172
+ const hasWhiteExample = supportsWhiteForegroundPreview(background);
16173
+ const isSelected = selectedBackground.token === background.token;
16174
+ 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`;
16175
+ return /* @__PURE__ */ jsxRuntime.jsxs(
16176
+ "button",
16177
+ {
16178
+ type: "button",
16179
+ "aria-label": ariaLabel,
16180
+ "aria-pressed": isSelected,
16181
+ onClick: () => handleBackgroundChange(background.token),
16182
+ className: cn(
16183
+ "group relative h-12 rounded-sm border transition-all",
16184
+ hasRecommendedForeground && "border-grey-200 hover:scale-[1.03] hover:shadow-sm dark:border-grey-700",
16185
+ !hasRecommendedForeground && hasWhiteExample && "border-grey-300/80 hover:scale-[1.01] dark:border-grey-600/80",
16186
+ !hasRecommendedForeground && !hasWhiteExample && "border-dashed border-grey-300/70 hover:scale-[1.01] dark:border-grey-600/70",
16187
+ isSelected && "ring-2 ring-primary-500 ring-offset-2 ring-offset-background"
16188
+ ),
16189
+ style: { backgroundColor: background.hex },
16190
+ children: [
16191
+ preferredPair ? /* @__PURE__ */ jsxRuntime.jsx(
16192
+ "span",
16193
+ {
16194
+ 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",
16195
+ style: { backgroundColor: preferredPair.foreground.hex }
16196
+ }
16197
+ ) : null,
16198
+ !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
16199
+ ]
16200
+ },
16201
+ background.token
16202
+ );
16203
+ }) })
16204
+ ] }, group.key);
16205
+ }),
16080
16206
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-4 text-sm text-muted-foreground", children: [
16081
16207
  /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
16082
16208
  "Selected background:",
@@ -16091,9 +16217,37 @@ function ColorPairingToolContent() {
16091
16217
  ] })
16092
16218
  ] })
16093
16219
  ] }),
16220
+ previewPair ? /* @__PURE__ */ jsxRuntime.jsx(
16221
+ PairPreview,
16222
+ {
16223
+ familySummary,
16224
+ isRecommended: Boolean(selectedPair),
16225
+ pair: previewPair
16226
+ }
16227
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
16228
+ PreviewFallbackCard,
16229
+ {
16230
+ familySummary,
16231
+ selectedBackground
16232
+ }
16233
+ ),
16094
16234
  /* @__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: [
16235
+ /* @__PURE__ */ jsxRuntime.jsx(
16236
+ PairDetailCard,
16237
+ {
16238
+ color: selectedBackground,
16239
+ role: "Background",
16240
+ visibleFormats
16241
+ }
16242
+ ),
16243
+ detailForeground ? /* @__PURE__ */ jsxRuntime.jsx(
16244
+ PairDetailCard,
16245
+ {
16246
+ color: detailForeground,
16247
+ role: "Foreground",
16248
+ visibleFormats
16249
+ }
16250
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: "gap-4", children: [
16097
16251
  /* @__PURE__ */ jsxRuntime.jsx(CardHeader, { className: "gap-3 border-b", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
16098
16252
  /* @__PURE__ */ jsxRuntime.jsx(CardTitle, { children: "Foreground" }),
16099
16253
  /* @__PURE__ */ jsxRuntime.jsx(CardDescription, { children: "No recommended foreground available" })
@@ -16101,21 +16255,27 @@ function ColorPairingToolContent() {
16101
16255
  /* @__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
16256
  ] })
16103
16257
  ] }),
16104
- selectedPair ? /* @__PURE__ */ jsxRuntime.jsx(PairComplianceCard, { pair: selectedPair }) : null,
16105
- showWhiteForegroundExample && whiteForegroundExample ? /* @__PURE__ */ jsxRuntime.jsx(WhiteTextExampleCard, { pair: whiteForegroundExample }) : null
16258
+ previewPair ? /* @__PURE__ */ jsxRuntime.jsx(PairComplianceCard, { pair: previewPair }) : null
16106
16259
  ] }),
16107
16260
  /* @__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" }),
16261
+ /* @__PURE__ */ jsxRuntime.jsxs(CardHeader, { className: "gap-4 border-b", children: [
16262
+ /* @__PURE__ */ jsxRuntime.jsx(
16263
+ WorkflowStep,
16264
+ {
16265
+ step: 4,
16266
+ title: "Choose a foreground",
16267
+ description: `Review the AAA combinations available for ${selectedBackground.token}.`,
16268
+ level: 3,
16269
+ variant: "card"
16270
+ }
16271
+ ),
16110
16272
  /* @__PURE__ */ jsxRuntime.jsxs(CardDescription, { children: [
16111
- "AAA combinations for ",
16112
- selectedBackground.token,
16113
- ", drawn from",
16114
- " ",
16273
+ "Recommended foregrounds are drawn from ",
16115
16274
  context.primary.label,
16116
- ", ",
16275
+ ",",
16276
+ " ",
16117
16277
  context.accent.label,
16118
- ", Grey, and white using only foreground tones 50, 200, 400, 600, and 800."
16278
+ ", Grey, and white where it meets AAA."
16119
16279
  ] })
16120
16280
  ] }),
16121
16281
  /* @__PURE__ */ jsxRuntime.jsx(CardContent, { className: "grid gap-3", children: selectedBackgroundPairs.length > 0 ? selectedBackgroundPairs.map((pair) => {
@@ -16124,7 +16284,7 @@ function ColorPairingToolContent() {
16124
16284
  "button",
16125
16285
  {
16126
16286
  type: "button",
16127
- "aria-label": `Use ${pair.foreground.token} on ${pair.background.token}`,
16287
+ "aria-label": `Use ${getPairingColorDisplayName(pair.foreground)} on ${getPairingColorDisplayName(pair.background)}`,
16128
16288
  "aria-pressed": isActive,
16129
16289
  onClick: () => handlePairChange(pair.id),
16130
16290
  className: cn(
@@ -16133,32 +16293,43 @@ function ColorPairingToolContent() {
16133
16293
  ),
16134
16294
  children: [
16135
16295
  /* @__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
- )
16296
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
16297
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 text-[0.65rem] font-semibold tracking-[0.14em] text-muted-foreground uppercase", children: [
16298
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-11 text-center", children: "BG" }),
16299
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-11 text-center", children: "FG" })
16300
+ ] }),
16301
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
16302
+ /* @__PURE__ */ jsxRuntime.jsx(
16303
+ "div",
16304
+ {
16305
+ className: "size-11 rounded-sm border border-grey-200 shadow-sm dark:border-grey-700",
16306
+ style: {
16307
+ backgroundColor: pair.background.hex
16308
+ }
16309
+ }
16310
+ ),
16311
+ /* @__PURE__ */ jsxRuntime.jsx(
16312
+ "div",
16313
+ {
16314
+ className: "size-11 rounded-sm border border-grey-200 shadow-sm dark:border-grey-700",
16315
+ style: {
16316
+ backgroundColor: pair.foreground.hex
16317
+ }
16318
+ }
16319
+ )
16320
+ ] })
16151
16321
  ] }),
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: [
16322
+ /* @__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
16323
  /* @__PURE__ */ jsxRuntime.jsx(Icons.contrast, { "data-slot": "icon", className: "size-4" }),
16154
16324
  pair.rating
16155
16325
  ] })
16156
16326
  ] }),
16157
16327
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 space-y-1", children: [
16158
16328
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "font-medium text-foreground", children: [
16159
- pair.background.token,
16160
- " / ",
16161
- pair.foreground.token
16329
+ getPairingColorDisplayName(pair.background),
16330
+ " /",
16331
+ " ",
16332
+ getPairingColorDisplayName(pair.foreground)
16162
16333
  ] }),
16163
16334
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground", children: [
16164
16335
  pair.foreground.familyLabel,
@@ -16179,8 +16350,11 @@ function ColorPairingToolContent() {
16179
16350
  ] }) })
16180
16351
  ] });
16181
16352
  }
16182
- function ColorPairingTool() {
16183
- return /* @__PURE__ */ jsxRuntime.jsx(React5.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(ColorPairingToolLoading, {}), children: /* @__PURE__ */ jsxRuntime.jsx(ColorPairingToolContent, {}) });
16353
+ function ColorPairingTool({
16354
+ visibleFormats = DEFAULT_VISIBLE_FORMATS
16355
+ } = {}) {
16356
+ const normalizedVisibleFormats = [...new Set(visibleFormats)];
16357
+ return /* @__PURE__ */ jsxRuntime.jsx(React5.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(ColorPairingToolLoading, {}), children: /* @__PURE__ */ jsxRuntime.jsx(ColorPairingToolContent, { visibleFormats: normalizedVisibleFormats }) });
16184
16358
  }
16185
16359
  function ColorSwatches({ theme: theme2, format, viewMode }) {
16186
16360
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -18661,93 +18835,152 @@ function FormMessage({ className, ...props }) {
18661
18835
  }
18662
18836
  );
18663
18837
  }
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);
18838
+ var styles3 = {
18839
+ base: [
18840
+ // Base
18841
+ "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",
18842
+ // States
18843
+ "data-[state=on]:bg-grey-100 data-[state=on]:text-grey-850",
18844
+ // Hover
18845
+ "hover:bg-grey-100 hover:text-grey-850",
18846
+ // Focus
18847
+ "focus:outline focus:outline-2 focus:outline-offset-0 focus:outline-(--toggle-border)",
18848
+ // Dark mode
18849
+ "dark:text-white",
18850
+ // Dark mode states
18851
+ "dark:data-[state=on]:bg-white/10 dark:data-[state=on]:text-white",
18852
+ // Dark mode hover
18853
+ "dark:hover:bg-white/10 dark:hover:text-white",
18854
+ // Disabled
18855
+ "disabled:pointer-events-none disabled:opacity-50",
18856
+ // Icon
18857
+ '[&_svg]:pointer-events-none [&_svg:not([class*="size-"])]:size-4 [&_svg]:shrink-0',
18858
+ // Aria invalid
18859
+ "aria-invalid:ring-destructive/20 aria-invalid:border-destructive",
18860
+ // Aria invalid dark mode
18861
+ "dark:aria-invalid:ring-destructive/40"
18862
+ ],
18863
+ outline: [
18864
+ // Base
18865
+ "text-grey-800 border [--toggle-border:var(--color-grey-300)]",
18866
+ // States
18867
+ "hover:[--toggle-border:var(--color-grey-400)]",
18868
+ // Dark mode
18869
+ "dark:[--toggle-border:white]/40",
18870
+ // Dark mode states
18871
+ "dark:hover:[--toggle-border:white]/50",
18872
+ // Data on
18873
+ "data-[state=on]:bg-primary-800/10"
18874
+ ]
18875
+ };
18876
+ var toggleVariants = classVarianceAuthority.cva(styles3.base, {
18877
+ variants: {
18878
+ variant: {
18879
+ ghost: "",
18880
+ outline: clsx12__default.default(styles3.outline)
18881
+ },
18882
+ size: {
18883
+ default: "h-9 px-2 min-w-9",
18884
+ sm: "h-8 px-1.5 min-w-8",
18885
+ lg: "h-10 px-2.5 min-w-10"
18886
+ }
18887
+ },
18888
+ defaultVariants: {
18889
+ variant: "ghost",
18890
+ size: "default"
18676
18891
  }
18677
- return "";
18678
- }
18679
- function baseSlug(input) {
18680
- return input.toLowerCase().trim().replace(/[\s\W]+/g, "-").replace(/^-+|-+$/g, "");
18892
+ });
18893
+ function Toggle({
18894
+ className,
18895
+ variant,
18896
+ size,
18897
+ ...props
18898
+ }) {
18899
+ return /* @__PURE__ */ jsxRuntime.jsx(
18900
+ TogglePrimitive__namespace.Root,
18901
+ {
18902
+ "data-slot": "toggle",
18903
+ className: cn(toggleVariants({ variant, size, className })),
18904
+ ...props
18905
+ }
18906
+ );
18681
18907
  }
18682
- function Heading({
18908
+ var ToggleGroupContext = React5__namespace.createContext({
18909
+ size: "default",
18910
+ variant: "ghost"
18911
+ });
18912
+ function ToggleGroup({
18683
18913
  className,
18684
- trim = "normal",
18685
- size = 1,
18686
- level = 1,
18687
- display = false,
18688
- id: idProp,
18914
+ variant,
18915
+ size,
18689
18916
  children,
18690
18917
  ...props
18691
18918
  }) {
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
18919
  return /* @__PURE__ */ jsxRuntime.jsx(
18733
- Tag,
18920
+ ToggleGroupPrimitive__namespace.Root,
18734
18921
  {
18922
+ "data-slot": "toggle-group",
18923
+ "data-variant": variant,
18924
+ "data-size": size,
18925
+ className: cn(
18926
+ "group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
18927
+ className
18928
+ ),
18735
18929
  ...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
18930
+ children: /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupContext.Provider, { value: { variant, size }, children })
18931
+ }
18932
+ );
18933
+ }
18934
+ function ToggleGroupItem({
18935
+ className,
18936
+ children,
18937
+ variant,
18938
+ size,
18939
+ ...props
18940
+ }) {
18941
+ const context = React5__namespace.useContext(ToggleGroupContext);
18942
+ return /* @__PURE__ */ jsxRuntime.jsx(
18943
+ ToggleGroupPrimitive__namespace.Item,
18944
+ {
18945
+ "data-slot": "toggle-group-item",
18946
+ "data-variant": context.variant || variant,
18947
+ "data-size": context.size || size,
18948
+ className: cn(
18949
+ toggleVariants({
18950
+ variant: context.variant || variant,
18951
+ size: context.size || size
18952
+ }),
18953
+ "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",
18954
+ className
18746
18955
  ),
18956
+ ...props,
18747
18957
  children
18748
18958
  }
18749
18959
  );
18750
18960
  }
18961
+ function FormatToggle({ format, setFormat }) {
18962
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
18963
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Format:" }),
18964
+ /* @__PURE__ */ jsxRuntime.jsxs(
18965
+ ToggleGroup,
18966
+ {
18967
+ type: "single",
18968
+ value: format,
18969
+ onValueChange: (value) => value && setFormat(value),
18970
+ children: [
18971
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupItem, { value: "hex", "aria-label": "HEX format", children: "HEX" }),
18972
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupItem, { value: "rgb", "aria-label": "RGB format", children: "RGB" }),
18973
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupItem, { value: "hsl", "aria-label": "HSL format", children: "HSL" }),
18974
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleGroupItem, { value: "oklch", "aria-label": "OKLCH format", children: "OKLCH" })
18975
+ ]
18976
+ }
18977
+ )
18978
+ ] });
18979
+ }
18980
+
18981
+ // package.json
18982
+ var package_default = {
18983
+ version: "1.100.0"};
18751
18984
  function Logo(props) {
18752
18985
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18753
18986
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "NSW Government" }),