@drawnagency/primitives 0.1.0 → 0.2.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.
Files changed (139) hide show
  1. package/dist/auth/index.js +26 -3
  2. package/dist/chunk-2VTPWODA.js +60 -0
  3. package/dist/chunk-CS7F6IOY.js +39 -0
  4. package/dist/chunk-HOJAF4VD.js +264 -0
  5. package/dist/chunk-IP6ODLXX.js +341 -0
  6. package/dist/chunk-T4BJ6RSB.js +58 -0
  7. package/dist/chunk-UKEVUCIZ.js +200 -0
  8. package/dist/chunk-UMSFICAC.js +36 -0
  9. package/dist/index.js +156 -4
  10. package/dist/lib/index.js +62 -12
  11. package/dist/lib/sanitize.d.ts.map +1 -1
  12. package/dist/media/index.js +36 -9
  13. package/dist/schemas/index.js +52 -7
  14. package/package.json +5 -4
  15. package/src/lib/sanitize.ts +6 -2
  16. package/dist/auth/cookies.js +0 -44
  17. package/dist/auth/errors.js +0 -10
  18. package/dist/auth/security.js +0 -48
  19. package/dist/auth/types.js +0 -1
  20. package/dist/components/brandguide/ColorSwatchSettings.js +0 -10
  21. package/dist/components/brandguide/Colors.js +0 -79
  22. package/dist/components/brandguide/DoDontList.js +0 -22
  23. package/dist/components/brandguide/DoDontMediaGrid.js +0 -5
  24. package/dist/components/editor/AudiencePicker.js +0 -24
  25. package/dist/components/editor/DeleteButton.js +0 -6
  26. package/dist/components/editor/DragHandle.js +0 -8
  27. package/dist/components/editor/InsertButton.js +0 -7
  28. package/dist/components/editor/SectionWrapper.js +0 -135
  29. package/dist/components/editor/SettingsButton.js +0 -6
  30. package/dist/components/editor/SettingsForm.js +0 -64
  31. package/dist/components/editor/StatusBadge.js +0 -10
  32. package/dist/components/editor/StatusPicker.js +0 -30
  33. package/dist/components/editor/index.js +0 -7
  34. package/dist/components/primitives/CustomParagraph.js +0 -24
  35. package/dist/components/primitives/EditableGrid.js +0 -90
  36. package/dist/components/primitives/EditableList.js +0 -54
  37. package/dist/components/primitives/EditablePlainText.js +0 -52
  38. package/dist/components/primitives/EditableRichText.js +0 -86
  39. package/dist/components/primitives/HeadingSection.js +0 -7
  40. package/dist/components/primitives/IconPicker.js +0 -21
  41. package/dist/components/primitives/LinkPopover.js +0 -48
  42. package/dist/components/primitives/MediaSettingsForms.js +0 -42
  43. package/dist/components/primitives/ResolvedMedia.js +0 -9
  44. package/dist/components/primitives/RichTextToolbar.js +0 -26
  45. package/dist/components/primitives/tiptap-presets.js +0 -44
  46. package/dist/components/primitives/useEditableCollection.js +0 -61
  47. package/dist/components/primitives/useEditablePlainText.js +0 -27
  48. package/dist/components/primitives/useEditableRichText.js +0 -52
  49. package/dist/components/sections/Button/CTAButton.js +0 -18
  50. package/dist/components/sections/Button/index.js +0 -28
  51. package/dist/components/sections/Colors/index.js +0 -34
  52. package/dist/components/sections/DoDontList/index.js +0 -33
  53. package/dist/components/sections/DoDontMediaGrid/index.js +0 -41
  54. package/dist/components/sections/IconList/IconList.js +0 -131
  55. package/dist/components/sections/IconList/IconListSettings.js +0 -22
  56. package/dist/components/sections/IconList/index.js +0 -27
  57. package/dist/components/sections/LinkHeading/index.js +0 -15
  58. package/dist/components/sections/MediaGrid/MediaGrid.js +0 -62
  59. package/dist/components/sections/MediaGrid/index.js +0 -35
  60. package/dist/components/sections/Prose/Prose.js +0 -11
  61. package/dist/components/sections/Prose/index.js +0 -15
  62. package/dist/components/sections/SectionLayout.js +0 -15
  63. package/dist/components/sections/SplitContent/SplitContent.js +0 -31
  64. package/dist/components/sections/SplitContent/SplitContentSettings.js +0 -17
  65. package/dist/components/sections/SplitContent/index.js +0 -27
  66. package/dist/components/sections/SubHeading/index.js +0 -18
  67. package/dist/components/sections/SubSubHeading/index.js +0 -18
  68. package/dist/components/sections/ViewRenderer.js +0 -13
  69. package/dist/components/sections/register-schemas.js +0 -15
  70. package/dist/components/sections/register.js +0 -15
  71. package/dist/components/shared/Button.js +0 -27
  72. package/dist/components/shared/Checkbox.js +0 -10
  73. package/dist/components/shared/ColorPicker.js +0 -5
  74. package/dist/components/shared/ErrorBoundary.js +0 -30
  75. package/dist/components/shared/FontPicker.js +0 -190
  76. package/dist/components/shared/FormLabel.js +0 -5
  77. package/dist/components/shared/IconButton.js +0 -16
  78. package/dist/components/shared/Input.js +0 -8
  79. package/dist/components/shared/Navigation.js +0 -71
  80. package/dist/components/shared/PasswordInput.js +0 -11
  81. package/dist/components/shared/Popover.js +0 -33
  82. package/dist/components/shared/PopoverItem.js +0 -6
  83. package/dist/components/shared/Select.js +0 -9
  84. package/dist/components/shared/Textarea.js +0 -8
  85. package/dist/components/shared/Toggle.js +0 -5
  86. package/dist/components/shared/Tooltip.js +0 -8
  87. package/dist/components/shared/icons.js +0 -23
  88. package/dist/components/shell/AudienceAddForm.js +0 -43
  89. package/dist/components/shell/AudienceRow.js +0 -74
  90. package/dist/components/shell/EditorContext.js +0 -24
  91. package/dist/components/shell/EditorLoginForm.js +0 -46
  92. package/dist/components/shell/EditorModal.js +0 -43
  93. package/dist/components/shell/EditorModalContext.js +0 -20
  94. package/dist/components/shell/EditorShell.js +0 -483
  95. package/dist/components/shell/MediaLibraryContext.js +0 -5
  96. package/dist/components/shell/MediaLibraryModal.js +0 -145
  97. package/dist/components/shell/ProcessingIndicator.js +0 -15
  98. package/dist/components/shell/SectionSkeleton.js +0 -22
  99. package/dist/components/shell/SectionTypePicker.js +0 -15
  100. package/dist/components/shell/SiteSettingsDisplay.js +0 -28
  101. package/dist/components/shell/SiteSettingsModal.js +0 -40
  102. package/dist/components/shell/SiteSettingsUsers.js +0 -87
  103. package/dist/components/shell/SiteSettingsViewerAccess.js +0 -94
  104. package/dist/components/shell/ViewerLoginForm.js +0 -40
  105. package/dist/data/google-fonts.json +0 -7718
  106. package/dist/hooks/index.js +0 -6
  107. package/dist/hooks/useActiveHeadings.js +0 -99
  108. package/dist/hooks/useEditorPersistence.js +0 -73
  109. package/dist/hooks/useEditorPublish.js +0 -145
  110. package/dist/hooks/useFocusTrap.js +0 -51
  111. package/dist/hooks/useMediaPipeline.js +0 -253
  112. package/dist/hooks/useResolvedMedia.js +0 -39
  113. package/dist/lib/cn.js +0 -5
  114. package/dist/lib/contrast.js +0 -11
  115. package/dist/lib/dexie.js +0 -236
  116. package/dist/lib/events.js +0 -15
  117. package/dist/lib/google-fonts.js +0 -11
  118. package/dist/lib/grid.js +0 -7
  119. package/dist/lib/icons.js +0 -27
  120. package/dist/lib/loader.js +0 -57
  121. package/dist/lib/nav.js +0 -58
  122. package/dist/lib/registry.js +0 -64
  123. package/dist/lib/safeRedirect.js +0 -11
  124. package/dist/lib/sanitize.js +0 -6
  125. package/dist/lib/timestamp.js +0 -28
  126. package/dist/media/github.js +0 -60
  127. package/dist/media/queue.js +0 -116
  128. package/dist/media/resolve.js +0 -50
  129. package/dist/media/types.js +0 -1
  130. package/dist/media/utils.js +0 -41
  131. package/dist/media/videoPoster.js +0 -44
  132. package/dist/media/worker.js +0 -73
  133. package/dist/schemas/audience.js +0 -19
  134. package/dist/schemas/auth.js +0 -22
  135. package/dist/schemas/media-grid-options.js +0 -7
  136. package/dist/schemas/media.js +0 -28
  137. package/dist/schemas/sections.js +0 -12
  138. package/dist/schemas/shared.js +0 -71
  139. package/dist/schemas/site-config.js +0 -26
