@drawnagency/primitives 0.1.65 → 0.1.67

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.
Files changed (79) hide show
  1. package/dist/chunk-4VMAZZXT.js +968 -0
  2. package/dist/{chunk-QYQ3DQHI.js → chunk-KM6HOW4H.js} +127 -40
  3. package/dist/{chunk-CBWK3KPV.js → chunk-VU3XR362.js} +4 -0
  4. package/dist/{chunk-FECCYOPJ.js → chunk-WJ7RZ6CC.js} +12 -3
  5. package/dist/{chunk-VMDKWUMT.js → chunk-XNGG5AGI.js} +74 -17
  6. package/dist/components/primitives/ResolvedMedia.d.ts +3 -1
  7. package/dist/components/primitives/ResolvedMedia.d.ts.map +1 -1
  8. package/dist/components/primitives/defineHeadingSection.d.ts +2 -0
  9. package/dist/components/primitives/defineHeadingSection.d.ts.map +1 -1
  10. package/dist/components/primitives/index.js +3 -3
  11. package/dist/components/sections/Button/index.d.ts.map +1 -1
  12. package/dist/components/sections/Colors/index.d.ts.map +1 -1
  13. package/dist/components/sections/Colors/schema.d.ts.map +1 -1
  14. package/dist/components/sections/Container/index.d.ts.map +1 -1
  15. package/dist/components/sections/DoDontMedia/index.d.ts.map +1 -1
  16. package/dist/components/sections/IconList/IconList.d.ts +2 -1
  17. package/dist/components/sections/IconList/IconList.d.ts.map +1 -1
  18. package/dist/components/sections/IconList/IconListSettings.d.ts +2 -1
  19. package/dist/components/sections/IconList/IconListSettings.d.ts.map +1 -1
  20. package/dist/components/sections/IconList/index.d.ts +1 -0
  21. package/dist/components/sections/IconList/index.d.ts.map +1 -1
  22. package/dist/components/sections/LinkHeading/index.d.ts.map +1 -1
  23. package/dist/components/sections/Media/MediaBlock.d.ts.map +1 -1
  24. package/dist/components/sections/Media/index.d.ts.map +1 -1
  25. package/dist/components/sections/Prose/index.d.ts.map +1 -1
  26. package/dist/components/sections/Spacer/index.d.ts.map +1 -1
  27. package/dist/components/sections/SubHeading/index.d.ts.map +1 -1
  28. package/dist/components/sections/SubSubHeading/index.d.ts.map +1 -1
  29. package/dist/components/sections/all-sections.d.ts +1 -0
  30. package/dist/components/sections/all-sections.d.ts.map +1 -1
  31. package/dist/components/sections/brand-guide.d.ts +1 -0
  32. package/dist/components/sections/brand-guide.d.ts.map +1 -1
  33. package/dist/components/sections/brand-guide.js +3 -3
  34. package/dist/components/sections/register-schemas.js +12 -942
  35. package/dist/components/sections/register.js +36 -0
  36. package/dist/components/shell/EditorLoading.d.ts +13 -0
  37. package/dist/components/shell/EditorLoading.d.ts.map +1 -0
  38. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  39. package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -1
  40. package/dist/hooks/useResolvedMedia.d.ts +2 -0
  41. package/dist/hooks/useResolvedMedia.d.ts.map +1 -1
  42. package/dist/index.js +21 -21
  43. package/dist/lib/index.js +2 -2
  44. package/dist/lib/insert-menu.d.ts +19 -0
  45. package/dist/lib/insert-menu.d.ts.map +1 -0
  46. package/dist/lib/registry.d.ts +23 -0
  47. package/dist/lib/registry.d.ts.map +1 -1
  48. package/dist/media/index.js +1 -1
  49. package/dist/media/resolve.d.ts.map +1 -1
  50. package/dist/types/database.d.ts +248 -34
  51. package/dist/types/database.d.ts.map +1 -1
  52. package/package.json +5 -1
  53. package/src/components/primitives/ResolvedMedia.tsx +51 -7
  54. package/src/components/primitives/defineHeadingSection.tsx +18 -2
  55. package/src/components/sections/Button/index.tsx +10 -3
  56. package/src/components/sections/Colors/index.tsx +15 -7
  57. package/src/components/sections/Colors/schema.ts +5 -2
  58. package/src/components/sections/Container/index.tsx +19 -3
  59. package/src/components/sections/DoDontMedia/index.tsx +18 -7
  60. package/src/components/sections/IconList/IconList.tsx +43 -19
  61. package/src/components/sections/IconList/IconListSettings.tsx +58 -11
  62. package/src/components/sections/IconList/index.tsx +51 -7
  63. package/src/components/sections/LinkHeading/index.tsx +2 -0
  64. package/src/components/sections/Media/MediaBlock.tsx +7 -0
  65. package/src/components/sections/Media/index.tsx +15 -6
  66. package/src/components/sections/Prose/index.tsx +9 -1
  67. package/src/components/sections/Spacer/index.tsx +2 -0
  68. package/src/components/sections/SubHeading/index.tsx +2 -0
  69. package/src/components/sections/SubSubHeading/index.tsx +2 -0
  70. package/src/components/shared/PasswordInput.tsx +3 -3
  71. package/src/components/shell/EditorLoading.tsx +37 -0
  72. package/src/components/shell/EditorShell.tsx +15 -20
  73. package/src/components/shell/SiteSettingsDisplay.tsx +28 -2
  74. package/src/hooks/useResolvedMedia.ts +9 -3
  75. package/src/lib/insert-menu.ts +42 -0
  76. package/src/lib/registry.ts +24 -0
  77. package/src/media/resolve.ts +4 -0
  78. package/src/types/database.ts +236 -36
  79. package/dist/{chunk-ZYXLNJ5M.js → chunk-AFK45HX2.js} +3 -3
