@drawnagency/primitives 0.1.1 → 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 +4 -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,16 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { forwardRef } from "react";
3
- import { cn } from "../../lib/cn";
4
- const sizeClasses = {
5
- sm: "h-chrome-sm w-chrome-sm",
6
- md: "h-chrome-md w-chrome-md",
7
- lg: "h-chrome-lg w-chrome-lg",
8
- };
9
- const intentClasses = {
10
- default: "text-base-contrast-light hover:text-base-contrast",
11
- destructive: "text-destructive hover:text-destructive-hover",
12
- primary: "text-primary/60 hover:text-primary",
13
- };
14
- export const IconButton = forwardRef(function IconButton({ icon, label, size = "md", intent = "default", className, ...rest }, ref) {
15
- return (_jsx("button", { ref: ref, type: "button", className: cn("cursor-pointer flex items-center justify-center rounded transition-colors", sizeClasses[size], intentClasses[intent], className), "aria-label": label, ...rest, children: icon }));
16
- });
@@ -1,8 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { forwardRef, useId } from "react";
3
- import { cn } from "../../lib/cn";
4
- import { FormLabel } from "./FormLabel";
5
- export const Input = forwardRef(function Input({ label, value, onChange, type = "text", className, disabled, ...rest }, ref) {
6
- const id = useId();
7
- return (_jsxs("div", { className: className, children: [_jsx(FormLabel, { htmlFor: id, children: label }), _jsx("input", { ref: ref, id: id, type: type, value: value, onChange: (e) => onChange(e.target.value), disabled: disabled, className: cn("w-full rounded border border-base-200 bg-base px-3 py-2 text-sm text-base-contrast", "focus:border-base-contrast focus:outline-none focus:ring-1 focus:ring-base-contrast", disabled && "cursor-not-allowed opacity-50"), ...rest })] }));
8
- });
@@ -1,71 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState, useCallback, useEffect } from "react";
3
- import { cn } from "../../lib/cn";
4
- import { Toggle } from "./Toggle";
5
- import { editModeEvent, navChangeEvent, darkModeEvent } from "../../lib/events";
6
- import { useActiveHeadings } from "../../hooks/useActiveHeadings";
7
- export default function Navigation({ navLinks: initialNavLinks, siteName, darkMode }) {
8
- const [isOpen, setIsOpen] = useState(false);
9
- const [isEditMode, setIsEditMode] = useState(false);
10
- const [currentDarkMode, setCurrentDarkMode] = useState(darkMode);
11
- const [isDark, setIsDark] = useState(false);
12
- const [navLinks, setNavLinks] = useState(initialNavLinks);
13
- useEffect(() => {
14
- const unlistenEdit = editModeEvent.listen(({ isEditMode }) => setIsEditMode(isEditMode));
15
- const unlistenNav = navChangeEvent.listen((links) => setNavLinks(links));
16
- const unlistenDark = darkModeEvent.listen((mode) => setCurrentDarkMode(mode));
17
- return () => { unlistenEdit(); unlistenNav(); unlistenDark(); };
18
- }, []);
19
- useEffect(() => {
20
- const root = document.documentElement;
21
- setIsDark(root.classList.contains("dark"));
22
- const observer = new MutationObserver(() => {
23
- setIsDark(root.classList.contains("dark"));
24
- });
25
- observer.observe(root, { attributes: true, attributeFilter: ["class"] });
26
- return () => observer.disconnect();
27
- }, []);
28
- const handleThemeToggle = useCallback((next) => {
29
- const root = document.documentElement;
30
- root.classList.add("theme-changing");
31
- root.classList.toggle("dark", next);
32
- localStorage.setItem("theme", next ? "dark" : "light");
33
- setTimeout(() => root.classList.remove("theme-changing"), 200);
34
- }, []);
35
- // Build ID maps for scroll tracking
36
- const parentIds = navLinks.map((item) => item.href.replace("#", ""));
37
- const childIdsByParent = {};
38
- const grandchildIdsByChild = {};
39
- for (const parent of navLinks) {
40
- const pid = parent.href.replace("#", "");
41
- childIdsByParent[pid] = parent.children.map((c) => c.href.replace("#", ""));
42
- for (const child of parent.children) {
43
- const cid = child.href.replace("#", "");
44
- grandchildIdsByChild[cid] = child.children.map((gc) => gc.href.replace("#", ""));
45
- }
46
- }
47
- const { activeParentId, activeChildId, activeGrandchildId, setActiveSection } = useActiveHeadings(parentIds, childIdsByParent, grandchildIdsByChild);
48
- const handleNav = useCallback((href) => {
49
- const id = href.replace("#", "");
50
- const el = document.getElementById(id);
51
- if (el) {
52
- el.scrollIntoView({ behavior: "smooth" });
53
- setActiveSection(id);
54
- }
55
- setIsOpen(false);
56
- }, [setActiveSection]);
57
- return (_jsxs(_Fragment, { children: [_jsxs("header", { className: "fixed top-0 left-0 right-0 z-50 flex h-16 items-center justify-between bg-base px-4 lg:hidden", children: [_jsx("span", { className: "text-lg font-bold text-primary", children: siteName }), _jsx("button", { onClick: () => setIsOpen(!isOpen), className: "cursor-pointer p-2 text-base-contrast", "aria-label": "Toggle navigation", children: _jsx("svg", { className: "h-6 w-6", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: isOpen
58
- ? _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" })
59
- : _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" }) }) })] }), isOpen && (_jsx("div", { className: "fixed inset-0 z-40 bg-black/50 lg:hidden", onClick: () => setIsOpen(false) })), _jsxs("nav", { className: cn("fixed top-0 left-0 z-40 h-full w-64 overflow-y-auto bg-base pt-16 transition-transform lg:translate-x-0", isOpen ? "translate-x-0" : "-translate-x-full"), children: [_jsx("div", { className: "hidden px-4 py-4 lg:block", children: _jsx("span", { className: "text-lg font-bold text-primary", children: siteName }) }), _jsx("ul", { className: "space-y-1 px-4 py-2", children: navLinks.map((parent) => {
60
- const pid = parent.href.replace("#", "");
61
- const isActiveParent = activeParentId === pid;
62
- return (_jsxs("li", { children: [_jsx("button", { onClick: () => handleNav(parent.href), className: cn("cursor-pointer w-full rounded px-3 py-2 text-left text-sm font-bold transition-colors", isActiveParent ? "text-primary" : "text-base-contrast hover:text-primary", !isEditMode && parent.status && parent.status !== "published" && "opacity-50"), children: parent.label }), isActiveParent && parent.children.length > 0 && (_jsx("ul", { className: "ml-3 mt-1 space-y-1 border-l border-base-200 pl-3", children: parent.children.map((child) => {
63
- const cid = child.href.replace("#", "");
64
- const isActiveChild = activeChildId === cid;
65
- return (_jsxs("li", { children: [_jsx("button", { onClick: () => handleNav(child.href), className: cn("cursor-pointer w-full rounded px-2 py-1 text-left text-sm transition-colors", isActiveChild ? "font-bold text-primary" : "text-base-contrast-light hover:text-primary", !isEditMode && child.status && child.status !== "published" && "opacity-50"), children: child.label }), isActiveChild && child.children.length > 0 && (_jsx("ul", { className: "ml-3 mt-1 space-y-1", children: child.children.map((gc) => {
66
- const gid = gc.href.replace("#", "");
67
- return (_jsx("li", { children: _jsx("button", { onClick: () => handleNav(gc.href), className: cn("cursor-pointer block w-full px-2 py-1 text-left text-xs transition-colors", activeGrandchildId === gid ? "font-bold text-primary" : "text-base-contrast-light hover:text-primary", !isEditMode && gc.status && gc.status !== "published" && "opacity-50"), children: gc.label }) }, gid));
68
- }) }))] }, cid));
69
- }) }))] }, pid));
70
- }) }), currentDarkMode === "optional" && (_jsx("div", { className: "mt-auto border-t border-base-200 px-4 py-4", children: _jsxs("div", { className: "flex items-center gap-2 text-sm text-base-contrast-light", children: [_jsx("span", { className: cn(!isDark && "text-base-contrast"), children: "Light" }), _jsx(Toggle, { checked: isDark, onChange: handleThemeToggle, label: "Toggle dark mode" }), _jsx("span", { className: cn(isDark && "text-base-contrast"), children: "Dark" })] }) }))] })] }));
71
- }
@@ -1,11 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useId, useState } from "react";
3
- import { Eye, EyeOff } from "lucide-react";
4
- import { cn } from "../../lib/cn";
5
- import { IconButton } from "./IconButton";
6
- import { FormLabel } from "./FormLabel";
7
- export function PasswordInput({ label, value, onChange, autoComplete, placeholder, disabled, showToggle = false, className, }) {
8
- const id = useId();
9
- const [revealed, setRevealed] = useState(false);
10
- return (_jsxs("div", { className: className, children: [_jsx(FormLabel, { htmlFor: id, children: label }), _jsxs("div", { className: "relative", children: [_jsx("input", { id: id, type: revealed ? "text" : "password", value: value, onChange: (e) => onChange(e.target.value), autoComplete: autoComplete, placeholder: placeholder, disabled: disabled, className: cn("w-full rounded border border-base-200 bg-base px-3 py-2 text-sm text-base-contrast", "focus:border-base-contrast focus:outline-none focus:ring-1 focus:ring-base-contrast", showToggle && "pr-9", disabled && "cursor-not-allowed opacity-50") }), showToggle && (_jsx(IconButton, { icon: revealed ? _jsx(EyeOff, { size: 16 }) : _jsx(Eye, { size: 16 }), label: revealed ? "Hide password" : "Show password", size: "sm", "aria-pressed": revealed, disabled: disabled, onClick: () => setRevealed((v) => !v), className: "absolute inset-y-0 right-0 my-auto px-2 disabled:cursor-not-allowed disabled:opacity-50" }))] })] }));
11
- }
@@ -1,33 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect, useRef } from "react";
3
- import { useFocusTrap } from "../../hooks/useFocusTrap";
4
- import { cn } from "../../lib/cn";
5
- export function Popover({ isOpen, onClose, anchorRef, children, align = "start", className, }) {
6
- const panelRef = useRef(null);
7
- useFocusTrap(panelRef, isOpen);
8
- useEffect(() => {
9
- if (!isOpen)
10
- return;
11
- function onKeyDown(e) {
12
- if (e.key === "Escape")
13
- onClose();
14
- }
15
- function onMouseDown(e) {
16
- const target = e.target;
17
- if (panelRef.current?.contains(target))
18
- return;
19
- if (anchorRef.current?.contains(target))
20
- return;
21
- onClose();
22
- }
23
- document.addEventListener("keydown", onKeyDown);
24
- document.addEventListener("mousedown", onMouseDown);
25
- return () => {
26
- document.removeEventListener("keydown", onKeyDown);
27
- document.removeEventListener("mousedown", onMouseDown);
28
- };
29
- }, [isOpen, onClose, anchorRef]);
30
- if (!isOpen)
31
- return null;
32
- return (_jsx("div", { ref: panelRef, role: "dialog", "aria-modal": "false", className: cn("absolute top-full z-50 mt-1 rounded-md border border-base-200 bg-base shadow-lg", align === "end" ? "right-0" : "left-0", className), children: children }));
33
- }
@@ -1,6 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { forwardRef } from "react";
3
- import { cn } from "../../lib/cn";
4
- export const PopoverItem = forwardRef(function PopoverItem({ className, children, ...rest }, ref) {
5
- return (_jsx("button", { ref: ref, type: "button", className: cn("flex w-full cursor-pointer items-center gap-3 px-3 py-1.5 text-left hover:bg-base-accent", className), ...rest, children: children }));
6
- });
@@ -1,9 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useId } from "react";
3
- import { ChevronDown } from "lucide-react";
4
- import { cn } from "../../lib/cn";
5
- import { FormLabel } from "./FormLabel";
6
- export function Select({ label, value, onChange, options, disabled, className, selectClassName, }) {
7
- const id = useId();
8
- return (_jsxs("div", { className: className, children: [label && _jsx(FormLabel, { htmlFor: id, children: label }), _jsxs("div", { className: "relative", children: [_jsx("select", { id: id, value: value, onChange: (e) => onChange(e.target.value), disabled: disabled, className: cn("w-full appearance-none rounded border border-base-200 bg-base py-2 pr-8 pl-3 text-sm text-base-contrast", "focus:border-base-contrast focus:outline-none focus:ring-1 focus:ring-base-contrast", disabled && "cursor-not-allowed opacity-50", selectClassName), children: options.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value))) }), _jsx(ChevronDown, { size: 14, className: "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-base-contrast-light" })] })] }));
9
- }
@@ -1,8 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { forwardRef, useId } from "react";
3
- import { cn } from "../../lib/cn";
4
- import { FormLabel } from "./FormLabel";
5
- export const Textarea = forwardRef(function Textarea({ label, value, onChange, rows = 3, className, disabled, ...rest }, ref) {
6
- const id = useId();
7
- return (_jsxs("div", { className: className, children: [_jsx(FormLabel, { htmlFor: id, children: label }), _jsx("textarea", { ref: ref, id: id, value: value, onChange: (e) => onChange(e.target.value), rows: rows, disabled: disabled, className: cn("w-full resize-none rounded border border-base-200 bg-base px-3 py-2 text-sm text-base-contrast", "focus:border-base-contrast focus:outline-none focus:ring-1 focus:ring-base-contrast", disabled && "cursor-not-allowed opacity-50"), ...rest })] }));
8
- });
@@ -1,5 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { cn } from "../../lib/cn";
3
- export function Toggle({ checked, onChange, label, disabled, className }) {
4
- return (_jsx("button", { type: "button", role: "switch", "aria-checked": checked, "aria-label": label, disabled: disabled, onClick: () => onChange(!checked), className: cn("relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors", checked ? "bg-primary" : "bg-base-200", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", className), children: _jsx("span", { className: cn("inline-block h-4 w-4 transform rounded-full bg-base shadow transition", checked ? "translate-x-[18px]" : "translate-x-0.5") }) }));
5
- }
@@ -1,8 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { cn } from "../../lib/cn";
3
- export function Tooltip({ content, children, className }) {
4
- return (_jsxs("div", { className: cn("group/tooltip relative inline-flex", className), children: [children, _jsx("span", { className: "pointer-events-none absolute left-1/2 top-full z-50 mt-1.5 -translate-x-1/2 opacity-0 transition-opacity delay-300 group-hover/tooltip:opacity-100", role: "tooltip", children: _jsx("span", { className: "block whitespace-nowrap rounded bg-tooltip px-2 py-1 text-center text-xs leading-relaxed text-tooltip-muted", children: content }) })] }));
5
- }
6
- export function Kbd({ children }) {
7
- return _jsx("strong", { className: "text-tooltip-contrast", children: children });
8
- }
@@ -1,23 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- /**
3
- * Shared icon button presets used across editor chrome.
4
- * Each returns a styled element ready to drop into any context.
5
- * Sizing and visibility are left to the consumer via className.
6
- */
7
- import { GripVertical, Plus, Trash2, X, Settings } from "lucide-react";
8
- export function DragHandle({ size = 16 }) {
9
- return _jsx(GripVertical, { size: size });
10
- }
11
- export function AddIcon({ size = 14 }) {
12
- return _jsx(Plus, { size: size });
13
- }
14
- export function RemoveIcon({ size = 14 }) {
15
- return _jsx(X, { size: size });
16
- }
17
- export function DeleteIcon({ size = 14 }) {
18
- return _jsx(Trash2, { size: size });
19
- }
20
- export function SettingsIcon({ size = 14 }) {
21
- return _jsx(Settings, { size: size });
22
- }
23
- export { GripVertical, Plus, Trash2, X, Settings } from "lucide-react";
@@ -1,43 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { AudienceColorSchema } from "../../schemas/audience";
4
- import { Input } from "../shared/Input";
5
- import { PasswordInput } from "../shared/PasswordInput";
6
- import { Button } from "../shared/Button";
7
- import { ColorPicker } from "../shared/ColorPicker";
8
- const DEFAULT_COLOR = "#3b82f6";
9
- export function AudienceAddForm({ onSubmit, onCancel }) {
10
- const [name, setName] = useState("");
11
- const [color, setColor] = useState(DEFAULT_COLOR);
12
- const [password, setPassword] = useState("");
13
- const [error, setError] = useState(null);
14
- const [submitting, setSubmitting] = useState(false);
15
- async function handleSubmit(e) {
16
- e.preventDefault();
17
- setError(null);
18
- if (name.trim().length === 0) {
19
- setError("Name is required");
20
- return;
21
- }
22
- const colorResult = AudienceColorSchema.safeParse(color);
23
- if (!colorResult.success) {
24
- setError("Invalid color");
25
- return;
26
- }
27
- if (password.length === 0) {
28
- setError("Password is required");
29
- return;
30
- }
31
- setSubmitting(true);
32
- try {
33
- await onSubmit({ displayName: name, color, password });
34
- }
35
- catch (e) {
36
- setError(e instanceof Error ? e.message : "Add failed");
37
- }
38
- finally {
39
- setSubmitting(false);
40
- }
41
- }
42
- return (_jsxs("form", { onSubmit: handleSubmit, autoComplete: "off", className: "space-y-3 rounded border border-base-200 px-3 py-3", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx(ColorPicker, { value: color, onChange: setColor, className: "mt-[1.625rem]" }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Input, { label: "Name", value: name, onChange: setName }), _jsx(PasswordInput, { label: "Password", value: password, onChange: setPassword, autoComplete: "new-password", showToggle: true })] })] }), error && _jsx("p", { className: "text-xs text-red-600", children: error }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: onCancel, disabled: submitting, children: "Cancel" }), _jsx(Button, { type: "submit", variant: "primary", isLoading: submitting, loadingLabel: "Adding...", children: "Add" })] })] }));
43
- }
@@ -1,74 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { Input } from "../shared/Input";
4
- import { PasswordInput } from "../shared/PasswordInput";
5
- import { Button } from "../shared/Button";
6
- import { ColorPicker } from "../shared/ColorPicker";
7
- const PLACEHOLDER_COLOR = "#94a3b8";
8
- export function AudienceRow({ audience, isDefault, canMutate, onSave, onDelete, }) {
9
- const [isEditing, setIsEditing] = useState(false);
10
- const [draftDisplayName, setDraftDisplayName] = useState(audience.displayName);
11
- const [draftColor, setDraftColor] = useState(audience.color ?? PLACEHOLDER_COLOR);
12
- const [draftPassword, setDraftPassword] = useState("");
13
- const [error, setError] = useState(null);
14
- const [saving, setSaving] = useState(false);
15
- function startEdit() {
16
- setIsEditing(true);
17
- setDraftColor(audience.color ?? PLACEHOLDER_COLOR);
18
- setDraftDisplayName(audience.displayName);
19
- setDraftPassword("");
20
- setError(null);
21
- }
22
- function cancelEdit() {
23
- setIsEditing(false);
24
- setError(null);
25
- }
26
- async function save() {
27
- setError(null);
28
- setSaving(true);
29
- const patch = {};
30
- if (draftDisplayName.trim().length === 0) {
31
- setError("Name is required");
32
- setSaving(false);
33
- return;
34
- }
35
- if (draftDisplayName !== audience.displayName) {
36
- patch.displayName = draftDisplayName;
37
- }
38
- if (draftColor !== (audience.color ?? PLACEHOLDER_COLOR)) {
39
- patch.color = draftColor;
40
- }
41
- if (draftPassword.length > 0) {
42
- patch.password = draftPassword;
43
- }
44
- if (Object.keys(patch).length === 0) {
45
- setIsEditing(false);
46
- setSaving(false);
47
- return;
48
- }
49
- try {
50
- await onSave(patch);
51
- setIsEditing(false);
52
- }
53
- catch (e) {
54
- setError(e instanceof Error ? e.message : "Save failed");
55
- }
56
- finally {
57
- setSaving(false);
58
- }
59
- }
60
- async function remove() {
61
- if (!confirm(`Remove audience "${audience.displayName}"?`))
62
- return;
63
- try {
64
- await onDelete();
65
- }
66
- catch (e) {
67
- setError(e instanceof Error ? e.message : "Delete failed");
68
- }
69
- }
70
- if (isEditing) {
71
- return (_jsxs("div", { className: "space-y-3 rounded border border-base-200 px-3 py-3", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx(ColorPicker, { value: draftColor, onChange: setDraftColor, className: "mt-[1.625rem]" }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Input, { label: "Display name", value: draftDisplayName, onChange: setDraftDisplayName }), _jsx(PasswordInput, { label: "New password", value: draftPassword, onChange: setDraftPassword, autoComplete: "new-password", placeholder: audience.hasPassword ? "Leave blank to keep current" : "Required", showToggle: true }), _jsx("span", { className: "block text-[11px] text-base-contrast-light", children: "Passwords are hashed \u2014 the existing one can't be recovered, only replaced." })] })] }), error && _jsx("p", { className: "text-xs text-red-600", children: error }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: cancelEdit, disabled: saving, children: "Cancel" }), _jsx(Button, { type: "button", variant: "primary", onClick: save, isLoading: saving, loadingLabel: "Saving...", children: "Save" })] })] }));
72
- }
73
- return (_jsx("div", { className: "rounded border border-base-200 px-3 py-2", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { "aria-hidden": true, className: "inline-block h-4 w-4 rounded-full border border-base-200", style: { backgroundColor: audience.color ?? PLACEHOLDER_COLOR } }), _jsxs("span", { className: "flex flex-1 items-center gap-2 text-sm text-base-contrast", children: [audience.displayName, isDefault && (_jsx("span", { className: "rounded bg-base-accent px-1.5 py-0.5 text-xs text-base-contrast-light", children: "Default" }))] }), audience.hasPassword ? (_jsx("span", { "aria-label": "Password is set", title: "Passwords are hashed and can't be shown. Edit to set a new one.", className: "text-xs tracking-widest text-base-contrast-light", children: "\u2022\u2022\u2022\u2022" })) : canMutate ? (_jsx(Button, { variant: "ghost", onClick: startEdit, children: "Set password" })) : null, canMutate && (_jsx(Button, { variant: "ghost", onClick: startEdit, children: "Edit" })), canMutate && !isDefault && (_jsx(Button, { variant: "ghost", tone: "destructive", onClick: remove, children: "Remove" }))] }) }));
74
- }
@@ -1,24 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useState, useCallback } from "react";
3
- import { editModeEvent } from "../../lib/events";
4
- const EditorContext = createContext(null);
5
- export function EditorProvider({ children }) {
6
- const [isEditMode, setIsEditMode] = useState(true);
7
- const [showAllChrome, setShowAllChrome] = useState(false);
8
- const toggleEditMode = useCallback(() => {
9
- setIsEditMode((prev) => {
10
- const next = !prev;
11
- editModeEvent.dispatch({ isEditMode: next });
12
- return next;
13
- });
14
- }, []);
15
- const toggleShowAllChrome = useCallback(() => setShowAllChrome((prev) => !prev), []);
16
- return (_jsx(EditorContext.Provider, { value: { isEditMode, showAllChrome, toggleEditMode, toggleShowAllChrome }, children: children }));
17
- }
18
- export function useEditorContext() {
19
- const context = useContext(EditorContext);
20
- if (!context) {
21
- throw new Error("useEditorContext must be used within an EditorProvider");
22
- }
23
- return context;
24
- }
@@ -1,46 +0,0 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { PasswordInput } from "../shared/PasswordInput";
4
- import { Button } from "../shared/Button";
5
- import { safeRedirect } from "../../lib/safeRedirect";
6
- export function EditorLoginForm({ capabilities, oauthProviders = [], next }) {
7
- const [email, setEmail] = useState("");
8
- const [password, setPassword] = useState("");
9
- const [error, setError] = useState(null);
10
- const [loading, setLoading] = useState(false);
11
- async function handlePasswordSubmit(e) {
12
- e.preventDefault();
13
- setLoading(true);
14
- setError(null);
15
- const base = capabilities.passwordOnly
16
- ? { type: "password", password }
17
- : { type: "email", email, password };
18
- const body = next ? { ...base, next } : base;
19
- try {
20
- const response = await fetch("/api/auth/sign-in", {
21
- method: "POST",
22
- headers: { "Content-Type": "application/json" },
23
- body: JSON.stringify(body),
24
- });
25
- if (!response.ok) {
26
- const data = await response.json().catch(() => ({}));
27
- setError(data.error || "Sign-in failed");
28
- return;
29
- }
30
- const data = await response.json().catch(() => ({}));
31
- window.location.assign(safeRedirect(data.redirectTo, "/edit"));
32
- }
33
- catch {
34
- setError("Network error");
35
- }
36
- finally {
37
- setLoading(false);
38
- }
39
- }
40
- async function handleOAuth(provider) {
41
- // OAuth is handled client-side via Supabase SDK
42
- // Implementation depends on Supabase adapter (Task 11)
43
- // For now, this is a placeholder that will be wired up later
44
- }
45
- return (_jsxs("div", { className: "mx-auto max-w-sm space-y-6", children: [capabilities.oauth && oauthProviders.length > 0 && (_jsxs("div", { className: "space-y-2", children: [oauthProviders.map((provider) => (_jsxs(Button, { type: "button", variant: "secondary", size: "md", onClick: () => handleOAuth(provider), className: "w-full", children: ["Continue with ", provider.charAt(0).toUpperCase() + provider.slice(1)] }, provider))), _jsxs("div", { className: "relative py-2", children: [_jsx("div", { className: "absolute inset-0 flex items-center", children: _jsx("div", { className: "w-full border-t border-base-200" }) }), _jsx("div", { className: "relative flex justify-center text-xs", children: _jsx("span", { className: "bg-base px-2 text-base-contrast-light", children: "or" }) })] })] })), _jsxs("form", { onSubmit: handlePasswordSubmit, className: "space-y-4", children: [capabilities.emailPassword && (_jsxs("div", { children: [_jsx("label", { htmlFor: "email", className: "mb-1.5 block text-sm font-medium text-base-contrast", children: "Email" }), _jsx("input", { id: "email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true, autoComplete: "email", className: "w-full rounded border border-base-200 bg-base px-3 py-2 text-sm text-base-contrast focus:border-base-contrast focus:outline-none focus:ring-1 focus:ring-base-contrast" })] })), _jsx(PasswordInput, { label: "Password", value: password, onChange: setPassword, autoComplete: "current-password" }), error && (_jsx("p", { className: "text-sm text-red-600", children: error })), _jsx(Button, { type: "submit", variant: "primary", size: "md", isLoading: loading, loadingLabel: "Signing in...", className: "w-full", children: "Sign In" })] })] }));
46
- }
@@ -1,43 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useId, useRef } from "react";
3
- import { createPortal } from "react-dom";
4
- import { RemoveIcon } from "../shared/icons";
5
- import { IconButton } from "../shared/IconButton";
6
- import { useFocusTrap } from "../../hooks/useFocusTrap";
7
- import { cn } from "../../lib/cn";
8
- export function EditorModal({ isOpen, onClose, title, blocking = false, size = "standard", noPadding = false, children }) {
9
- const panelRef = useRef(null);
10
- const titleId = useId();
11
- useFocusTrap(panelRef, isOpen);
12
- useEffect(() => {
13
- if (!isOpen || blocking)
14
- return;
15
- const handleKeyDown = (e) => {
16
- if (e.key === "Escape")
17
- onClose();
18
- };
19
- document.addEventListener("keydown", handleKeyDown);
20
- return () => document.removeEventListener("keydown", handleKeyDown);
21
- }, [isOpen, blocking, onClose]);
22
- useEffect(() => {
23
- if (!isOpen)
24
- return;
25
- const original = document.body.style.overflow;
26
- document.body.style.overflow = "hidden";
27
- return () => {
28
- document.body.style.overflow = original;
29
- };
30
- }, [isOpen]);
31
- if (!isOpen)
32
- return null;
33
- return createPortal(_jsx("div", { "data-testid": "modal-backdrop", className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", onMouseDown: blocking
34
- ? undefined
35
- : (e) => {
36
- if (e.target === e.currentTarget)
37
- onClose();
38
- }, children: _jsxs("div", { ref: panelRef, role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, className: cn("mx-4 w-full rounded-lg bg-base shadow-lg", size === "large"
39
- ? "max-w-[1400px] max-h-[900px] h-[90vh] flex flex-col"
40
- : size === "settings"
41
- ? "max-w-lg flex flex-col min-h-[32rem] max-h-[calc(100vh-4rem)]"
42
- : "max-w-md"), children: [_jsxs("div", { className: "flex items-center justify-between border-b border-base-200 px-6 py-4", children: [_jsx("h3", { id: titleId, className: "text-lg font-semibold text-base-contrast", children: title }), !blocking && (_jsx(IconButton, { icon: _jsx(RemoveIcon, { size: 18 }), label: "Close modal", size: "lg", onClick: onClose, className: "rounded-md hover:bg-base-accent" }))] }), _jsx("div", { className: cn(!noPadding && "px-6 py-4", size === "large" && "flex-1 overflow-y-auto", size === "settings" && "flex-1 flex flex-col overflow-hidden"), children: children })] }) }), document.body);
43
- }
@@ -1,20 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useState, useCallback } from "react";
3
- const EditorModalContext = createContext(null);
4
- export function EditorModalProvider({ children }) {
5
- const [modalState, setModalState] = useState(null);
6
- const openModal = useCallback((title, content) => {
7
- setModalState({ title, content });
8
- }, []);
9
- const closeModal = useCallback(() => {
10
- setModalState(null);
11
- }, []);
12
- return (_jsx(EditorModalContext.Provider, { value: { modalState, openModal, closeModal }, children: children }));
13
- }
14
- export function useEditorModal() {
15
- const context = useContext(EditorModalContext);
16
- if (!context) {
17
- throw new Error("useEditorModal must be used within an EditorModalProvider");
18
- }
19
- return context;
20
- }