@@ -1,48 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from "react";
3
- import { Button } from "../shared/Button";
4
- export function LinkPopover({ editor, onClose }) {
5
- const existingAttrs = editor.getAttributes("link");
6
- const [url, setUrl] = useState(existingAttrs.href ?? "");
7
- const [openInNewTab, setOpenInNewTab] = useState(existingAttrs.target === "_blank");
8
- const inputRef = useRef(null);
9
- const hasLink = Boolean(existingAttrs.href);
10
- useEffect(() => {
11
- inputRef.current?.focus();
12
- }, []);
13
- const handleApply = () => {
14
- if (url.trim()) {
15
- editor
16
- .chain()
17
- .focus()
18
- .extendMarkRange("link")
19
- .setLink({ href: url.trim(), target: openInNewTab ? "_blank" : null })
20
- .run();
21
- }
22
- onClose();
23
- };
24
- const handleRemove = () => {
25
- editor.chain().focus().extendMarkRange("link").unsetLink().run();
26
- onClose();
27
- };
28
- const handleKeyDown = (e) => {
29
- if (e.key === "Enter") {
30
- e.preventDefault();
31
- handleApply();
32
- }
33
- else if (e.key === "Escape") {
34
- e.preventDefault();
35
- onClose();
36
- }
37
- };
38
- return (_jsxs("div", { className: "flex flex-col gap-2 rounded bg-base-accent p-3 shadow-lg", onMouseDown: (e) => e.stopPropagation(), children: [_jsx("input", { ref: inputRef, type: "url", value: url, onChange: (e) => setUrl(e.target.value), onKeyDown: handleKeyDown, placeholder: "https://example.com", className: "rounded border border-base-contrast/20 bg-base px-2 py-1 text-sm text-base-contrast placeholder:text-base-contrast/40 focus:outline-none focus:ring-1 focus:ring-primary" }), _jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm text-base-contrast", children: [_jsx("input", { type: "checkbox", checked: openInNewTab, onChange: (e) => setOpenInNewTab(e.target.checked), className: "accent-primary" }), "Open in new tab"] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "brand", size: "sm", onMouseDown: (e) => {
39
- e.preventDefault();
40
- handleApply();
41
- }, children: "Apply" }), hasLink && (_jsx(Button, { variant: "secondary", size: "sm", onMouseDown: (e) => {
42
- e.preventDefault();
43
- handleRemove();
44
- }, children: "Remove" })), _jsx(Button, { variant: "secondary", size: "sm", onMouseDown: (e) => {
45
- e.preventDefault();
46
- onClose();
47
- }, children: "Cancel" })] })] }));
48
- }
@@ -1,42 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useRef } from "react";
3
- import { cn } from "../../lib/cn";
4
- import { Check, X } from "lucide-react";
5
- import { Checkbox } from "../shared/Checkbox";
6
- import { Select } from "../shared/Select";
7
- export function ImageSettingsForm({ border, objectFit, onChange, }) {
8
- const [itemBorder, setItemBorder] = useState(border);
9
- const [fit, setFit] = useState(objectFit);
10
- return (_jsxs("div", { className: "space-y-4", children: [_jsx(Select, { label: "Object fit", value: fit ?? "", onChange: (v) => {
11
- const val = (v || undefined);
12
- setFit(val);
13
- onChange({ border: itemBorder, objectFit: val });
14
- }, options: [
15
- { value: "", label: "Default (inherit from grid)" },
16
- { value: "contain", label: "Contain" },
17
- { value: "cover", label: "Crop to fill" },
18
- ] }), _jsx(Checkbox, { checked: itemBorder ?? false, onChange: (v) => {
19
- const val = v || undefined;
20
- setItemBorder(val);
21
- onChange({ border: val, objectFit: fit });
22
- }, label: "Override border settings" })] }));
23
- }
24
- export function DoDontImageSettingsForm({ border, objectFit, doDont: initialDoDont, onChange, }) {
25
- const [doDont, setDoDont] = useState(initialDoDont);
26
- const latestRef = useRef({ border, objectFit });
27
- const handleBaseChange = (updated) => {
28
- latestRef.current = updated;
29
- onChange({ ...updated, doDont });
30
- };
31
- return (_jsxs("div", { className: "space-y-4", children: [_jsx(ImageSettingsForm, { border: border, objectFit: objectFit, onChange: handleBaseChange }), _jsx("hr", { className: "border-base-200" }), _jsxs("div", { className: "space-y-2", children: [_jsxs("button", { className: cn("cursor-pointer flex w-full items-center gap-3 rounded-md border px-3 py-2.5 text-sm transition-colors", doDont === "do"
32
- ? "border-green-600 bg-base-accent text-base-contrast"
33
- : "border-base-200 text-base-contrast-light hover:bg-base-accent"), onClick: () => {
34
- setDoDont("do");
35
- onChange({ ...latestRef.current, doDont: "do" });
36
- }, children: [_jsx(Check, { size: 16, className: "text-green-600" }), "Do"] }), _jsxs("button", { className: cn("cursor-pointer flex w-full items-center gap-3 rounded-md border px-3 py-2.5 text-sm transition-colors", doDont === "dont"
37
- ? "border-red-600 bg-base-accent text-base-contrast"
38
- : "border-base-200 text-base-contrast-light hover:bg-base-accent"), onClick: () => {
39
- setDoDont("dont");
40
- onChange({ ...latestRef.current, doDont: "dont" });
41
- }, children: [_jsx(X, { size: 16, className: "text-red-600" }), "Don't"] })] })] }));
42
- }
@@ -1,9 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { cn } from "../../lib/cn";
3
- import { ImageIcon } from "lucide-react";
4
- import { useResolvedMedia } from "../../hooks/useResolvedMedia";
5
- export function ResolvedMedia({ imageId, className, imgClassName, }) {
6
- const { src, srcset, poster, alt, kind } = useResolvedMedia(imageId);
7
- const mediaClass = cn("h-full w-full", imgClassName);
8
- return (_jsx("div", { className: className, children: src ? (kind === "video" ? (_jsx("video", { src: src, poster: poster, muted: true, playsInline: true, loop: true, autoPlay: true, className: mediaClass })) : (_jsx("img", { src: src, srcSet: srcset, alt: alt, className: mediaClass }))) : (_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: [_jsx(ImageIcon, { size: 24 }), _jsx("span", { className: "text-sm", children: "No Image" })] })) }));
9
- }
@@ -1,26 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { cn } from "../../lib/cn";
4
- import { LinkPopover } from "./LinkPopover";
5
- function ToolbarButton({ isActive, onClick, title, children }) {
6
- return (_jsx("button", { type: "button", title: title, "aria-label": title, "aria-pressed": isActive, onMouseDown: (e) => {
7
- e.preventDefault();
8
- onClick();
9
- }, className: cn("cursor-pointer rounded px-2 py-1 text-sm font-medium transition-colors", isActive
10
- ? "bg-primary text-primary-contrast"
11
- : "bg-base-accent text-base-contrast hover:opacity-80"), children: children }));
12
- }
13
- function Separator() {
14
- return _jsx("div", { className: "mx-1 h-5 w-px bg-base-contrast/20" });
15
- }
16
- export function RichTextToolbar({ editor, preset }) {
17
- const [showLinkPopover, setShowLinkPopover] = useState(false);
18
- const paragraphClass = editor.getAttributes("paragraph").class;
19
- const isLargeActive = paragraphClass === "large";
20
- const isLeadInActive = paragraphClass === "lead-in";
21
- const toggleParagraphClass = (cls) => {
22
- const next = paragraphClass === cls ? null : cls;
23
- editor.chain().focus().updateAttributes("paragraph", { class: next }).run();
24
- };
25
- return (_jsxs("div", { "data-testid": "rich-text-toolbar", className: "flex items-center gap-1 rounded border border-base-contrast/10 bg-base-accent p-1 shadow-md", children: [_jsx(ToolbarButton, { isActive: editor.isActive("bold"), onClick: () => editor.chain().focus().toggleBold().run(), title: "Bold", children: _jsx("strong", { children: "B" }) }), _jsx(ToolbarButton, { isActive: editor.isActive("italic"), onClick: () => editor.chain().focus().toggleItalic().run(), title: "Italic", children: _jsx("em", { children: "I" }) }), _jsx(ToolbarButton, { isActive: editor.isActive("underline"), onClick: () => editor.chain().focus().toggleUnderline().run(), title: "Underline", children: _jsx("span", { className: "underline", children: "U" }) }), preset === "rich" && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "relative", children: [_jsx(ToolbarButton, { isActive: editor.isActive("link") || showLinkPopover, onClick: () => setShowLinkPopover((prev) => !prev), title: "Link", children: "\uD83D\uDD17" }), showLinkPopover && (_jsx("div", { className: "absolute left-0 top-full z-50 mt-1", children: _jsx(LinkPopover, { editor: editor, onClose: () => setShowLinkPopover(false) }) }))] }), _jsx(Separator, {}), _jsx(ToolbarButton, { isActive: editor.isActive("bulletList"), onClick: () => editor.chain().focus().toggleBulletList().run(), title: "Bullet List", children: "\u2022\u2261" }), _jsx(ToolbarButton, { isActive: editor.isActive("orderedList"), onClick: () => editor.chain().focus().toggleOrderedList().run(), title: "Ordered List", children: "1\u2261" }), _jsx(Separator, {}), _jsx(ToolbarButton, { isActive: isLargeActive, onClick: () => toggleParagraphClass("large"), title: "Large Paragraph", children: "Lg" }), _jsx(ToolbarButton, { isActive: isLeadInActive, onClick: () => toggleParagraphClass("lead-in"), title: "Lead-In", children: "Li" })] }))] }));
26
- }
@@ -1,44 +0,0 @@
1
- import StarterKit from "@tiptap/starter-kit";
2
- import Underline from "@tiptap/extension-underline";
3
- import Link from "@tiptap/extension-link";
4
- import { CustomParagraph } from "./CustomParagraph";
5
- /**
6
- * Basic preset: inline formatting only.
7
- * Bold, italic, underline. No lists, links, or structural elements.
8
- */
9
- const basic = [
10
- StarterKit.configure({
11
- paragraph: false,
12
- heading: false,
13
- blockquote: false,
14
- bulletList: false,
15
- orderedList: false,
16
- listItem: false,
17
- codeBlock: false,
18
- horizontalRule: false,
19
- code: false,
20
- strike: false,
21
- }),
22
- CustomParagraph,
23
- Underline,
24
- ];
25
- /**
26
- * Rich preset: full prose editing.
27
- * Everything in basic + links, bullet lists, ordered lists,
28
- * large paragraph and lead-in variants (via CustomParagraph).
29
- */
30
- const rich = [
31
- StarterKit.configure({
32
- paragraph: false,
33
- heading: false,
34
- blockquote: false,
35
- codeBlock: false,
36
- horizontalRule: false,
37
- code: false,
38
- strike: false,
39
- }),
40
- CustomParagraph,
41
- Underline,
42
- Link.configure({ openOnClick: false }),
43
- ];
44
- export const presets = { basic, rich };
@@ -1,61 +0,0 @@
1
- import { useState, useCallback, useRef, useEffect } from "react";
2
- let nextId = 0;
3
- function uid() {
4
- return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
5
- ? crypto.randomUUID()
6
- : `_id_${nextId++}_${Date.now()}`;
7
- }
8
- function wrapItems(items) {
9
- return items.map((data) => ({ id: uid(), data }));
10
- }
11
- export function useEditableCollection({ items, onChange, createItem, }) {
12
- const [wrappedItems, setWrappedItems] = useState(() => wrapItems(items));
13
- const prevItemsRef = useRef(items);
14
- useEffect(() => {
15
- if (items === prevItemsRef.current)
16
- return;
17
- prevItemsRef.current = items;
18
- setWrappedItems((prev) => {
19
- return items.map((data, i) => {
20
- if (i < prev.length) {
21
- return { id: prev[i].id, data };
22
- }
23
- return { id: uid(), data };
24
- });
25
- });
26
- }, [items]);
27
- const onReorder = useCallback((fromIndex, toIndex) => {
28
- setWrappedItems((prev) => {
29
- const next = [...prev];
30
- const [moved] = next.splice(fromIndex, 1);
31
- next.splice(toIndex, 0, moved);
32
- onChange(next.map((w) => w.data));
33
- return next;
34
- });
35
- }, [onChange]);
36
- const onAdd = useCallback(() => {
37
- const newItem = createItem();
38
- setWrappedItems((prev) => {
39
- const next = [...prev, { id: uid(), data: newItem }];
40
- onChange(next.map((w) => w.data));
41
- return next;
42
- });
43
- }, [createItem, onChange]);
44
- const onInsert = useCallback((index) => {
45
- const newItem = createItem();
46
- setWrappedItems((prev) => {
47
- const next = [...prev];
48
- next.splice(index, 0, { id: uid(), data: newItem });
49
- onChange(next.map((w) => w.data));
50
- return next;
51
- });
52
- }, [createItem, onChange]);
53
- const onRemove = useCallback((id) => {
54
- setWrappedItems((prev) => {
55
- const next = prev.filter((w) => w.id !== id);
56
- onChange(next.map((w) => w.data));
57
- return next;
58
- });
59
- }, [onChange]);
60
- return { wrappedItems, onReorder, onAdd, onInsert, onRemove };
61
- }
@@ -1,27 +0,0 @@
1
- import { useState, useEffect, useRef, useCallback } from "react";
2
- export function useEditablePlainText({ value: propValue, onChange, multiline = false, }) {
3
- const [value, setValue] = useState(propValue);
4
- const lastPropValue = useRef(propValue);
5
- // Sync with external prop changes
6
- useEffect(() => {
7
- if (propValue !== lastPropValue.current) {
8
- setValue(propValue);
9
- lastPropValue.current = propValue;
10
- }
11
- }, [propValue]);
12
- const handleInput = useCallback((text) => {
13
- setValue(text);
14
- }, []);
15
- const handleBlur = useCallback(() => {
16
- if (value !== propValue) {
17
- onChange(value);
18
- lastPropValue.current = value;
19
- }
20
- }, [value, propValue, onChange]);
21
- return {
22
- value,
23
- handleInput,
24
- handleBlur,
25
- shouldPreventEnter: !multiline,
26
- };
27
- }
@@ -1,52 +0,0 @@
1
- import { useState, useRef, useCallback, useEffect } from "react";
2
- import { Editor } from "@tiptap/core";
3
- import { presets } from "./tiptap-presets";
4
- export function useEditableRichText({ value, onChange, preset, }) {
5
- // Dual ref+state: ref for stable callback access, state for re-renders
6
- const editorRef = useRef(null);
7
- const [editor, setEditor] = useState(null);
8
- // Refs for latest prop values (avoids stale closures in callbacks)
9
- const valueRef = useRef(value);
10
- const onChangeRef = useRef(onChange);
11
- useEffect(() => {
12
- valueRef.current = value;
13
- }, [value]);
14
- useEffect(() => {
15
- onChangeRef.current = onChange;
16
- }, [onChange]);
17
- const activate = useCallback(() => {
18
- if (editorRef.current)
19
- return;
20
- const ed = new Editor({
21
- extensions: presets[preset],
22
- content: valueRef.current,
23
- });
24
- editorRef.current = ed;
25
- setEditor(ed);
26
- }, [preset]);
27
- const deactivate = useCallback(() => {
28
- const ed = editorRef.current;
29
- if (!ed)
30
- return;
31
- const html = ed.getHTML();
32
- ed.destroy();
33
- editorRef.current = null;
34
- setEditor(null);
35
- if (html !== valueRef.current) {
36
- onChangeRef.current(html);
37
- }
38
- }, []);
39
- // Cleanup on unmount
40
- useEffect(() => {
41
- return () => {
42
- editorRef.current?.destroy();
43
- editorRef.current = null;
44
- };
45
- }, []);
46
- return {
47
- isEditorActive: editor !== null,
48
- editor,
49
- activate,
50
- deactivate,
51
- };
52
- }
@@ -1,18 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { cn } from "../../../lib/cn";
3
- import { Download } from "lucide-react";
4
- import { EditablePlainText } from "../../primitives/EditablePlainText";
5
- export default function Button({ text, href, target, download, onChange }) {
6
- const base = "inline-block rounded-md px-6 py-3 font-bold transition-colors";
7
- const variant = "border-2 border-primary text-primary hover:bg-primary hover:text-primary-contrast";
8
- if (onChange) {
9
- return (_jsxs("span", { className: cn(base, variant, "inline-flex items-center gap-2"), children: [download && _jsx(Download, { size: 16, className: "shrink-0" }), _jsx(EditablePlainText, { tag: "span", value: text, onChange: (newText) => onChange({
10
- type: "button",
11
- content: { text: newText, href, target, download },
12
- }), isEditMode: true, placeholder: "Button text" })] }));
13
- }
14
- if (href) {
15
- return (_jsxs("a", { href: href, target: target, download: download ? "" : undefined, className: cn(base, variant, download && "inline-flex items-center gap-2"), children: [download && _jsx(Download, { size: 16, className: "shrink-0" }), text] }));
16
- }
17
- return (_jsx("button", { className: cn("cursor-pointer", base, variant), children: text }));
18
- }
@@ -1,28 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { defineSection } from "../../../lib/registry";
3
- import { z } from "zod";
4
- import CTAButton from "./CTAButton";
5
- const schema = z.object({
6
- type: z.literal("button"),
7
- content: z.object({
8
- text: z.string(),
9
- href: z.string().optional(),
10
- target: z.string().optional(),
11
- download: z.boolean().optional(),
12
- }),
13
- });
14
- export default defineSection({
15
- type: "button",
16
- label: "Button",
17
- schema,
18
- component: ({ content, onChange }) => (_jsx(CTAButton, { text: content.content.text, href: content.content.href, target: content.content.target, download: content.content.download, onChange: onChange ? (c) => onChange(c) : undefined })),
19
- defaults: () => ({ type: "button", content: { text: "Button" } }),
20
- settings: {
21
- href: { type: "text", label: "URL", default: "", target: "content", placeholder: "https://..." },
22
- target: {
23
- type: "select", label: "Target", default: "_self", target: "content",
24
- options: [{ label: "Same tab (_self)", value: "_self" }, { label: "New tab (_blank)", value: "_blank" }],
25
- },
26
- download: { type: "checkbox", label: "Download link", default: false, target: "content" },
27
- },
28
- });
@@ -1,34 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { defineSection } from "../../../lib/registry";
3
- import { z } from "zod";
4
- import { ColorItemSchema } from "../../../schemas/shared";
5
- import Colors from "../../brandguide/Colors";
6
- const schema = z.object({
7
- type: z.literal("colors"),
8
- content: z.object({ colors: z.array(ColorItemSchema) }),
9
- options: z.object({
10
- label: z.string().optional(),
11
- columns: z.number().int().min(2).max(4).optional(),
12
- collapsing: z.boolean().optional(),
13
- showLabel: z.boolean().optional(),
14
- }).optional(),
15
- });
16
- export default defineSection({
17
- type: "colors",
18
- label: "Colors",
19
- schema,
20
- component: ({ content, options, onChange, openModal }) => (_jsx(Colors, { colors: content.content.colors, columns: options?.columns, label: options?.label, collapsing: options?.collapsing, showLabel: options?.showLabel, onChange: onChange ? (c) => onChange(c) : undefined, openModal: openModal })),
21
- defaults: () => ({
22
- type: "colors",
23
- content: { colors: [{ spaces: [{ hex: "#000000" }] }] },
24
- }),
25
- settings: {
26
- columns: {
27
- type: "select", label: "Columns", default: "3", coerce: "number",
28
- options: [{ label: "2", value: "2" }, { label: "3", value: "3" }, { label: "4", value: "4" }],
29
- },
30
- label: { type: "text", label: "Label", default: "", placeholder: "Color Section Label" },
31
- showLabel: { type: "checkbox", label: "Show label", default: true },
32
- collapsing: { type: "checkbox", label: "Collapsing layout", default: false },
33
- },
34
- });
@@ -1,33 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { defineSection } from "../../../lib/registry";
3
- import { z } from "zod";
4
- import DoDontList from "../../brandguide/DoDontList";
5
- const DoDontItemSchema = z.object({ label: z.string(), text: z.string(), icon: z.string().optional() });
6
- const schema = z.object({
7
- type: z.literal("do_dont"),
8
- content: z.object({
9
- doItems: z.array(DoDontItemSchema),
10
- dontItems: z.array(DoDontItemSchema),
11
- }),
12
- options: z.object({
13
- showLabel: z.boolean().optional(),
14
- stackText: z.boolean().optional(),
15
- }).optional(),
16
- });
17
- export default defineSection({
18
- type: "do_dont",
19
- label: "Do / Don't",
20
- schema,
21
- component: ({ content, options, onChange }) => (_jsx(DoDontList, { doItems: content.content.doItems, dontItems: content.content.dontItems, showLabel: options?.showLabel, stackText: options?.stackText, onChange: onChange ? (c) => onChange(c) : undefined })),
22
- defaults: () => ({
23
- type: "do_dont",
24
- content: {
25
- doItems: [{ label: "Do", text: "" }],
26
- dontItems: [{ label: "Don't", text: "" }],
27
- },
28
- }),
29
- settings: {
30
- showLabel: { type: "checkbox", label: "Show labels", default: true },
31
- stackText: { type: "checkbox", label: "Stack label above text", default: false },
32
- },
33
- });
@@ -1,41 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { defineSection } from "../../../lib/registry";
3
- import { z } from "zod";
4
- import { MediaReferenceSchema } from "../../../schemas/shared";
5
- import { MediaGridOptionsSchema } from "../../../schemas/media-grid-options";
6
- import DoDontMediaGrid from "../../brandguide/DoDontMediaGrid";
7
- const schema = z.object({
8
- type: z.literal("do_dont_grid"),
9
- content: z.object({
10
- columns: z.number().int().min(1).max(5),
11
- media: z.array(MediaReferenceSchema),
12
- }),
13
- options: MediaGridOptionsSchema,
14
- });
15
- export default defineSection({
16
- type: "do_dont_grid",
17
- label: "Do / Don't Grid",
18
- schema,
19
- component: ({ content, options, onChange, openModal }) => (_jsx(DoDontMediaGrid, { media: content.content.media, columns: content.content.columns, square: options?.square, border: options?.border, crop: options?.crop, showCaptions: options?.showCaptions, onChange: onChange ? (c) => onChange(c) : undefined, openModal: openModal })),
20
- defaults: () => ({
21
- type: "do_dont_grid",
22
- options: {},
23
- content: {
24
- columns: 2,
25
- media: [
26
- { type: "doDontImage", imageId: "", doDont: "do" },
27
- { type: "doDontImage", imageId: "", doDont: "dont" },
28
- ],
29
- },
30
- }),
31
- settings: {
32
- columns: {
33
- type: "select", label: "Columns", default: "2", target: "content", coerce: "number",
34
- options: [{ label: "1", value: "1" }, { label: "2", value: "2" }, { label: "3", value: "3" }, { label: "4", value: "4" }],
35
- },
36
- border: { type: "checkbox", label: "Show border", default: false },
37
- square: { type: "checkbox", label: "Square aspect ratio", default: false },
38
- crop: { type: "checkbox", label: "Crop to fill", default: false },
39
- showCaptions: { type: "checkbox", label: "Show captions", default: false },
40
- },
41
- });
@@ -1,131 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef, useEffect, useState, useCallback } from "react";
3
- import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
4
- import { cn } from "../../../lib/cn";
5
- import { getIcon } from "../../../lib/icons";
6
- import { useEditableCollection } from "../../primitives/useEditableCollection";
7
- import { EditablePlainText } from "../../primitives/EditablePlainText";
8
- import { DragHandle, DeleteIcon, AddIcon } from "../../shared/icons";
9
- import { IconButton } from "../../shared/IconButton";
10
- import { IconPicker } from "../../primitives/IconPicker";
11
- export default function IconList({ items, icon = null, showLabel = true, stackText = false, labelClassName = "font-bold text-base-contrast", textClassName = "text-base-contrast-light", iconClassName = "text-primary", onChange, onItemsChange, }) {
12
- const isEditMode = !!onChange || !!onItemsChange;
13
- const emitItems = useCallback((newItems) => {
14
- onChange?.({
15
- type: "icon_list",
16
- content: { items: newItems },
17
- options: { icon, showLabel, stackText },
18
- });
19
- onItemsChange?.(newItems);
20
- }, [onChange, onItemsChange, icon, showLabel, stackText]);
21
- if (!isEditMode) {
22
- return (_jsx(ViewIconList, { items: items, defaultIcon: icon, showLabel: showLabel, stackText: stackText, labelClassName: labelClassName, textClassName: textClassName, iconClassName: iconClassName }));
23
- }
24
- return (_jsx(EditIconList, { items: items, defaultIcon: icon, showLabel: showLabel, stackText: stackText, labelClassName: labelClassName, textClassName: textClassName, iconClassName: iconClassName, onItemsChange: emitItems }));
25
- }
26
- // ---------------------------------------------------------------------------
27
- // Resolve the effective icon for an item: per-item override > section default
28
- // ---------------------------------------------------------------------------
29
- function resolveIcon(item, defaultIcon) {
30
- const id = item.icon ?? defaultIcon;
31
- return id ? getIcon(id) : null;
32
- }
33
- // ---------------------------------------------------------------------------
34
- // View mode
35
- // ---------------------------------------------------------------------------
36
- function ViewIconList({ items, defaultIcon, showLabel, stackText, labelClassName, textClassName, iconClassName, }) {
37
- const hasAnyIcon = items.some((item) => resolveIcon(item, defaultIcon));
38
- return (_jsx("div", { className: "grid gap-4 pb-4", children: items.map((item, i) => {
39
- const iconEntry = resolveIcon(item, defaultIcon);
40
- return (_jsxs("div", { className: cn("grid gap-x-3", hasAnyIcon ? "grid-cols-[24px_1fr]" : "grid-cols-1"), children: [hasAnyIcon && (_jsx("div", { "data-testid": "icon-list-icon", className: cn("flex items-start pt-0.5", iconClassName), children: iconEntry && _jsx(iconEntry.icon, { size: 18 }) })), _jsxs("div", { "data-testid": "icon-list-content", className: stackText ? "flex flex-col" : undefined, children: [showLabel && (_jsxs("span", { className: labelClassName, children: [item.label, !stackText && " "] })), _jsx("span", { className: textClassName, children: item.text })] })] }, i));
41
- }) }));
42
- }
43
- // ---------------------------------------------------------------------------
44
- // Edit mode
45
- // ---------------------------------------------------------------------------
46
- function EditIconList({ items, defaultIcon, showLabel, stackText, labelClassName, textClassName, iconClassName, onItemsChange, }) {
47
- const { wrappedItems, onReorder, onAdd, onRemove } = useEditableCollection({
48
- items,
49
- onChange: onItemsChange,
50
- createItem: () => ({ label: "", text: "" }),
51
- });
52
- const [dragState, setDragState] = useState({ sourceId: null, targetId: null });
53
- const updateItem = useCallback((index, patch) => {
54
- const updated = wrappedItems.map((w, i) => i === index ? { ...w.data, ...patch } : w.data);
55
- onItemsChange(updated);
56
- }, [wrappedItems, onItemsChange]);
57
- const hasAnyIcon = items.some((item) => resolveIcon(item, defaultIcon));
58
- return (_jsxs("div", { className: "group/list relative grid gap-4 pb-4", children: [wrappedItems.map((wrapped, index) => (_jsx(EditableRow, { id: wrapped.id, index: index, item: wrapped.data, defaultIcon: defaultIcon, hasAnyIcon: hasAnyIcon, showLabel: showLabel, stackText: stackText, labelClassName: labelClassName, textClassName: textClassName, iconClassName: iconClassName, dragState: dragState, setDragState: setDragState, onReorder: onReorder, onRemove: onRemove, onUpdateItem: (patch) => updateItem(index, patch) }, wrapped.id))), _jsx(TrailingDropZone, { index: wrappedItems.length, dragState: dragState, setDragState: setDragState, onReorder: onReorder }), _jsx(IconButton, { icon: _jsx(AddIcon, { size: 16 }), label: "Add item", size: "lg", intent: "primary", onClick: onAdd, className: "absolute -bottom-6 left-1/2 z-20 -translate-x-1/2 rounded-full border border-base-200 bg-base opacity-0 transition-opacity group-hover/list:opacity-100" })] }));
59
- }
60
- function EditableRow({ id, index, item, defaultIcon, hasAnyIcon, showLabel, stackText, labelClassName, textClassName, iconClassName, dragState, setDragState, onReorder, onRemove, onUpdateItem, }) {
61
- const rowRef = useRef(null);
62
- const handleRef = useRef(null);
63
- const [iconHover, setIconHover] = useState(false);
64
- const [pickerOpen, setPickerOpen] = useState(false);
65
- const iconEntry = resolveIcon(item, defaultIcon);
66
- const effectiveIconId = item.icon ?? defaultIcon ?? null;
67
- useEffect(() => {
68
- const row = rowRef.current;
69
- const handle = handleRef.current;
70
- if (!row || !handle)
71
- return;
72
- const cleanupDraggable = draggable({
73
- element: handle,
74
- getInitialData: () => ({ dragType: "icon-list-row", id, index }),
75
- onDragStart: () => setDragState({ sourceId: id, targetId: null }),
76
- onDrop: () => setDragState({ sourceId: null, targetId: null }),
77
- });
78
- const cleanupDropTarget = dropTargetForElements({
79
- element: row,
80
- canDrop: ({ source }) => source.data.dragType === "icon-list-row",
81
- getData: () => ({ id, index }),
82
- onDragEnter: () => setDragState((prev) => ({ ...prev, targetId: id })),
83
- onDragLeave: () => setDragState((prev) => prev.targetId === id ? { ...prev, targetId: null } : prev),
84
- onDrop: ({ source }) => {
85
- const fromIndex = source.data.index;
86
- if (fromIndex !== index)
87
- onReorder(fromIndex, index);
88
- },
89
- });
90
- return () => {
91
- cleanupDraggable();
92
- cleanupDropTarget();
93
- };
94
- }, [id, index, onReorder, setDragState]);
95
- const isDropTarget = dragState.targetId === id && dragState.sourceId !== id;
96
- return (_jsxs("div", { ref: rowRef, className: cn("group/row relative grid gap-x-3", hasAnyIcon ? "grid-cols-[24px_1fr]" : "grid-cols-1", isDropTarget && "border-t-2 border-primary"), children: [_jsx(IconButton, { ref: handleRef, icon: _jsx(DragHandle, { size: 16 }), label: "Drag to reorder", size: "sm", className: cn("absolute -left-7 top-0 shrink-0 cursor-grab rounded-md text-base-contrast-light/80 hover:bg-base-contrast-light/10 hover:text-base-contrast active:cursor-grabbing", iconHover
97
- ? "opacity-0 pointer-events-none"
98
- : "opacity-0 group-hover/row:opacity-100 no-hover:opacity-100"), tabIndex: -1 }), hasAnyIcon && (_jsxs("div", { className: cn("relative flex items-start pt-0.5", iconClassName), onMouseEnter: () => setIconHover(true), onMouseLeave: () => setIconHover(false), children: [iconEntry ? (_jsx("button", { className: "cursor-pointer transition-opacity hover:opacity-70", "aria-label": "Change icon", onClick: () => setPickerOpen((prev) => !prev), children: _jsx(iconEntry.icon, { size: 18 }) })) : null, iconHover && !pickerOpen && iconEntry && (_jsx("span", { className: "pointer-events-none absolute right-full mr-3 top-0.5 whitespace-nowrap text-xs text-base-contrast-light/80", children: "Edit" })), pickerOpen && (_jsx("div", { className: "absolute top-full left-0 z-50 mt-1", children: _jsx(IconPicker, { selected: effectiveIconId, showRemove: false, onSelect: (newIcon) => {
99
- if (newIcon !== null)
100
- onUpdateItem({ icon: newIcon });
101
- setPickerOpen(false);
102
- setIconHover(false);
103
- }, onClose: () => {
104
- setPickerOpen(false);
105
- setIconHover(false);
106
- } }) }))] })), _jsxs("div", { className: stackText ? "flex flex-col" : undefined, children: [showLabel && (_jsx(EditablePlainText, { tag: "span", value: item.label, onChange: (label) => onUpdateItem({ label }), isEditMode: true, placeholder: "Label", className: cn(labelClassName, !stackText && "mr-1") })), _jsx(EditablePlainText, { tag: "span", value: item.text, onChange: (text) => onUpdateItem({ text }), isEditMode: true, placeholder: "Text", className: textClassName })] }), _jsx(IconButton, { icon: _jsx(DeleteIcon, { size: 14 }), label: "Delete item", size: "sm", intent: "destructive", onClick: () => onRemove(id), className: "absolute -right-7 top-0 opacity-0 group-hover/row:opacity-100 no-hover:opacity-100" })] }));
107
- }
108
- function TrailingDropZone({ index, dragState, setDragState, onReorder, }) {
109
- const ref = useRef(null);
110
- const trailingId = "__trailing__";
111
- useEffect(() => {
112
- const el = ref.current;
113
- if (!el)
114
- return;
115
- const cleanup = dropTargetForElements({
116
- element: el,
117
- canDrop: ({ source }) => source.data.dragType === "icon-list-row",
118
- getData: () => ({ id: trailingId, index }),
119
- onDragEnter: () => setDragState((prev) => ({ ...prev, targetId: trailingId })),
120
- onDragLeave: () => setDragState((prev) => prev.targetId === trailingId ? { ...prev, targetId: null } : prev),
121
- onDrop: ({ source }) => {
122
- const fromIndex = source.data.index;
123
- if (fromIndex !== index - 1)
124
- onReorder(fromIndex, index - 1);
125
- },
126
- });
127
- return cleanup;
128
- }, [index, onReorder, setDragState]);
129
- const isDropTarget = dragState.targetId === trailingId && dragState.sourceId !== null;
130
- return (_jsx("div", { ref: ref, className: cn("absolute bottom-0 left-0 right-0 h-4", isDropTarget && "border-t-2 border-primary") }));
131
- }
@@ -1,22 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { Checkbox } from "../../shared/Checkbox";
4
- import { FormLabel } from "../../shared/FormLabel";
5
- import { IconPicker } from "../../primitives/IconPicker";
6
- import { getIcon } from "../../../lib/icons";
7
- export function IconListSettings({ icon: initialIcon, showLabel: initialShowLabel, stackText: initialStackText, onChange, }) {
8
- const [icon, setIcon] = useState(initialIcon);
9
- const [showLabel, setShowLabel] = useState(initialShowLabel);
10
- const [stackText, setStackText] = useState(initialStackText);
11
- const [pickerOpen, setPickerOpen] = useState(false);
12
- const emit = (overrides) => onChange({ icon, showLabel, stackText, ...overrides });
13
- const showIcons = icon !== null;
14
- const iconEntry = icon ? getIcon(icon) : null;
15
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "relative", children: [_jsx(FormLabel, { htmlFor: "icon-picker-btn", children: "Default Icon" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("button", { id: "icon-picker-btn", 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", onClick: () => setPickerOpen((prev) => !prev), children: [iconEntry && _jsx(iconEntry.icon, { size: 16 }), _jsx("span", { children: iconEntry?.label ?? "None" })] }), icon && (_jsx("button", { className: "cursor-pointer text-xs text-base-contrast-light hover:text-base-contrast", onClick: () => { setIcon(null); emit({ icon: null }); }, children: "Clear" }))] }), pickerOpen && (_jsx("div", { className: "absolute top-full left-0 z-50 mt-1", children: _jsx(IconPicker, { selected: icon, onSelect: (newIcon) => {
16
- if (newIcon !== null) {
17
- setIcon(newIcon);
18
- emit({ icon: newIcon });
19
- }
20
- setPickerOpen(false);
21
- }, onClose: () => setPickerOpen(false) }) }))] }), _jsx(Checkbox, { checked: showLabel, onChange: (v) => { setShowLabel(v); emit({ showLabel: v }); }, label: "Show labels" }), showLabel && (_jsx(Checkbox, { checked: stackText, onChange: (v) => { setStackText(v); emit({ stackText: v }); }, label: "Stack label above text" }))] }));
22
- }