@@ -9,7 +9,7 @@ import {
9
9
  MediaBlock,
10
10
  containerGridClass,
11
11
  useEditableCollection
12
- } from "./chunk-VMDKWUMT.js";
12
+ } from "./chunk-XNGG5AGI.js";
13
13
  import {
14
14
  AddIcon,
15
15
  DeleteIcon,
@@ -51,8 +51,8 @@ var ColorSpaceSchema = z.object({
51
51
  { message: "At least one color space must be defined" }
52
52
  );
53
53
  var ColorItemSchema = z.object({
54
- name: z.string().optional(),
55
- spaces: z.array(ColorSpaceSchema).min(1)
54
+ name: z.string().describe("Optional display name for this color (e.g. 'Brand Primary').").optional(),
55
+ spaces: z.array(ColorSpaceSchema).describe("One or more color-space definitions (hex/rgb/cmyk/pantone) for this color.").min(1)
56
56
  });
57
57
 
58
58
  // src/components/brandguide/Colors.tsx
@@ -329,12 +329,14 @@ function pluralize(n, word) {
329
329
  import { jsx as jsx3 } from "react/jsx-runtime";
330
330
  var schema = z2.object({
331
331
  type: z2.literal("colors"),
332
- content: z2.object({ colors: z2.array(ColorItemSchema) }),
332
+ content: z2.object({
333
+ colors: z2.array(ColorItemSchema).describe("The list of brand colors, each with one or more color-space values (hex/rgb/cmyk/pantone).")
334
+ }),
333
335
  options: z2.object({
334
- label: z2.string().optional(),
335
- columns: z2.number().int().min(2).max(4).optional(),
336
- collapsing: z2.boolean().optional(),
337
- showLabel: z2.boolean().optional()
336
+ label: z2.string().describe("Optional heading shown above the palette.").optional(),
337
+ columns: z2.number().int().min(2).max(4).describe("Number of swatch columns (2\u20134).").optional(),
338
+ collapsing: z2.boolean().describe("When true, use the compact collapsing swatch layout.").optional(),
339
+ showLabel: z2.boolean().describe("When true, show the palette label.").optional()
338
340
  }).optional()
339
341
  });
340
342
  var Colors_default = defineSection({
@@ -343,6 +345,7 @@ var Colors_default = defineSection({
343
345
  label: "Colors",
344
346
  icon: /* @__PURE__ */ jsx3(Palette, { size: 18 }),
345
347
  schema,
348
+ authoringNote: "A brand color palette. Each color may carry hex/rgb/cmyk/pantone values. Use to document brand colors with swatches.",
346
349
  component: ({ content, options, onChange, openModal }) => /* @__PURE__ */ jsx3(
347
350
  Colors,
348
351
  {
@@ -377,7 +380,7 @@ var Colors_default = defineSection({
377
380
 
378
381
  // src/components/sections/IconList/index.tsx
379
382
  import { z as z3 } from "zod";
380
- import { List } from "lucide-react";
383
+ import { List, Columns2 } from "lucide-react";
381
384
 
382
385
  // src/components/sections/IconList/IconList.tsx
383
386
  import { useRef as useRef3, useEffect as useEffect4, useState as useState3, useCallback } from "react";
@@ -514,6 +517,7 @@ function IconList({
514
517
  icon = null,
515
518
  showLabel = true,
516
519
  stackText = false,
520
+ dodont,
517
521
  labelClassName = "font-bold text-base-contrast",
518
522
  textClassName = "text-base-contrast-light",
519
523
  iconClassName = "text-primary",
@@ -526,11 +530,11 @@ function IconList({
526
530
  onChange?.({
527
531
  type: "icon_list",
528
532
  content: { items: newItems },
529
- options: { icon, showLabel, stackText }
533
+ options: { icon, showLabel, stackText, dodont }
530
534
  });
531
535
  onItemsChange?.(newItems);
532
536
  },
533
- [onChange, onItemsChange, icon, showLabel, stackText]
537
+ [onChange, onItemsChange, icon, showLabel, stackText, dodont]
534
538
  );
535
539
  if (!isEditMode) {
536
540
  return /* @__PURE__ */ jsx6(
@@ -540,6 +544,7 @@ function IconList({
540
544
  defaultIcon: icon,
541
545
  showLabel,
542
546
  stackText,
547
+ dodont,
543
548
  labelClassName,
544
549
  textClassName,
545
550
  iconClassName
@@ -553,6 +558,7 @@ function IconList({
553
558
  defaultIcon: icon,
554
559
  showLabel,
555
560
  stackText,
561
+ dodont,
556
562
  labelClassName,
557
563
  textClassName,
558
564
  iconClassName,
@@ -560,15 +566,20 @@ function IconList({
560
566
  }
561
567
  );
562
568
  }
563
- function resolveIcon(item, defaultIcon) {
569
+ function resolveIcon(item, defaultIcon, sectionDodont) {
564
570
  if (item.dodont === "do") return getIcon("check") ?? null;
565
571
  if (item.dodont === "dont") return getIcon("x") ?? null;
566
- const id = item.icon ?? defaultIcon;
567
- return id ? getIcon(id) : null;
572
+ if (item.icon) return getIcon(item.icon) ?? null;
573
+ if (sectionDodont === "do") return getIcon("check") ?? null;
574
+ if (sectionDodont === "dont") return getIcon("x") ?? null;
575
+ return defaultIcon ? getIcon(defaultIcon) : null;
568
576
  }
569
- function resolveIconColor(item, iconClassName) {
577
+ function resolveIconColor(item, iconClassName, sectionDodont) {
570
578
  if (item.dodont === "do") return "text-green-600";
571
579
  if (item.dodont === "dont") return "text-red-600";
580
+ if (item.icon) return iconClassName;
581
+ if (sectionDodont === "do") return "text-green-600";
582
+ if (sectionDodont === "dont") return "text-red-600";
572
583
  return iconClassName;
573
584
  }
574
585
  function ViewIconList({
@@ -576,14 +587,15 @@ function ViewIconList({
576
587
  defaultIcon,
577
588
  showLabel,
578
589
  stackText,
590
+ dodont,
579
591
  labelClassName,
580
592
  textClassName,
581
593
  iconClassName
582
594
  }) {
583
- const hasAnyIcon = items.some((item) => resolveIcon(item, defaultIcon));
595
+ const hasAnyIcon = items.some((item) => resolveIcon(item, defaultIcon, dodont));
584
596
  return /* @__PURE__ */ jsx6("div", { className: "grid gap-4 pb-4", children: items.map((item, i) => {
585
- const iconEntry = resolveIcon(item, defaultIcon);
586
- const iconColor = resolveIconColor(item, iconClassName);
597
+ const iconEntry = resolveIcon(item, defaultIcon, dodont);
598
+ const iconColor = resolveIconColor(item, iconClassName, dodont);
587
599
  return /* @__PURE__ */ jsxs5(
588
600
  "div",
589
601
  {
@@ -625,6 +637,7 @@ function EditIconList({
625
637
  defaultIcon,
626
638
  showLabel,
627
639
  stackText,
640
+ dodont,
628
641
  labelClassName,
629
642
  textClassName,
630
643
  iconClassName,
@@ -669,6 +682,7 @@ function EditIconList({
669
682
  hasAnyIcon,
670
683
  showLabel,
671
684
  stackText,
685
+ dodont,
672
686
  labelClassName,
673
687
  textClassName,
674
688
  iconClassName,
@@ -711,6 +725,7 @@ function EditableRow({
711
725
  hasAnyIcon,
712
726
  showLabel,
713
727
  stackText,
728
+ dodont,
714
729
  labelClassName,
715
730
  textClassName,
716
731
  iconClassName,
@@ -727,9 +742,9 @@ function EditableRow({
727
742
  const [pickerOpen, setPickerOpen] = useState3(false);
728
743
  const [dodontOpen, setDodontOpen] = useState3(false);
729
744
  const [editingSuppressed, setEditingSuppressed] = useState3(false);
730
- const iconEntry = resolveIcon(item, defaultIcon);
745
+ const iconEntry = resolveIcon(item, defaultIcon, dodont);
731
746
  const effectiveIconId = item.icon ?? defaultIcon ?? null;
732
- const iconColor = resolveIconColor(item, iconClassName);
747
+ const iconColor = resolveIconColor(item, iconClassName, dodont);
733
748
  useEffect4(() => {
734
749
  const row = rowRef.current;
735
750
  const handle = handleRef.current;
@@ -967,21 +982,59 @@ function TrailingDropZone({
967
982
 
968
983
  // src/components/sections/IconList/IconListSettings.tsx
969
984
  import { useState as useState4 } from "react";
985
+ import { Check as Check3, X as X3 } from "lucide-react";
970
986
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
971
987
  function IconListSettings({
972
988
  icon: initialIcon,
973
989
  showLabel: initialShowLabel,
974
990
  stackText: initialStackText,
991
+ dodont: initialDodont,
975
992
  onChange
976
993
  }) {
977
994
  const [icon, setIcon] = useState4(initialIcon);
978
995
  const [showLabel, setShowLabel] = useState4(initialShowLabel);
979
996
  const [stackText, setStackText] = useState4(initialStackText);
997
+ const [dodont, setDodont] = useState4(initialDodont);
980
998
  const [pickerOpen, setPickerOpen] = useState4(false);
981
- const emit = (overrides) => onChange({ content: {}, options: { icon, showLabel, stackText, ...overrides } });
982
- const showIcons = icon !== null;
999
+ const emit = (overrides) => onChange({ content: {}, options: { icon, showLabel, stackText, dodont, ...overrides } });
983
1000
  const iconEntry = icon ? getIcon(icon) : null;
1001
+ const dodontActive = dodont === "do" || dodont === "dont";
1002
+ const tile = (value, label, aria, glyph, tint) => /* @__PURE__ */ jsxs6(
1003
+ "button",
1004
+ {
1005
+ type: "button",
1006
+ "aria-label": aria,
1007
+ "aria-pressed": dodont === value,
1008
+ className: cn(
1009
+ "cursor-pointer flex flex-col items-center gap-1 rounded border py-2 transition-colors",
1010
+ tint,
1011
+ dodont === value ? "border-primary bg-base-accent" : "border-base-200"
1012
+ ),
1013
+ onClick: () => {
1014
+ setDodont(value);
1015
+ emit({ dodont: value });
1016
+ },
1017
+ children: [
1018
+ glyph,
1019
+ /* @__PURE__ */ jsx7("span", { className: "text-xs font-medium", children: label })
1020
+ ]
1021
+ }
1022
+ );
984
1023
  return /* @__PURE__ */ jsxs6("div", { className: "space-y-4", children: [
1024
+ /* @__PURE__ */ jsxs6("div", { children: [
1025
+ /* @__PURE__ */ jsx7(FormLabel, { children: "Do / Don't list" }),
1026
+ /* @__PURE__ */ jsxs6("div", { className: "grid grid-cols-3 gap-2", children: [
1027
+ tile(
1028
+ void 0,
1029
+ "None",
1030
+ "No do/don't",
1031
+ /* @__PURE__ */ jsx7("span", { className: "flex h-5 w-5 items-center justify-center rounded-sm border border-dashed border-base-contrast-light/40" }),
1032
+ "text-base-contrast-light"
1033
+ ),
1034
+ tile("do", "Do", "Do list", /* @__PURE__ */ jsx7(Check3, { size: 20 }), "text-green-600"),
1035
+ tile("dont", "Don't", "Don't list", /* @__PURE__ */ jsx7(X3, { size: 20 }), "text-red-600")
1036
+ ] })
1037
+ ] }),
985
1038
  /* @__PURE__ */ jsxs6("div", { className: "relative", children: [
986
1039
  /* @__PURE__ */ jsx7(FormLabel, { htmlFor: "icon-picker-btn", children: "Default Icon" }),
987
1040
  /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
@@ -989,15 +1042,21 @@ function IconListSettings({
989
1042
  "button",
990
1043
  {
991
1044
  id: "icon-picker-btn",
992
- className: "cursor-pointer flex items-center gap-2 rounded border border-base-200 px-3 py-1.5 text-sm text-base-contrast-light hover:border-primary hover:text-primary",
993
- onClick: () => setPickerOpen((prev) => !prev),
1045
+ disabled: dodontActive,
1046
+ className: cn(
1047
+ "flex items-center gap-2 rounded border border-base-200 px-3 py-1.5 text-sm text-base-contrast-light",
1048
+ dodontActive ? "cursor-not-allowed opacity-50" : "cursor-pointer hover:border-primary hover:text-primary"
1049
+ ),
1050
+ onClick: () => {
1051
+ if (!dodontActive) setPickerOpen((prev) => !prev);
1052
+ },
994
1053
  children: [
995
1054
  iconEntry && /* @__PURE__ */ jsx7(iconEntry.icon, { size: 16 }),
996
1055
  /* @__PURE__ */ jsx7("span", { children: iconEntry?.label ?? "None" })
997
1056
  ]
998
1057
  }
999
1058
  ),
1000
- icon && /* @__PURE__ */ jsx7(
1059
+ icon && !dodontActive && /* @__PURE__ */ jsx7(
1001
1060
  "button",
1002
1061
  {
1003
1062
  className: "cursor-pointer text-xs text-base-contrast-light hover:text-base-contrast",
@@ -1009,7 +1068,8 @@ function IconListSettings({
1009
1068
  }
1010
1069
  )
1011
1070
  ] }),
1012
- pickerOpen && /* @__PURE__ */ jsx7("div", { className: "absolute top-full left-0 z-50 mt-1", children: /* @__PURE__ */ jsx7(
1071
+ dodontActive && /* @__PURE__ */ jsx7("p", { className: "mt-1 text-xs text-base-contrast-light/70", children: "Whole-list Do/Don't overrides the default icon." }),
1072
+ pickerOpen && !dodontActive && /* @__PURE__ */ jsx7("div", { className: "absolute top-full left-0 z-50 mt-1", children: /* @__PURE__ */ jsx7(
1013
1073
  IconPicker,
1014
1074
  {
1015
1075
  selected: icon,
@@ -1060,20 +1120,40 @@ var IconListItemSchema = z3.object({
1060
1120
  var schema2 = z3.object({
1061
1121
  type: z3.literal("icon_list"),
1062
1122
  content: z3.object({
1063
- items: z3.array(IconListItemSchema)
1123
+ items: z3.array(IconListItemSchema).describe(
1124
+ "The list entries. Each item has a label, descriptive text, an optional icon, and an optional do/don't marker."
1125
+ )
1064
1126
  }),
1065
1127
  options: z3.object({
1066
- icon: z3.string().nullable().optional(),
1067
- showLabel: z3.boolean().optional(),
1068
- stackText: z3.boolean().optional()
1128
+ icon: z3.string().nullable().describe("Default icon name applied to items that don't specify their own; null for no icon.").optional(),
1129
+ showLabel: z3.boolean().describe("When true, render each item's label.").optional(),
1130
+ stackText: z3.boolean().describe("When true, stack the label and text vertically instead of inline.").optional(),
1131
+ dodont: z3.enum(["do", "dont"]).describe("Whole-list do/don't mode: render every item with a green check (do) or red x (don't).").optional()
1069
1132
  }).optional()
1070
1133
  });
1134
+ var doDontListPreset = {
1135
+ id: "do_dont_list",
1136
+ label: "Do / Don't list",
1137
+ icon: /* @__PURE__ */ jsx8(Columns2, { size: 18 }),
1138
+ build: (newId) => ({
1139
+ type: "container",
1140
+ content: {
1141
+ columns: 2,
1142
+ flow: "row",
1143
+ children: [
1144
+ { id: newId(), type: "icon_list", content: { items: [{ label: "", text: "" }] }, options: { dodont: "do" } },
1145
+ { id: newId(), type: "icon_list", content: { items: [{ label: "", text: "" }] }, options: { dodont: "dont" } }
1146
+ ]
1147
+ }
1148
+ })
1149
+ };
1071
1150
  var IconList_default = defineSection({
1072
1151
  type: "icon_list",
1073
1152
  category: "brand-guide",
1074
1153
  label: "Icon List",
1075
1154
  icon: /* @__PURE__ */ jsx8(List, { size: 18 }),
1076
1155
  schema: schema2,
1156
+ authoringNote: "A list of labeled items, optionally with per-item icons and do/don't markers. Use for feature lists, guidelines, or do/don't guidance.",
1077
1157
  component: ({ content, options, onChange }) => /* @__PURE__ */ jsx8(
1078
1158
  IconList,
1079
1159
  {
@@ -1081,6 +1161,7 @@ var IconList_default = defineSection({
1081
1161
  icon: options?.icon,
1082
1162
  showLabel: options?.showLabel,
1083
1163
  stackText: options?.stackText,
1164
+ dodont: options?.dodont,
1084
1165
  onChange: onChange ? (c) => onChange(c) : void 0
1085
1166
  }
1086
1167
  ),
@@ -1089,7 +1170,8 @@ var IconList_default = defineSection({
1089
1170
  content: { items: [{ label: "", text: "" }] }
1090
1171
  }),
1091
1172
  getLabel: (content) => pluralize(content.content.items.length, "icon"),
1092
- settingsForm: IconListSettings
1173
+ settingsForm: IconListSettings,
1174
+ presets: [doDontListPreset]
1093
1175
  });
1094
1176
 
1095
1177
  // src/components/sections/DoDontMedia/index.tsx
@@ -1097,11 +1179,11 @@ import { z as z4 } from "zod";
1097
1179
  import { Image as ImageIcon } from "lucide-react";
1098
1180
 
1099
1181
  // src/components/sections/DoDontMedia/DoDontMediaBlock.tsx
1100
- import { Check as Check3, X as X3 } from "lucide-react";
1182
+ import { Check as Check4, X as X4 } from "lucide-react";
1101
1183
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1102
1184
  function DoDontMediaBlock({ refValue, options, link, dodont, isEditMode, onRefChange }) {
1103
1185
  return /* @__PURE__ */ jsxs7("div", { children: [
1104
- /* @__PURE__ */ jsx9("div", { className: "flex h-10 items-center justify-center pb-1", children: dodont === "do" ? /* @__PURE__ */ jsx9(Check3, { size: 28, className: "text-green-600" }) : /* @__PURE__ */ jsx9(X3, { size: 28, className: "text-red-600" }) }),
1186
+ /* @__PURE__ */ jsx9("div", { className: "flex h-10 items-center justify-center pb-1", children: dodont === "do" ? /* @__PURE__ */ jsx9(Check4, { size: 28, className: "text-green-600" }) : /* @__PURE__ */ jsx9(X4, { size: 28, className: "text-red-600" }) }),
1105
1187
  /* @__PURE__ */ jsx9(
1106
1188
  MediaBlock,
1107
1189
  {
@@ -1120,15 +1202,19 @@ import { jsx as jsx10 } from "react/jsx-runtime";
1120
1202
  var schema3 = z4.object({
1121
1203
  type: z4.literal("dodont_media"),
1122
1204
  content: z4.object({
1123
- ref: SingleMediaReferenceSchema,
1124
- link: LinkValueSchema.optional(),
1125
- dodont: z4.enum(["do", "dont"])
1205
+ ref: SingleMediaReferenceSchema.describe(
1206
+ "The image or video asset to display (a media reference by id). Required."
1207
+ ),
1208
+ link: LinkValueSchema.describe(
1209
+ "Optional link wrapping the media \u2014 an internal page or a safe external URL."
1210
+ ).optional(),
1211
+ dodont: z4.enum(["do", "dont"]).describe("Whether this is a 'do' (correct) or 'dont' (incorrect) example.")
1126
1212
  }),
1127
1213
  options: z4.object({
1128
- square: z4.boolean().optional(),
1129
- showCaption: z4.boolean().optional(),
1130
- border: z4.boolean().optional(),
1131
- objectFit: z4.enum(["cover", "contain"]).optional()
1214
+ square: z4.boolean().describe("When true, crop the media to a 1:1 square aspect ratio.").optional(),
1215
+ showCaption: z4.boolean().describe("When true, render the asset's caption beneath it.").optional(),
1216
+ border: z4.boolean().describe("When true, draw a border around the media.").optional(),
1217
+ objectFit: z4.enum(["cover", "contain"]).describe("How the image fills its box: 'contain' (fit whole image) or 'cover' (fill and crop).").optional()
1132
1218
  }).default({})
1133
1219
  });
1134
1220
  var DoDontMedia_default = defineSection({
@@ -1137,6 +1223,7 @@ var DoDontMedia_default = defineSection({
1137
1223
  category: "brand-guide",
1138
1224
  icon: /* @__PURE__ */ jsx10(ImageIcon, { size: 18 }),
1139
1225
  schema: schema3,
1226
+ authoringNote: "A single image or video tagged as a 'do' or 'don't' example. Use inside a Container to build side-by-side do/don't comparisons of visual usage.",
1140
1227
  component: ({ content, options, onChange }) => /* @__PURE__ */ jsx10(
1141
1228
  DoDontMediaBlock,
1142
1229
  {
@@ -200,11 +200,15 @@ function resolveManifestReferences(content, manifest, sizes, provider) {
200
200
  if ("srcSet" in resolved && resolved.srcSet) {
201
201
  patch.srcset = resolved.srcSet;
202
202
  }
203
+ patch.width = resolved.width;
204
+ patch.height = resolved.height;
203
205
  } else if (resolved && resolved.tag === "video") {
204
206
  patch.src = resolved.src;
205
207
  if (resolved.poster) {
206
208
  patch.poster = resolved.poster;
207
209
  }
210
+ patch.width = resolved.width;
211
+ patch.height = resolved.height;
208
212
  }
209
213
  return { ...obj, ...patch };
210
214
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  EditablePlainText
3
- } from "./chunk-VMDKWUMT.js";
3
+ } from "./chunk-XNGG5AGI.js";
4
4
  import {
5
5
  usePages
6
6
  } from "./chunk-4E66N6UV.js";
@@ -39,7 +39,15 @@ function HeadingSection({ heading, tag, placeholder, className, onChange }) {
39
39
  // src/components/primitives/defineHeadingSection.tsx
40
40
  import { jsx as jsx2 } from "react/jsx-runtime";
41
41
  function defineHeadingSection(config) {
42
- const content = config.navExclusion ? z.object({ heading: z.string(), excludeFromNav: z.boolean().optional() }) : z.object({ heading: z.string() });
42
+ const heading = z.string().describe(
43
+ "The heading text. Plain text, one line; keep concise \u2014 it appears verbatim in the on-page heading and (unless excluded) the navigation."
44
+ );
45
+ const content = config.navExclusion ? z.object({
46
+ heading,
47
+ excludeFromNav: z.boolean().describe(
48
+ "When true, this heading is omitted from the generated navigation tree while still rendering on the page."
49
+ ).optional()
50
+ }) : z.object({ heading });
43
51
  const schema = z.object({ type: z.literal(config.type), content });
44
52
  return defineSection({
45
53
  type: config.type,
@@ -47,13 +55,14 @@ function defineHeadingSection(config) {
47
55
  icon: config.icon,
48
56
  schema,
49
57
  navRole: config.navRole,
58
+ authoringNote: config.authoringNote,
50
59
  component: ({ content: content2, onChange }) => /* @__PURE__ */ jsx2(
51
60
  HeadingSection,
52
61
  {
53
62
  heading: content2.content.heading,
54
63
  tag: config.tag,
55
64
  placeholder: config.placeholder,
56
- onChange: onChange ? (heading) => onChange({ ...content2, content: { ...content2.content, heading } }) : void 0
65
+ onChange: onChange ? (heading2) => onChange({ ...content2, content: { ...content2.content, heading: heading2 } }) : void 0
57
66
  }
58
67
  ),
59
68
  defaults: () => ({ type: config.type, content: { heading: config.defaultHeading } }),
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-I5737FJY.js";
8
8
  import {
9
9
  getMediaProvider
10
- } from "./chunk-CBWK3KPV.js";
10
+ } from "./chunk-VU3XR362.js";
11
11
  import {
12
12
  cn
13
13
  } from "./chunk-7EOSDFS3.js";
@@ -126,6 +126,7 @@ function EditablePlainText({
126
126
  }
127
127
 
128
128
  // src/components/primitives/ResolvedMedia.tsx
129
+ import { useState as useState2 } from "react";
129
130
  import { ImageIcon } from "lucide-react";
130
131
 
131
132
  // src/hooks/useResolvedMedia.ts
@@ -152,9 +153,11 @@ function useResolvedMedia(imageId) {
152
153
  const item = manifestItem ?? pendingItem;
153
154
  const alt = item?.alt ?? "";
154
155
  const kind = item?.kind ?? "image";
156
+ const width = item?.width;
157
+ const height = item?.height;
155
158
  const localUrl = pendingLocalUrls[imageId];
156
159
  if (localUrl) {
157
- return { src: localUrl, srcset: void 0, poster: void 0, alt, kind };
160
+ return { src: localUrl, srcset: void 0, poster: void 0, alt, kind, width, height };
158
161
  }
159
162
  if (manifestItem) {
160
163
  const provider = getMediaProvider();
@@ -165,24 +168,28 @@ function useResolvedMedia(imageId) {
165
168
  srcset: "srcSet" in resolved ? resolved.srcSet : void 0,
166
169
  poster: void 0,
167
170
  alt,
168
- kind
171
+ kind,
172
+ width: resolved.width,
173
+ height: resolved.height
169
174
  };
170
175
  }
171
176
  if (resolved && resolved.tag === "video") {
172
- return { src: resolved.src, srcset: void 0, poster: resolved.poster, alt, kind };
177
+ return { src: resolved.src, srcset: void 0, poster: resolved.poster, alt, kind, width: resolved.width, height: resolved.height };
173
178
  }
174
179
  }
175
- return { src: void 0, srcset: void 0, poster: void 0, alt, kind };
180
+ return { src: void 0, srcset: void 0, poster: void 0, alt, kind, width, height };
176
181
  }, [imageId, ctx]);
177
182
  }
178
183
 
179
184
  // src/components/primitives/ResolvedMedia.tsx
180
- import { jsx, jsxs } from "react/jsx-runtime";
185
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
181
186
  function ResolvedMedia({
182
187
  imageId,
183
188
  src: propSrc,
184
189
  srcset: propSrcset,
185
190
  alt: propAlt,
191
+ width: propWidth,
192
+ height: propHeight,
186
193
  className,
187
194
  imgClassName,
188
195
  invertFrom
@@ -193,22 +200,66 @@ function ResolvedMedia({
193
200
  const poster = resolved.poster;
194
201
  const alt = propAlt ?? resolved.alt;
195
202
  const kind = resolved.kind;
203
+ const width = propWidth ?? resolved.width;
204
+ const height = propHeight ?? resolved.height;
205
+ const [loaded, setLoaded] = useState2(false);
196
206
  const invertClass = invertFrom === "light" ? "invert dark:invert-0" : invertFrom === "dark" ? "dark:invert" : void 0;
197
- const mediaClass = cn("h-full w-full", imgClassName, invertClass);
198
- return /* @__PURE__ */ jsx("div", { className, children: src ? kind === "video" ? /* @__PURE__ */ jsx("video", { src, poster, muted: true, playsInline: true, loop: true, autoPlay: true, className: mediaClass }) : /* @__PURE__ */ jsx("img", { src, srcSet: srcset, alt, className: mediaClass }) : /* @__PURE__ */ jsxs("div", { className: "flex aspect-video h-full w-full flex-col items-center justify-center gap-1 rounded bg-base-accent text-base-contrast-light/50", children: [
207
+ const mediaClass = cn("relative h-full w-full", imgClassName, invertClass);
208
+ return /* @__PURE__ */ jsx("div", { className: cn("relative", className), children: src ? /* @__PURE__ */ jsxs(Fragment, { children: [
209
+ /* @__PURE__ */ jsx(
210
+ "div",
211
+ {
212
+ "aria-hidden": "true",
213
+ "data-testid": "media-placeholder",
214
+ className: cn(
215
+ "pointer-events-none absolute inset-0 bg-base-accent transition-opacity duration-300",
216
+ loaded ? "opacity-0" : "opacity-100"
217
+ )
218
+ }
219
+ ),
220
+ kind === "video" ? /* @__PURE__ */ jsx(
221
+ "video",
222
+ {
223
+ src,
224
+ poster,
225
+ width,
226
+ height,
227
+ muted: true,
228
+ playsInline: true,
229
+ loop: true,
230
+ autoPlay: true,
231
+ className: mediaClass,
232
+ onLoadedData: () => setLoaded(true)
233
+ }
234
+ ) : /* @__PURE__ */ jsx(
235
+ "img",
236
+ {
237
+ src,
238
+ srcSet: srcset,
239
+ alt,
240
+ width,
241
+ height,
242
+ loading: "lazy",
243
+ decoding: "async",
244
+ className: mediaClass,
245
+ onLoad: () => setLoaded(true),
246
+ onError: () => setLoaded(true)
247
+ }
248
+ )
249
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex aspect-video h-full w-full flex-col items-center justify-center gap-1 rounded bg-base-accent text-base-contrast-light/50", children: [
199
250
  /* @__PURE__ */ jsx(ImageIcon, { size: 24 }),
200
251
  /* @__PURE__ */ jsx("span", { className: "text-sm", children: "No Image" })
201
252
  ] }) });
202
253
  }
203
254
 
204
255
  // src/components/primitives/ImageDropZone.tsx
205
- import { useState as useState2 } from "react";
206
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
256
+ import { useState as useState3 } from "react";
257
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
207
258
  var ACCEPTED = /^(image|video)\//;
208
259
  function ImageDropZone({ onImageSelected, className, children }) {
209
260
  const mediaLibrary = useMediaLibrary();
210
- const [dragging, setDragging] = useState2(false);
211
- if (!mediaLibrary) return /* @__PURE__ */ jsx2(Fragment, { children });
261
+ const [dragging, setDragging] = useState3(false);
262
+ if (!mediaLibrary) return /* @__PURE__ */ jsx2(Fragment2, { children });
212
263
  const maxFileSize = mediaLibrary.siteConfig?.media.maxFileSize;
213
264
  const hasFiles = (e) => e.dataTransfer.types.includes("Files");
214
265
  const handleDragOver = (e) => {
@@ -265,6 +316,9 @@ function MediaBlock({ refValue, options, link, isEditMode, onRefChange }) {
265
316
  const captionStr = refValue.caption ? Array.isArray(refValue.caption) ? refValue.caption.join("\n") : refValue.caption : "";
266
317
  const replace = onRefChange ? (imageId) => onRefChange({ ...refValue, imageId }) : void 0;
267
318
  const baked = refValue;
319
+ const bakedWidth = typeof baked.width === "number" ? baked.width : void 0;
320
+ const bakedHeight = typeof baked.height === "number" ? baked.height : void 0;
321
+ const aspectRatio = bakedWidth && bakedHeight && !options.square ? `${bakedWidth} / ${bakedHeight}` : void 0;
268
322
  const resolved = /* @__PURE__ */ jsx3(
269
323
  ResolvedMedia,
270
324
  {
@@ -272,6 +326,8 @@ function MediaBlock({ refValue, options, link, isEditMode, onRefChange }) {
272
326
  src: baked.src,
273
327
  srcset: baked.srcset,
274
328
  alt: baked.alt,
329
+ width: bakedWidth,
330
+ height: bakedHeight,
275
331
  className: "h-full w-full",
276
332
  imgClassName: fitClass,
277
333
  invertFrom: refValue.invertFrom
@@ -293,6 +349,7 @@ function MediaBlock({ refValue, options, link, isEditMode, onRefChange }) {
293
349
  options.square && "aspect-square",
294
350
  onImageClick && "cursor-pointer"
295
351
  ),
352
+ style: aspectRatio ? { aspectRatio } : void 0,
296
353
  onClick: onImageClick,
297
354
  onPointerDown: onImageClick ? (e) => e.stopPropagation() : void 0,
298
355
  children: media
@@ -313,7 +370,7 @@ function MediaBlock({ refValue, options, link, isEditMode, onRefChange }) {
313
370
  }
314
371
 
315
372
  // src/components/primitives/EditableGrid.tsx
316
- import { useRef as useRef4, useEffect as useEffect4, useState as useState4 } from "react";
373
+ import { useRef as useRef4, useEffect as useEffect4, useState as useState5 } from "react";
317
374
  import { ImageIcon as ImageIcon2 } from "lucide-react";
318
375
 
319
376
  // src/lib/container-grid.ts
@@ -344,7 +401,7 @@ var spacerHiddenClass = {
344
401
  };
345
402
 
346
403
  // src/components/primitives/useEditableCollection.ts
347
- import { useState as useState3, useCallback as useCallback3, useRef as useRef3, useEffect as useEffect3 } from "react";
404
+ import { useState as useState4, useCallback as useCallback3, useRef as useRef3, useEffect as useEffect3 } from "react";
348
405
  var nextId = 0;
349
406
  function uid() {
350
407
  return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `_id_${nextId++}_${Date.now()}`;
@@ -357,7 +414,7 @@ function useEditableCollection({
357
414
  onChange,
358
415
  createItem
359
416
  }) {
360
- const [wrappedItems, setWrappedItems] = useState3(
417
+ const [wrappedItems, setWrappedItems] = useState4(
361
418
  () => wrapItems(items)
362
419
  );
363
420
  const prevItemsRef = useRef3(items);
@@ -437,8 +494,8 @@ function EditableGrid({
437
494
  onChange,
438
495
  createItem
439
496
  });
440
- const [dragState, setDragState] = useState4({ sourceId: null, closestEdge: null });
441
- const [isDragging, setIsDragging] = useState4(false);
497
+ const [dragState, setDragState] = useState5({ sourceId: null, closestEdge: null });
498
+ const [isDragging, setIsDragging] = useState5(false);
442
499
  useEffect4(() => {
443
500
  let cleanup;
444
501
  let cancelled = false;
@@ -3,10 +3,12 @@ interface ResolvedMediaProps {
3
3
  src?: string;
4
4
  srcset?: string;
5
5
  alt?: string;
6
+ width?: number;
7
+ height?: number;
6
8
  className?: string;
7
9
  imgClassName?: string;
8
10
  invertFrom?: string;
9
11
  }
10
- export declare function ResolvedMedia({ imageId, src: propSrc, srcset: propSrcset, alt: propAlt, className, imgClassName, invertFrom, }: ResolvedMediaProps): import("react/jsx-runtime").JSX.Element;
12
+ export declare function ResolvedMedia({ imageId, src: propSrc, srcset: propSrcset, alt: propAlt, width: propWidth, height: propHeight, className, imgClassName, invertFrom, }: ResolvedMediaProps): import("react/jsx-runtime").JSX.Element;
11
13
  export {};
12
14
  //# sourceMappingURL=ResolvedMedia.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ResolvedMedia.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/ResolvedMedia.tsx"],"names":[],"mappings":"AAIA,UAAU,kBAAkB;IAC1B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,OAAO,EACZ,SAAS,EACT,YAAY,EACZ,UAAU,GACX,EAAE,kBAAkB,2CA+BpB"}
1
+ {"version":3,"file":"ResolvedMedia.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/ResolvedMedia.tsx"],"names":[],"mappings":"AAKA,UAAU,kBAAkB;IAC1B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,UAAU,EAClB,SAAS,EACT,YAAY,EACZ,UAAU,GACX,EAAE,kBAAkB,2CAsEpB"}
@@ -11,6 +11,8 @@ interface HeadingSectionConfig<T extends string> {
11
11
  defaultHeading: string;
12
12
  /** When true, adds the `excludeFromNav` content field plus its settings toggle. */
13
13
  navExclusion?: boolean;
14
+ /** Per-type authoring note surfaced by get_section_schema. */
15
+ authoringNote: string;
14
16
  }
15
17
  /**
16
18
  * Factory for the heading section family (link / sub / sub-sub). They differ only