@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.
- package/dist/auth/index.js +26 -3
- package/dist/chunk-2VTPWODA.js +60 -0
- package/dist/chunk-CS7F6IOY.js +39 -0
- package/dist/chunk-HOJAF4VD.js +264 -0
- package/dist/chunk-IP6ODLXX.js +341 -0
- package/dist/chunk-T4BJ6RSB.js +58 -0
- package/dist/chunk-UKEVUCIZ.js +200 -0
- package/dist/chunk-UMSFICAC.js +36 -0
- package/dist/index.js +156 -4
- package/dist/lib/index.js +62 -12
- package/dist/lib/sanitize.d.ts.map +1 -1
- package/dist/media/index.js +36 -9
- package/dist/schemas/index.js +52 -7
- package/package.json +5 -4
- package/src/lib/sanitize.ts +6 -2
- package/dist/auth/cookies.js +0 -44
- package/dist/auth/errors.js +0 -10
- package/dist/auth/security.js +0 -48
- package/dist/auth/types.js +0 -1
- package/dist/components/brandguide/ColorSwatchSettings.js +0 -10
- package/dist/components/brandguide/Colors.js +0 -79
- package/dist/components/brandguide/DoDontList.js +0 -22
- package/dist/components/brandguide/DoDontMediaGrid.js +0 -5
- package/dist/components/editor/AudiencePicker.js +0 -24
- package/dist/components/editor/DeleteButton.js +0 -6
- package/dist/components/editor/DragHandle.js +0 -8
- package/dist/components/editor/InsertButton.js +0 -7
- package/dist/components/editor/SectionWrapper.js +0 -135
- package/dist/components/editor/SettingsButton.js +0 -6
- package/dist/components/editor/SettingsForm.js +0 -64
- package/dist/components/editor/StatusBadge.js +0 -10
- package/dist/components/editor/StatusPicker.js +0 -30
- package/dist/components/editor/index.js +0 -7
- package/dist/components/primitives/CustomParagraph.js +0 -24
- package/dist/components/primitives/EditableGrid.js +0 -90
- package/dist/components/primitives/EditableList.js +0 -54
- package/dist/components/primitives/EditablePlainText.js +0 -52
- package/dist/components/primitives/EditableRichText.js +0 -86
- package/dist/components/primitives/HeadingSection.js +0 -7
- package/dist/components/primitives/IconPicker.js +0 -21
- package/dist/components/primitives/LinkPopover.js +0 -48
- package/dist/components/primitives/MediaSettingsForms.js +0 -42
- package/dist/components/primitives/ResolvedMedia.js +0 -9
- package/dist/components/primitives/RichTextToolbar.js +0 -26
- package/dist/components/primitives/tiptap-presets.js +0 -44
- package/dist/components/primitives/useEditableCollection.js +0 -61
- package/dist/components/primitives/useEditablePlainText.js +0 -27
- package/dist/components/primitives/useEditableRichText.js +0 -52
- package/dist/components/sections/Button/CTAButton.js +0 -18
- package/dist/components/sections/Button/index.js +0 -28
- package/dist/components/sections/Colors/index.js +0 -34
- package/dist/components/sections/DoDontList/index.js +0 -33
- package/dist/components/sections/DoDontMediaGrid/index.js +0 -41
- package/dist/components/sections/IconList/IconList.js +0 -131
- package/dist/components/sections/IconList/IconListSettings.js +0 -22
- package/dist/components/sections/IconList/index.js +0 -27
- package/dist/components/sections/LinkHeading/index.js +0 -15
- package/dist/components/sections/MediaGrid/MediaGrid.js +0 -62
- package/dist/components/sections/MediaGrid/index.js +0 -35
- package/dist/components/sections/Prose/Prose.js +0 -11
- package/dist/components/sections/Prose/index.js +0 -15
- package/dist/components/sections/SectionLayout.js +0 -15
- package/dist/components/sections/SplitContent/SplitContent.js +0 -31
- package/dist/components/sections/SplitContent/SplitContentSettings.js +0 -17
- package/dist/components/sections/SplitContent/index.js +0 -27
- package/dist/components/sections/SubHeading/index.js +0 -18
- package/dist/components/sections/SubSubHeading/index.js +0 -18
- package/dist/components/sections/ViewRenderer.js +0 -13
- package/dist/components/sections/register-schemas.js +0 -15
- package/dist/components/sections/register.js +0 -15
- package/dist/components/shared/Button.js +0 -27
- package/dist/components/shared/Checkbox.js +0 -10
- package/dist/components/shared/ColorPicker.js +0 -5
- package/dist/components/shared/ErrorBoundary.js +0 -30
- package/dist/components/shared/FontPicker.js +0 -190
- package/dist/components/shared/FormLabel.js +0 -5
- package/dist/components/shared/IconButton.js +0 -16
- package/dist/components/shared/Input.js +0 -8
- package/dist/components/shared/Navigation.js +0 -71
- package/dist/components/shared/PasswordInput.js +0 -11
- package/dist/components/shared/Popover.js +0 -33
- package/dist/components/shared/PopoverItem.js +0 -6
- package/dist/components/shared/Select.js +0 -9
- package/dist/components/shared/Textarea.js +0 -8
- package/dist/components/shared/Toggle.js +0 -5
- package/dist/components/shared/Tooltip.js +0 -8
- package/dist/components/shared/icons.js +0 -23
- package/dist/components/shell/AudienceAddForm.js +0 -43
- package/dist/components/shell/AudienceRow.js +0 -74
- package/dist/components/shell/EditorContext.js +0 -24
- package/dist/components/shell/EditorLoginForm.js +0 -46
- package/dist/components/shell/EditorModal.js +0 -43
- package/dist/components/shell/EditorModalContext.js +0 -20
- package/dist/components/shell/EditorShell.js +0 -483
- package/dist/components/shell/MediaLibraryContext.js +0 -5
- package/dist/components/shell/MediaLibraryModal.js +0 -145
- package/dist/components/shell/ProcessingIndicator.js +0 -15
- package/dist/components/shell/SectionSkeleton.js +0 -22
- package/dist/components/shell/SectionTypePicker.js +0 -15
- package/dist/components/shell/SiteSettingsDisplay.js +0 -28
- package/dist/components/shell/SiteSettingsModal.js +0 -40
- package/dist/components/shell/SiteSettingsUsers.js +0 -87
- package/dist/components/shell/SiteSettingsViewerAccess.js +0 -94
- package/dist/components/shell/ViewerLoginForm.js +0 -40
- package/dist/data/google-fonts.json +0 -7718
- package/dist/hooks/index.js +0 -6
- package/dist/hooks/useActiveHeadings.js +0 -99
- package/dist/hooks/useEditorPersistence.js +0 -73
- package/dist/hooks/useEditorPublish.js +0 -145
- package/dist/hooks/useFocusTrap.js +0 -51
- package/dist/hooks/useMediaPipeline.js +0 -253
- package/dist/hooks/useResolvedMedia.js +0 -39
- package/dist/lib/cn.js +0 -5
- package/dist/lib/contrast.js +0 -11
- package/dist/lib/dexie.js +0 -236
- package/dist/lib/events.js +0 -15
- package/dist/lib/google-fonts.js +0 -11
- package/dist/lib/grid.js +0 -7
- package/dist/lib/icons.js +0 -27
- package/dist/lib/loader.js +0 -57
- package/dist/lib/nav.js +0 -58
- package/dist/lib/registry.js +0 -64
- package/dist/lib/safeRedirect.js +0 -11
- package/dist/lib/sanitize.js +0 -6
- package/dist/lib/timestamp.js +0 -28
- package/dist/media/github.js +0 -60
- package/dist/media/queue.js +0 -116
- package/dist/media/resolve.js +0 -50
- package/dist/media/types.js +0 -1
- package/dist/media/utils.js +0 -41
- package/dist/media/videoPoster.js +0 -44
- package/dist/media/worker.js +0 -73
- package/dist/schemas/audience.js +0 -19
- package/dist/schemas/auth.js +0 -22
- package/dist/schemas/media-grid-options.js +0 -7
- package/dist/schemas/media.js +0 -28
- package/dist/schemas/sections.js +0 -12
- package/dist/schemas/shared.js +0 -71
- package/dist/schemas/site-config.js +0 -26
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { defineSection } from "../../../lib/registry";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import IconList from "./IconList";
|
|
5
|
-
import { IconListSettings } from "./IconListSettings";
|
|
6
|
-
const schema = z.object({
|
|
7
|
-
type: z.literal("icon_list"),
|
|
8
|
-
content: z.object({
|
|
9
|
-
items: z.array(z.object({ label: z.string(), text: z.string(), icon: z.string().optional() })),
|
|
10
|
-
}),
|
|
11
|
-
options: z.object({
|
|
12
|
-
icon: z.string().nullable().optional(),
|
|
13
|
-
showLabel: z.boolean().optional(),
|
|
14
|
-
stackText: z.boolean().optional(),
|
|
15
|
-
}).optional(),
|
|
16
|
-
});
|
|
17
|
-
export default defineSection({
|
|
18
|
-
type: "icon_list",
|
|
19
|
-
label: "Icon List",
|
|
20
|
-
schema,
|
|
21
|
-
component: ({ content, options, onChange }) => (_jsx(IconList, { items: content.content.items, icon: options?.icon, showLabel: options?.showLabel, stackText: options?.stackText, onChange: onChange ? (c) => onChange(c) : undefined })),
|
|
22
|
-
defaults: () => ({
|
|
23
|
-
type: "icon_list",
|
|
24
|
-
content: { items: [{ label: "", text: "" }] },
|
|
25
|
-
}),
|
|
26
|
-
settingsForm: IconListSettings,
|
|
27
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { defineSection } from "../../../lib/registry";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { HeadingSection } from "../../primitives/HeadingSection";
|
|
5
|
-
const schema = z.object({
|
|
6
|
-
type: z.literal("link_heading"),
|
|
7
|
-
content: z.object({ heading: z.string() }),
|
|
8
|
-
});
|
|
9
|
-
export default defineSection({
|
|
10
|
-
type: "link_heading",
|
|
11
|
-
label: "Link Heading",
|
|
12
|
-
schema,
|
|
13
|
-
component: ({ content, onChange }) => (_jsx(HeadingSection, { heading: content.content.heading, tag: "h2", placeholder: "Section heading", className: "text-primary", onChange: onChange ? (heading) => onChange({ type: "link_heading", content: { heading } }) : undefined })),
|
|
14
|
-
defaults: () => ({ type: "link_heading", content: { heading: "New Section" } }),
|
|
15
|
-
});
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { cn } from "../../../lib/cn";
|
|
3
|
-
import { gridColsClass } from "../../../lib/grid";
|
|
4
|
-
import { EditableGrid } from "../../primitives/EditableGrid";
|
|
5
|
-
import { ResolvedMedia } from "../../primitives/ResolvedMedia";
|
|
6
|
-
import { ImageSettingsForm, DoDontImageSettingsForm } from "../../primitives/MediaSettingsForms";
|
|
7
|
-
import { EditablePlainText } from "../../primitives/EditablePlainText";
|
|
8
|
-
import { Check, X } from "lucide-react";
|
|
9
|
-
import { useMediaLibrary } from "../../shell/MediaLibraryContext";
|
|
10
|
-
export default function MediaGrid({ media, columns, square, border, crop, showCaptions, sectionType, onChange, openModal }) {
|
|
11
|
-
if (onChange && openModal) {
|
|
12
|
-
return (_jsx(MediaGridEditable, { media: media, columns: columns, square: square, border: border, crop: crop, showCaptions: showCaptions, sectionType: sectionType, onChange: onChange, openModal: openModal }));
|
|
13
|
-
}
|
|
14
|
-
return (_jsx("div", { className: cn("grid gap-4", gridColsClass[columns] || "grid-cols-1"), children: media.map((item, i) => (_jsx(MediaGridItem, { item: item, isEditMode: false, square: square, border: border, crop: crop, showCaptions: showCaptions }, i))) }));
|
|
15
|
-
}
|
|
16
|
-
function MediaGridEditable({ media, columns, square, border, crop, showCaptions, sectionType = "media_grid", onChange, openModal }) {
|
|
17
|
-
const mediaLibrary = useMediaLibrary();
|
|
18
|
-
const opts = square || border || crop || showCaptions
|
|
19
|
-
? { options: { square, border, crop, showCaptions } }
|
|
20
|
-
: {};
|
|
21
|
-
const createItem = () => sectionType === "do_dont_grid"
|
|
22
|
-
? { type: "doDontImage", imageId: "", doDont: "do" }
|
|
23
|
-
: { type: "image", imageId: "" };
|
|
24
|
-
const handleItemSettings = (index) => {
|
|
25
|
-
const item = media[index];
|
|
26
|
-
if (item.type === "doDontImage") {
|
|
27
|
-
openModal("Image Settings", _jsx(DoDontImageSettingsForm, { border: item.border, objectFit: item.objectFit, doDont: item.doDont, onChange: (updated) => {
|
|
28
|
-
const newMedia = media.map((m, i) => i === index ? { ...m, ...updated } : m);
|
|
29
|
-
onChange({ type: sectionType, content: { columns, media: newMedia }, ...opts });
|
|
30
|
-
} }));
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
openModal("Image Settings", _jsx(ImageSettingsForm, { border: item.border, objectFit: item.objectFit, onChange: (updated) => {
|
|
34
|
-
const newMedia = media.map((m, i) => i === index ? { ...m, ...updated } : m);
|
|
35
|
-
onChange({ type: sectionType, content: { columns, media: newMedia }, ...opts });
|
|
36
|
-
} }));
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
const handleItemImageClick = (index) => {
|
|
40
|
-
mediaLibrary?.openSelectModal((imageId) => {
|
|
41
|
-
const newMedia = media.map((m, i) => i === index ? { ...m, imageId } : m);
|
|
42
|
-
onChange({ type: sectionType, content: { columns, media: newMedia }, ...opts });
|
|
43
|
-
});
|
|
44
|
-
};
|
|
45
|
-
return (_jsx(EditableGrid, { items: media, columns: columns, onChange: (newMedia) => onChange({ type: sectionType, content: { columns, media: newMedia }, ...opts }), createItem: createItem, isEditMode: true, onItemSettings: handleItemSettings, onItemImageClick: mediaLibrary ? handleItemImageClick : undefined, chromeTopClass: sectionType === "do_dont_grid" ? "top-[50px]" : undefined, renderItem: (item, { isEditMode, index }) => (_jsx(MediaGridItem, { item: item, isEditMode: isEditMode, square: square, border: border, crop: crop, showCaptions: showCaptions, onCaptionChange: (caption) => {
|
|
46
|
-
const newMedia = media.map((m, i) => i === index ? { ...m, caption: caption || undefined } : m);
|
|
47
|
-
onChange({ type: sectionType, content: { columns, media: newMedia }, ...opts });
|
|
48
|
-
} })) }));
|
|
49
|
-
}
|
|
50
|
-
function MediaGridItem({ item, isEditMode, square, border, crop, showCaptions, onCaptionChange, }) {
|
|
51
|
-
const isDoDont = item.type === "doDontImage";
|
|
52
|
-
const showBorder = item.border ?? border;
|
|
53
|
-
const itemFit = item.objectFit ?? (crop ? "cover" : undefined);
|
|
54
|
-
const fitClass = itemFit === "cover" ? "object-cover" : "object-contain";
|
|
55
|
-
const captionStr = item.caption
|
|
56
|
-
? (Array.isArray(item.caption) ? item.caption.join("\n") : item.caption)
|
|
57
|
-
: "";
|
|
58
|
-
const media = (_jsx(ResolvedMedia, { imageId: item.imageId || undefined, className: "h-full w-full", imgClassName: fitClass }));
|
|
59
|
-
return (_jsxs("figure", { children: [isDoDont && (_jsx("div", { className: "flex h-10 items-center justify-center pb-1", children: item.doDont === "do"
|
|
60
|
-
? _jsx(Check, { size: 28, className: "text-green-600" })
|
|
61
|
-
: _jsx(X, { size: 28, className: "text-red-600" }) })), _jsx("div", { className: cn("overflow-hidden rounded-md", showBorder && "border border-base-200", square && "aspect-square"), children: item.type === "linkedImage" && !isEditMode ? (_jsxs("a", { href: item.href, target: item.target, className: "group block", children: [media, item.linkText && (_jsx("span", { className: "mt-2 block text-sm text-primary group-hover:underline", children: item.linkText }))] })) : media }), showCaptions && (_jsx("figcaption", { className: "mt-2 min-h-[1em] text-sm text-base-contrast-light", children: isEditMode && onCaptionChange ? (_jsx(EditablePlainText, { tag: "span", value: captionStr, onChange: onCaptionChange, isEditMode: true, placeholder: "Caption", className: "block min-h-[1em]" })) : (captionStr || " ") }))] }));
|
|
62
|
-
}
|
|
@@ -1,35 +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 MediaGrid from "./MediaGrid";
|
|
7
|
-
const schema = z.object({
|
|
8
|
-
type: z.literal("media_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: "media_grid",
|
|
17
|
-
label: "Media Grid",
|
|
18
|
-
schema,
|
|
19
|
-
component: ({ content, options, onChange, openModal }) => (_jsx(MediaGrid, { 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: "media_grid",
|
|
22
|
-
content: { columns: 2, media: [{ type: "image", imageId: "" }] },
|
|
23
|
-
options: {},
|
|
24
|
-
}),
|
|
25
|
-
settings: {
|
|
26
|
-
columns: {
|
|
27
|
-
type: "select", label: "Columns", default: "2", target: "content", coerce: "number",
|
|
28
|
-
options: [{ label: "1", value: "1" }, { label: "2", value: "2" }, { label: "3", value: "3" }, { label: "4", value: "4" }],
|
|
29
|
-
},
|
|
30
|
-
border: { type: "checkbox", label: "Show border", default: false },
|
|
31
|
-
square: { type: "checkbox", label: "Square aspect ratio", default: false },
|
|
32
|
-
crop: { type: "checkbox", label: "Crop to fill", default: false },
|
|
33
|
-
showCaptions: { type: "checkbox", label: "Show captions", default: false },
|
|
34
|
-
},
|
|
35
|
-
});
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { EditableRichText } from "../../primitives/EditableRichText";
|
|
3
|
-
import { sanitizeHtml } from "../../../lib/sanitize";
|
|
4
|
-
export default function Prose({ body, onChange }) {
|
|
5
|
-
if (onChange) {
|
|
6
|
-
return (_jsx("div", { className: "prose-content", children: _jsx(EditableRichText, { value: body, onChange: (html) => onChange({ type: "prose", content: { body: html } }), isEditMode: true, preset: "rich" }) }));
|
|
7
|
-
}
|
|
8
|
-
// View mode: renders server-side via Astro SSR; content comes from trusted project JSON files.
|
|
9
|
-
// eslint-disable-next-line react/no-danger
|
|
10
|
-
return (_jsx("div", { className: "prose-content", dangerouslySetInnerHTML: { __html: sanitizeHtml(body) } }));
|
|
11
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { defineSection } from "../../../lib/registry";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import Prose from "./Prose";
|
|
5
|
-
const schema = z.object({
|
|
6
|
-
type: z.literal("prose"),
|
|
7
|
-
content: z.object({ body: z.string() }),
|
|
8
|
-
});
|
|
9
|
-
export default defineSection({
|
|
10
|
-
type: "prose",
|
|
11
|
-
label: "Prose",
|
|
12
|
-
schema,
|
|
13
|
-
component: ({ content, onChange }) => (_jsx(Prose, { body: content.content.body, onChange: onChange ? (c) => onChange(c) : undefined })),
|
|
14
|
-
defaults: () => ({ type: "prose", content: { body: "<p></p>" } }),
|
|
15
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { cn } from "../../lib/cn";
|
|
3
|
-
const categoryMap = {
|
|
4
|
-
link_heading: "section-heading",
|
|
5
|
-
sub_heading: "section-subheading",
|
|
6
|
-
sub_sub_heading: "section-subheading",
|
|
7
|
-
};
|
|
8
|
-
function getSectionCategory(type) {
|
|
9
|
-
return categoryMap[type] ?? "section-content";
|
|
10
|
-
}
|
|
11
|
-
export function SectionLayout({ type, status, dimNonPublished, children }) {
|
|
12
|
-
const isDimmed = dimNonPublished && status && status !== "published";
|
|
13
|
-
return (_jsx("div", { className: cn("section-wrapper group", getSectionCategory(type), isDimmed && "opacity-50"), children: children }));
|
|
14
|
-
}
|
|
15
|
-
export { getSectionCategory };
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { cn } from "../../../lib/cn";
|
|
3
|
-
import { sanitizeHtml } from "../../../lib/sanitize";
|
|
4
|
-
import { ResolvedMedia } from "../../primitives/ResolvedMedia";
|
|
5
|
-
import { EditableRichText } from "../../primitives/EditableRichText";
|
|
6
|
-
import { IconButton } from "../../shared/IconButton";
|
|
7
|
-
import { useMediaLibrary } from "../../shell/MediaLibraryContext";
|
|
8
|
-
import { ImageIcon } from "lucide-react";
|
|
9
|
-
export default function SplitContent({ imageId, body, border, imagePosition = "left", onChange }) {
|
|
10
|
-
const mediaLibrary = useMediaLibrary();
|
|
11
|
-
const handleImageChange = (newImageId) => {
|
|
12
|
-
onChange?.({
|
|
13
|
-
type: "split_content",
|
|
14
|
-
content: { imageId: newImageId, body },
|
|
15
|
-
options: { border, imagePosition },
|
|
16
|
-
});
|
|
17
|
-
};
|
|
18
|
-
const handleBodyChange = (html) => {
|
|
19
|
-
onChange?.({
|
|
20
|
-
type: "split_content",
|
|
21
|
-
content: { imageId, body: html },
|
|
22
|
-
options: { border, imagePosition },
|
|
23
|
-
});
|
|
24
|
-
};
|
|
25
|
-
const handleImageClick = () => {
|
|
26
|
-
mediaLibrary?.openSelectModal(handleImageChange);
|
|
27
|
-
};
|
|
28
|
-
return (_jsxs("div", { className: cn("flex flex-col gap-6 lg:flex-row lg:items-start", imagePosition === "right" && "lg:flex-row-reverse"), children: [_jsxs("div", { className: cn("group/img relative flex-shrink-0 lg:w-1/2", border && "overflow-hidden rounded-md border border-base-200"), children: [_jsx(ResolvedMedia, { imageId: imageId, className: "w-full" }), onChange && mediaLibrary && (_jsx("div", { className: "absolute top-1.5 right-1.5 z-10 opacity-0 transition-opacity group-hover/img:opacity-100", children: _jsx(IconButton, { icon: _jsx(ImageIcon, { size: 16 }), label: "Change image", onClick: handleImageClick, className: "bg-base/80 shadow-sm" }) }))] }), _jsx("div", { className: "prose-content lg:w-1/2", children: onChange ? (_jsx(EditableRichText, { value: body, onChange: handleBodyChange, isEditMode: true, preset: "rich" })) : (
|
|
29
|
-
// eslint-disable-next-line react/no-danger
|
|
30
|
-
_jsx("div", { dangerouslySetInnerHTML: { __html: sanitizeHtml(body) } })) })] }));
|
|
31
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
import { Select } from "../../shared/Select";
|
|
4
|
-
import { Checkbox } from "../../shared/Checkbox";
|
|
5
|
-
export function SplitContentSettings({ imagePosition: initialPos, border: initialBorder, onChange, }) {
|
|
6
|
-
const [imagePosition, setImagePosition] = useState(initialPos);
|
|
7
|
-
const [border, setBorder] = useState(initialBorder);
|
|
8
|
-
const emit = (overrides) => onChange({ imagePosition, border, ...overrides });
|
|
9
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsx(Select, { label: "Image Position", value: imagePosition, onChange: (val) => {
|
|
10
|
-
const v = val;
|
|
11
|
-
setImagePosition(v);
|
|
12
|
-
emit({ imagePosition: v });
|
|
13
|
-
}, options: [
|
|
14
|
-
{ value: "left", label: "Left" },
|
|
15
|
-
{ value: "right", label: "Right" },
|
|
16
|
-
] }), _jsx(Checkbox, { checked: border, onChange: (v) => { setBorder(v); emit({ border: v }); }, label: "Show border" })] }));
|
|
17
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { defineSection } from "../../../lib/registry";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import SplitContent from "./SplitContent";
|
|
5
|
-
import { SplitContentSettings } from "./SplitContentSettings";
|
|
6
|
-
const schema = z.object({
|
|
7
|
-
type: z.literal("split_content"),
|
|
8
|
-
content: z.object({
|
|
9
|
-
imageId: z.string().optional(),
|
|
10
|
-
body: z.string(),
|
|
11
|
-
}),
|
|
12
|
-
options: z.object({
|
|
13
|
-
border: z.boolean().optional(),
|
|
14
|
-
imagePosition: z.enum(["left", "right"]).optional(),
|
|
15
|
-
}).optional(),
|
|
16
|
-
});
|
|
17
|
-
export default defineSection({
|
|
18
|
-
type: "split_content",
|
|
19
|
-
label: "Split Content",
|
|
20
|
-
schema,
|
|
21
|
-
component: ({ content, options, onChange }) => (_jsx(SplitContent, { imageId: content.content.imageId, body: content.content.body, border: options?.border, imagePosition: options?.imagePosition, onChange: onChange ? (c) => onChange(c) : undefined })),
|
|
22
|
-
defaults: () => ({
|
|
23
|
-
type: "split_content",
|
|
24
|
-
content: { imageId: undefined, body: "<p></p>" },
|
|
25
|
-
}),
|
|
26
|
-
settingsForm: SplitContentSettings,
|
|
27
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { defineSection } from "../../../lib/registry";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { HeadingSection } from "../../primitives/HeadingSection";
|
|
5
|
-
const schema = z.object({
|
|
6
|
-
type: z.literal("sub_heading"),
|
|
7
|
-
content: z.object({ heading: z.string(), excludeFromNav: z.boolean().optional() }),
|
|
8
|
-
});
|
|
9
|
-
export default defineSection({
|
|
10
|
-
type: "sub_heading",
|
|
11
|
-
label: "Sub Heading",
|
|
12
|
-
schema,
|
|
13
|
-
component: ({ content, onChange }) => (_jsx(HeadingSection, { heading: content.content.heading, tag: "h3", placeholder: "Sub heading", className: "text-base-contrast", onChange: onChange ? (heading) => onChange({ ...content, content: { ...content.content, heading } }) : undefined })),
|
|
14
|
-
defaults: () => ({ type: "sub_heading", content: { heading: "New Sub Heading" } }),
|
|
15
|
-
settings: {
|
|
16
|
-
excludeFromNav: { type: "checkbox", label: "Exclude from navigation", default: false, target: "content" },
|
|
17
|
-
},
|
|
18
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { defineSection } from "../../../lib/registry";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { HeadingSection } from "../../primitives/HeadingSection";
|
|
5
|
-
const schema = z.object({
|
|
6
|
-
type: z.literal("sub_sub_heading"),
|
|
7
|
-
content: z.object({ heading: z.string(), excludeFromNav: z.boolean().optional() }),
|
|
8
|
-
});
|
|
9
|
-
export default defineSection({
|
|
10
|
-
type: "sub_sub_heading",
|
|
11
|
-
label: "Sub Sub Heading",
|
|
12
|
-
schema,
|
|
13
|
-
component: ({ content, onChange }) => (_jsx(HeadingSection, { heading: content.content.heading, tag: "h4", placeholder: "Sub sub heading", className: "text-lg font-bold text-base-contrast", onChange: onChange ? (heading) => onChange({ ...content, content: { ...content.content, heading } }) : undefined })),
|
|
14
|
-
defaults: () => ({ type: "sub_sub_heading", content: { heading: "New Sub Sub Heading" } }),
|
|
15
|
-
settings: {
|
|
16
|
-
excludeFromNav: { type: "checkbox", label: "Exclude from navigation", default: false, target: "content" },
|
|
17
|
-
},
|
|
18
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import "./register";
|
|
3
|
-
import { getSection } from "../../lib/registry";
|
|
4
|
-
import { SectionLayout } from "./SectionLayout";
|
|
5
|
-
export default function ViewRenderer({ sections }) {
|
|
6
|
-
return (_jsx(_Fragment, { children: sections.map(({ section, meta }) => {
|
|
7
|
-
const def = getSection(section.type);
|
|
8
|
-
if (!def)
|
|
9
|
-
return null;
|
|
10
|
-
const Component = def.component;
|
|
11
|
-
return (_jsx(SectionLayout, { type: section.type, status: meta.status, dimNonPublished: true, children: _jsx(Component, { content: section, options: "options" in section ? section.options : undefined, isEditMode: false }) }, section.id));
|
|
12
|
-
}) }));
|
|
13
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { registerSchema } from "../../lib/registry";
|
|
2
|
-
import linkHeading from "./LinkHeading";
|
|
3
|
-
import subHeading from "./SubHeading";
|
|
4
|
-
import subSubHeading from "./SubSubHeading";
|
|
5
|
-
import prose from "./Prose";
|
|
6
|
-
import mediaGrid from "./MediaGrid";
|
|
7
|
-
import splitContent from "./SplitContent";
|
|
8
|
-
import button from "./Button";
|
|
9
|
-
import colors from "./Colors";
|
|
10
|
-
import doDontList from "./DoDontList";
|
|
11
|
-
import doDontImageGrid from "./DoDontMediaGrid";
|
|
12
|
-
import iconList from "./IconList";
|
|
13
|
-
[linkHeading, subHeading, subSubHeading, prose, mediaGrid,
|
|
14
|
-
splitContent, button, colors, doDontList, doDontImageGrid, iconList,
|
|
15
|
-
].forEach((def) => registerSchema(def.type, def.schema));
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { registerSection } from "../../lib/registry";
|
|
2
|
-
import linkHeading from "./LinkHeading";
|
|
3
|
-
import subHeading from "./SubHeading";
|
|
4
|
-
import subSubHeading from "./SubSubHeading";
|
|
5
|
-
import prose from "./Prose";
|
|
6
|
-
import mediaGrid from "./MediaGrid";
|
|
7
|
-
import splitContent from "./SplitContent";
|
|
8
|
-
import button from "./Button";
|
|
9
|
-
import colors from "./Colors";
|
|
10
|
-
import doDontList from "./DoDontList";
|
|
11
|
-
import doDontImageGrid from "./DoDontMediaGrid";
|
|
12
|
-
import iconList from "./IconList";
|
|
13
|
-
[linkHeading, subHeading, subSubHeading, prose, mediaGrid,
|
|
14
|
-
splitContent, button, colors, doDontList, doDontImageGrid, iconList,
|
|
15
|
-
].forEach(registerSection);
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef } from "react";
|
|
3
|
-
import { cn } from "../../lib/cn";
|
|
4
|
-
const boxSize = {
|
|
5
|
-
sm: "rounded px-3 py-1.5 text-xs font-medium",
|
|
6
|
-
md: "rounded px-4 py-2 text-sm font-medium",
|
|
7
|
-
};
|
|
8
|
-
const ghostSize = {
|
|
9
|
-
sm: "text-xs font-medium",
|
|
10
|
-
md: "text-sm font-medium",
|
|
11
|
-
};
|
|
12
|
-
const variantClasses = {
|
|
13
|
-
primary: "bg-base-contrast text-base-accent hover:bg-base-contrast/90",
|
|
14
|
-
brand: "bg-primary text-primary-contrast hover:opacity-90",
|
|
15
|
-
secondary: "border border-base-200 text-base-contrast hover:bg-base-accent",
|
|
16
|
-
destructive: "border border-red-600 text-red-600 hover:bg-red-600 hover:text-white",
|
|
17
|
-
};
|
|
18
|
-
const ghostToneClasses = {
|
|
19
|
-
neutral: "text-base-contrast-light hover:text-base-contrast",
|
|
20
|
-
destructive: "text-red-600 hover:text-red-800",
|
|
21
|
-
};
|
|
22
|
-
export const Button = forwardRef(function Button({ variant = "primary", size = "sm", tone = "neutral", fullWidth = false, isLoading = false, loadingLabel, disabled, className, children, ...rest }, ref) {
|
|
23
|
-
const isGhost = variant === "ghost";
|
|
24
|
-
const resolvedDisabled = disabled || isLoading;
|
|
25
|
-
const label = isLoading && loadingLabel ? loadingLabel : children;
|
|
26
|
-
return (_jsx("button", { ref: ref, disabled: resolvedDisabled, className: cn("inline-flex items-center justify-center transition-colors", isGhost ? ghostSize[size] : boxSize[size], isGhost ? ghostToneClasses[tone] : variantClasses[variant], fullWidth && "w-full", resolvedDisabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", className), ...rest, children: label }));
|
|
27
|
-
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useId } from "react";
|
|
3
|
-
import { Check } from "lucide-react";
|
|
4
|
-
import { cn } from "../../lib/cn";
|
|
5
|
-
export function Checkbox({ checked, onChange, label, description, disabled, className, startAdornment, align = "start", }) {
|
|
6
|
-
const id = useId();
|
|
7
|
-
return (_jsxs("label", { htmlFor: id, className: cn("flex cursor-pointer gap-3 select-none hover:opacity-80", align === "start" ? "items-start" : "items-center", disabled && "cursor-not-allowed opacity-50", className), children: [_jsxs("span", { className: cn("relative flex h-5 w-5 shrink-0 items-center justify-center", align === "start" && "mt-0.5"), children: [_jsx("input", { id: id, type: "checkbox", checked: checked, disabled: disabled, onChange: (e) => onChange(e.target.checked), className: "sr-only" }), _jsx("span", { "aria-hidden": "true", className: cn("flex h-5 w-5 items-center justify-center rounded border transition-colors", checked
|
|
8
|
-
? "border-primary bg-primary text-primary-contrast"
|
|
9
|
-
: "border-base-300 bg-base hover:border-primary", disabled && "pointer-events-none"), children: checked && _jsx(Check, { size: 14, strokeWidth: 3 }) })] }), startAdornment, _jsxs("div", { className: "text-sm", children: [_jsx("span", { className: "font-medium text-base-contrast", children: label }), description && (_jsx("span", { className: "block text-base-contrast-light", children: description }))] })] }));
|
|
10
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { cn } from "../../lib/cn";
|
|
3
|
-
export function ColorPicker({ value, onChange, label = "Color", className }) {
|
|
4
|
-
return (_jsx("label", { className: cn("inline-flex h-9 w-9 shrink-0 cursor-pointer items-center justify-center rounded-full border border-base-200 focus-within:outline focus-within:outline-2 focus-within:outline-base-contrast", className), style: { backgroundColor: value }, children: _jsx("input", { type: "color", "aria-label": label, value: value, onChange: (e) => onChange(e.target.value), className: "sr-only" }) }));
|
|
5
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Component } from "react";
|
|
3
|
-
import { cn } from "../../lib/cn";
|
|
4
|
-
import { Button } from "./Button";
|
|
5
|
-
/**
|
|
6
|
-
* Catches render errors in children and shows a recoverable fallback UI.
|
|
7
|
-
* Designed to wrap individual section components so a single broken section
|
|
8
|
-
* doesn't take down the entire editor.
|
|
9
|
-
*/
|
|
10
|
-
export class ErrorBoundary extends Component {
|
|
11
|
-
constructor(props) {
|
|
12
|
-
super(props);
|
|
13
|
-
this.state = { hasError: false, error: null };
|
|
14
|
-
}
|
|
15
|
-
static getDerivedStateFromError(error) {
|
|
16
|
-
return { hasError: true, error };
|
|
17
|
-
}
|
|
18
|
-
componentDidCatch(error, info) {
|
|
19
|
-
console.error(`[ErrorBoundary${this.props.label ? `: ${this.props.label}` : ""}] Render error:`, error, info.componentStack);
|
|
20
|
-
}
|
|
21
|
-
handleRetry = () => {
|
|
22
|
-
this.setState({ hasError: false, error: null });
|
|
23
|
-
};
|
|
24
|
-
render() {
|
|
25
|
-
if (this.state.hasError) {
|
|
26
|
-
return (_jsxs("div", { role: "alert", className: cn("flex flex-col items-center justify-center gap-3 rounded border border-red-200 bg-red-50 px-6 py-10 text-center", "dark:border-red-900/40 dark:bg-red-950/20"), children: [_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", className: "h-6 w-6 text-red-500", "aria-hidden": "true", children: _jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z", clipRule: "evenodd" }) }), _jsx("p", { className: "text-sm font-medium text-red-700 dark:text-red-400", children: "This section couldn't be rendered" }), this.props.label && (_jsxs("p", { className: "text-xs text-red-500 dark:text-red-500/80", children: ["Section: ", this.props.label] })), this.state.error && (_jsx("p", { className: "max-w-md text-xs text-red-400 dark:text-red-500/60", children: this.state.error.message })), _jsx(Button, { variant: "secondary", size: "sm", onClick: this.handleRetry, className: "mt-1", children: "Try again" })] }));
|
|
27
|
-
}
|
|
28
|
-
return this.props.children;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useRef, useEffect, useId, useCallback, useMemo } from "react";
|
|
3
|
-
import { createPortal } from "react-dom";
|
|
4
|
-
import { ChevronDown } from "lucide-react";
|
|
5
|
-
import { cn } from "../../lib/cn";
|
|
6
|
-
import { FormLabel } from "./FormLabel";
|
|
7
|
-
import fontCatalog from "../../data/google-fonts.json";
|
|
8
|
-
const fonts = fontCatalog;
|
|
9
|
-
function displayValue(value) {
|
|
10
|
-
return value === "system-ui" ? "System Default" : value;
|
|
11
|
-
}
|
|
12
|
-
function loadFontPreview(families) {
|
|
13
|
-
if (families.length === 0)
|
|
14
|
-
return;
|
|
15
|
-
const params = families.map((f) => `family=${f.replace(/ /g, "+")}:wght@400;700`).join("&");
|
|
16
|
-
const href = `https://fonts.googleapis.com/css2?${params}&display=swap`;
|
|
17
|
-
if (document.querySelector(`link[href="${href}"]`))
|
|
18
|
-
return;
|
|
19
|
-
const link = document.createElement("link");
|
|
20
|
-
link.rel = "stylesheet";
|
|
21
|
-
link.href = href;
|
|
22
|
-
document.head.appendChild(link);
|
|
23
|
-
}
|
|
24
|
-
export function FontPicker({ label, value, onChange }) {
|
|
25
|
-
const id = useId();
|
|
26
|
-
const [open, setOpen] = useState(false);
|
|
27
|
-
const [query, setQuery] = useState("");
|
|
28
|
-
const [debouncedQuery, setDebouncedQuery] = useState("");
|
|
29
|
-
const [highlightIndex, setHighlightIndex] = useState(0);
|
|
30
|
-
const containerRef = useRef(null);
|
|
31
|
-
const inputWrapRef = useRef(null);
|
|
32
|
-
const listRef = useRef(null);
|
|
33
|
-
const loadedRef = useRef(new Set());
|
|
34
|
-
const batchRef = useRef([]);
|
|
35
|
-
const timerRef = useRef(undefined);
|
|
36
|
-
const debounceRef = useRef(undefined);
|
|
37
|
-
const [dropdownPos, setDropdownPos] = useState({
|
|
38
|
-
top: 0,
|
|
39
|
-
left: 0,
|
|
40
|
-
width: 0,
|
|
41
|
-
});
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
clearTimeout(debounceRef.current);
|
|
44
|
-
if (!query) {
|
|
45
|
-
setDebouncedQuery("");
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
debounceRef.current = setTimeout(() => setDebouncedQuery(query), 150);
|
|
49
|
-
return () => clearTimeout(debounceRef.current);
|
|
50
|
-
}, [query]);
|
|
51
|
-
const filtered = useMemo(() => debouncedQuery
|
|
52
|
-
? fonts.filter((f) => f.family.toLowerCase().includes(debouncedQuery.toLowerCase()))
|
|
53
|
-
: [], [debouncedQuery]);
|
|
54
|
-
const options = [
|
|
55
|
-
{ family: "system-ui", display: "System Default" },
|
|
56
|
-
...filtered.map((f) => ({ family: f.family, display: f.family })),
|
|
57
|
-
];
|
|
58
|
-
const scheduleLoad = useCallback((family) => {
|
|
59
|
-
if (family === "system-ui" || loadedRef.current.has(family))
|
|
60
|
-
return;
|
|
61
|
-
loadedRef.current.add(family);
|
|
62
|
-
batchRef.current.push(family);
|
|
63
|
-
clearTimeout(timerRef.current);
|
|
64
|
-
timerRef.current = setTimeout(() => {
|
|
65
|
-
const batch = batchRef.current.splice(0);
|
|
66
|
-
if (batch.length > 0)
|
|
67
|
-
loadFontPreview(batch);
|
|
68
|
-
}, 100);
|
|
69
|
-
}, []);
|
|
70
|
-
const observerRef = useRef(undefined);
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
return () => {
|
|
73
|
-
clearTimeout(timerRef.current);
|
|
74
|
-
clearTimeout(debounceRef.current);
|
|
75
|
-
};
|
|
76
|
-
}, []);
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
if (!open || typeof IntersectionObserver === "undefined")
|
|
79
|
-
return;
|
|
80
|
-
observerRef.current = new IntersectionObserver((entries) => {
|
|
81
|
-
for (const entry of entries) {
|
|
82
|
-
if (entry.isIntersecting) {
|
|
83
|
-
const family = entry.target.dataset.family;
|
|
84
|
-
if (family)
|
|
85
|
-
scheduleLoad(family);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}, { root: listRef.current, threshold: 0 });
|
|
89
|
-
const items = listRef.current?.querySelectorAll("[data-family]");
|
|
90
|
-
items?.forEach((el) => observerRef.current.observe(el));
|
|
91
|
-
return () => observerRef.current?.disconnect();
|
|
92
|
-
}, [open, debouncedQuery, scheduleLoad]);
|
|
93
|
-
const dropdownRef = useRef(null);
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
if (!open || !inputWrapRef.current)
|
|
96
|
-
return;
|
|
97
|
-
function updatePosition() {
|
|
98
|
-
const rect = inputWrapRef.current.getBoundingClientRect();
|
|
99
|
-
setDropdownPos({
|
|
100
|
-
top: rect.bottom + 4,
|
|
101
|
-
left: rect.left,
|
|
102
|
-
width: rect.width,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
updatePosition();
|
|
106
|
-
window.addEventListener("scroll", updatePosition, true);
|
|
107
|
-
window.addEventListener("resize", updatePosition);
|
|
108
|
-
return () => {
|
|
109
|
-
window.removeEventListener("scroll", updatePosition, true);
|
|
110
|
-
window.removeEventListener("resize", updatePosition);
|
|
111
|
-
};
|
|
112
|
-
}, [open]);
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
if (!open)
|
|
115
|
-
return;
|
|
116
|
-
function handleClickOutside(e) {
|
|
117
|
-
const target = e.target;
|
|
118
|
-
if (containerRef.current && !containerRef.current.contains(target) &&
|
|
119
|
-
dropdownRef.current && !dropdownRef.current.contains(target)) {
|
|
120
|
-
setOpen(false);
|
|
121
|
-
setQuery("");
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
125
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
126
|
-
}, [open]);
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
setHighlightIndex(0);
|
|
129
|
-
}, [debouncedQuery]);
|
|
130
|
-
function handleSelect(family) {
|
|
131
|
-
onChange(family);
|
|
132
|
-
setOpen(false);
|
|
133
|
-
setQuery("");
|
|
134
|
-
}
|
|
135
|
-
function handleKeyDown(e) {
|
|
136
|
-
if (!open) {
|
|
137
|
-
if (e.key === "ArrowDown" || e.key === "Enter") {
|
|
138
|
-
setOpen(true);
|
|
139
|
-
e.preventDefault();
|
|
140
|
-
}
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
switch (e.key) {
|
|
144
|
-
case "ArrowDown":
|
|
145
|
-
e.preventDefault();
|
|
146
|
-
setHighlightIndex((i) => Math.min(i + 1, options.length - 1));
|
|
147
|
-
break;
|
|
148
|
-
case "ArrowUp":
|
|
149
|
-
e.preventDefault();
|
|
150
|
-
setHighlightIndex((i) => Math.max(i - 1, 0));
|
|
151
|
-
break;
|
|
152
|
-
case "Enter":
|
|
153
|
-
e.preventDefault();
|
|
154
|
-
if (options[highlightIndex])
|
|
155
|
-
handleSelect(options[highlightIndex].family);
|
|
156
|
-
break;
|
|
157
|
-
case "Escape":
|
|
158
|
-
e.preventDefault();
|
|
159
|
-
setOpen(false);
|
|
160
|
-
setQuery("");
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
useEffect(() => {
|
|
165
|
-
if (!open || !listRef.current)
|
|
166
|
-
return;
|
|
167
|
-
const el = listRef.current.children[highlightIndex];
|
|
168
|
-
el?.scrollIntoView?.({ block: "nearest" });
|
|
169
|
-
}, [highlightIndex, open]);
|
|
170
|
-
return (_jsxs("div", { ref: containerRef, className: "relative", children: [_jsx(FormLabel, { htmlFor: id, children: label }), _jsxs("div", { ref: inputWrapRef, className: "relative", children: [_jsx("input", { id: id, type: "text", role: "combobox", autoComplete: "off", "aria-expanded": open, "aria-haspopup": "listbox", "aria-autocomplete": "list", "aria-controls": `${id}-listbox`, "aria-activedescendant": open ? `${id}-option-${highlightIndex}` : undefined, value: open ? query : displayValue(value), onChange: (e) => {
|
|
171
|
-
setQuery(e.target.value);
|
|
172
|
-
if (!open)
|
|
173
|
-
setOpen(true);
|
|
174
|
-
}, onFocus: () => {
|
|
175
|
-
setOpen(true);
|
|
176
|
-
setQuery("");
|
|
177
|
-
}, onKeyDown: handleKeyDown, placeholder: "Search fonts...", className: cn("w-full 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"), style: !open && value !== "system-ui" ? { fontFamily: `"${value}", system-ui, sans-serif` } : undefined }), _jsx(ChevronDown, { size: 14, className: "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-base-contrast-light" })] }), open &&
|
|
178
|
-
createPortal(_jsx("div", { ref: dropdownRef, className: "rounded border border-base-200 bg-base shadow-lg", style: {
|
|
179
|
-
position: "fixed",
|
|
180
|
-
top: dropdownPos.top,
|
|
181
|
-
left: dropdownPos.left,
|
|
182
|
-
width: dropdownPos.width,
|
|
183
|
-
zIndex: 9999,
|
|
184
|
-
}, children: _jsxs("ul", { ref: listRef, id: `${id}-listbox`, role: "listbox", className: "max-h-64 overflow-y-auto py-1", children: [options.map((opt, i) => (_jsx("li", { id: `${id}-option-${i}`, role: "option", "aria-selected": opt.family === value, "data-family": opt.family, onMouseDown: (e) => {
|
|
185
|
-
e.preventDefault();
|
|
186
|
-
handleSelect(opt.family);
|
|
187
|
-
}, onMouseEnter: () => setHighlightIndex(i), className: cn("cursor-pointer px-3 py-1.5 text-sm", i === highlightIndex && "bg-base-100", opt.family === value && "font-medium text-base-contrast"), style: opt.family !== "system-ui"
|
|
188
|
-
? { fontFamily: `"${opt.family}", system-ui, sans-serif` }
|
|
189
|
-
: undefined, children: opt.display }, opt.family))), debouncedQuery && filtered.length === 0 && (_jsx("li", { className: "px-3 py-1.5 text-sm text-base-contrast-light", children: "No fonts found" })), !debouncedQuery && (_jsx("li", { className: "px-3 py-1.5 text-sm text-base-contrast-light", children: "Type to search fonts..." }))] }) }), document.body)] }));
|
|
190
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { cn } from "../../lib/cn";
|
|
3
|
-
export function FormLabel({ htmlFor, children, className }) {
|
|
4
|
-
return (_jsx("label", { htmlFor: htmlFor, className: cn("mb-1.5 block text-sm font-medium text-base-contrast", className), children: children }));
|
|
5
|
-
}
|