beez-ui 0.7.0 → 0.8.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/CHANGELOG.md +21 -3
- package/README.md +290 -274
- package/dist/components/account-menu.d.ts +53 -0
- package/dist/components/account-menu.js +405 -0
- package/dist/components/animated-collapse.d.ts +19 -0
- package/dist/components/animated-collapse.js +81 -0
- package/dist/components/animated-count.d.ts +13 -0
- package/dist/components/animated-count.js +105 -0
- package/dist/components/animated-list-item.d.ts +25 -0
- package/dist/components/animated-list-item.js +132 -0
- package/dist/components/bouncing-dots-loader.d.ts +19 -0
- package/dist/components/bouncing-dots-loader.js +47 -0
- package/dist/components/confirm-delete-button.d.ts +29 -0
- package/dist/components/confirm-delete-button.js +254 -0
- package/dist/components/empty-state.d.ts +24 -0
- package/dist/components/empty-state.js +37 -0
- package/dist/components/error-state.d.ts +20 -0
- package/dist/components/error-state.js +145 -0
- package/dist/components/external-browser-handoff.d.ts +27 -0
- package/dist/components/external-browser-handoff.js +306 -0
- package/dist/components/file-upload.d.ts +78 -0
- package/dist/components/file-upload.js +678 -0
- package/dist/components/info-popover.d.ts +23 -0
- package/dist/components/info-popover.js +158 -0
- package/dist/components/month-calendar-header.d.ts +65 -0
- package/dist/components/month-calendar-header.js +337 -0
- package/dist/components/month-grid.d.ts +59 -0
- package/dist/components/month-grid.js +301 -0
- package/dist/components/notification-bell.d.ts +50 -0
- package/dist/components/notification-bell.js +338 -0
- package/dist/components/notification-panel.d.ts +56 -0
- package/dist/components/notification-panel.js +281 -0
- package/dist/components/open-in-browser-cta.d.ts +22 -0
- package/dist/components/open-in-browser-cta.js +61 -0
- package/dist/components/presence-swap.d.ts +22 -0
- package/dist/components/presence-swap.js +162 -0
- package/dist/components/progress-ring.d.ts +20 -0
- package/dist/components/progress-ring.js +53 -0
- package/dist/components/pwa-update-control.d.ts +18 -0
- package/dist/components/pwa-update-control.js +119 -0
- package/dist/components/reaction-button.d.ts +26 -0
- package/dist/components/reaction-button.js +114 -0
- package/dist/components/rich-link-editor.d.ts +25 -0
- package/dist/components/rich-link-editor.js +320 -0
- package/dist/components/rich-markdown-content.d.ts +10 -0
- package/dist/components/rich-markdown-content.js +56 -0
- package/dist/components/rich-text-content.d.ts +19 -0
- package/dist/components/rich-text-content.js +26 -0
- package/dist/emoji-picker.d.ts +27 -0
- package/dist/emoji-picker.js +283 -0
- package/dist/hooks/use-horizontal-swipe.d.ts +23 -0
- package/dist/hooks/use-horizontal-swipe.js +80 -0
- package/dist/hooks/use-is-hydrated.d.ts +6 -0
- package/dist/hooks/use-is-hydrated.js +20 -0
- package/dist/hooks/use-minute-clock.d.ts +23 -0
- package/dist/hooks/use-minute-clock.js +147 -0
- package/dist/hooks/use-month-transition-direction.d.ts +21 -0
- package/dist/hooks/use-month-transition-direction.js +110 -0
- package/dist/hooks/use-rich-link-editor.d.ts +55 -0
- package/dist/hooks/use-rich-link-editor.js +515 -0
- package/dist/hooks/use-viewer-time-zone.d.ts +6 -0
- package/dist/hooks/use-viewer-time-zone.js +20 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +43 -0
- package/dist/lib/browser-clipboard.d.ts +13 -0
- package/dist/lib/browser-clipboard.js +48 -0
- package/dist/lib/browser-navigation.d.ts +21 -0
- package/dist/lib/browser-navigation.js +42 -0
- package/dist/lib/file-acceptance.d.ts +43 -0
- package/dist/lib/file-acceptance.js +107 -0
- package/dist/lib/format-file-size.d.ts +16 -0
- package/dist/lib/format-file-size.js +34 -0
- package/dist/lib/fuzzy-search.d.ts +17 -0
- package/dist/lib/fuzzy-search.js +183 -0
- package/dist/lib/horizontal-swipe.d.ts +23 -0
- package/dist/lib/horizontal-swipe.js +29 -0
- package/dist/lib/in-app-browser.d.ts +35 -0
- package/dist/lib/in-app-browser.js +80 -0
- package/dist/lib/month-grid.d.ts +42 -0
- package/dist/lib/month-grid.js +74 -0
- package/dist/lib/name-initials.d.ts +7 -0
- package/dist/lib/name-initials.js +11 -0
- package/dist/lib/rich-text/link-markdown-constants.d.ts +147 -0
- package/dist/lib/rich-text/link-markdown-constants.js +458 -0
- package/dist/lib/rich-text/link-markdown-types.d.ts +53 -0
- package/dist/lib/rich-text/link-markdown-types.js +1 -0
- package/dist/lib/rich-text/link-markdown.d.ts +193 -0
- package/dist/lib/rich-text/link-markdown.js +592 -0
- package/dist/lib/rich-text/rich-markdown.d.ts +41 -0
- package/dist/lib/rich-text/rich-markdown.js +105 -0
- package/dist/lib/rich-text/rich-text-editor-dom.d.ts +26 -0
- package/dist/lib/rich-text/rich-text-editor-dom.js +79 -0
- package/dist/motion/tokens.d.ts +17 -1
- package/dist/motion/tokens.js +17 -1
- package/dist/styles.css +1 -1
- package/package.json +211 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface PwaUpdateControlLabels {
|
|
2
|
+
/** Short notice next to the button; hidden on narrow screens. */
|
|
3
|
+
badge?: string;
|
|
4
|
+
action?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PwaUpdateControlProps {
|
|
7
|
+
labels?: PwaUpdateControlLabels;
|
|
8
|
+
/** Message posted to the waiting worker so it activates. */
|
|
9
|
+
skipWaitingMessage?: unknown;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Renders the update notice and action once an update is waiting; nothing otherwise, including
|
|
14
|
+
* during server rendering and hydration.
|
|
15
|
+
* @param props - Optional labels, activation message and class name.
|
|
16
|
+
* @returns The update control, or nothing.
|
|
17
|
+
*/
|
|
18
|
+
export declare function PwaUpdateControl({ labels, skipWaitingMessage, className }: PwaUpdateControlProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* Offers to apply a waiting service worker update. It stays hidden until the browser reports a
|
|
4
|
+
* waiting worker, asks it to activate on demand and reloads once the new worker takes control.
|
|
5
|
+
*/
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
|
+
import { RefreshCw } from "lucide-react";
|
|
8
|
+
import { useIsHydrated } from "../hooks/use-is-hydrated.js";
|
|
9
|
+
import { cn } from "../lib/utils.js";
|
|
10
|
+
import { Button } from "./button.js";
|
|
11
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
12
|
+
/** Message understood by Workbox and most hand-written workers to call `skipWaiting()`. */
|
|
13
|
+
const DEFAULT_SKIP_WAITING_MESSAGE = { type: "SKIP_WAITING" };
|
|
14
|
+
const INSTALLED_WORKER_STATE = "installed";
|
|
15
|
+
const PWA_UPDATE_DEFAULT_LABELS = {
|
|
16
|
+
badge: "Hay una nueva versión",
|
|
17
|
+
action: "Actualizar app"
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Tells whether the current page can talk to service workers.
|
|
21
|
+
* @returns Whether the Service Worker API is available.
|
|
22
|
+
*/
|
|
23
|
+
function canUseServiceWorker() {
|
|
24
|
+
return typeof window !== "undefined" && "serviceWorker" in navigator;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Reports a waiting worker now, or as soon as a newly found one finishes installing.
|
|
28
|
+
* @param registration - Registration of the current page.
|
|
29
|
+
* @param onWaiting - Called when an update is ready to activate.
|
|
30
|
+
* @returns A cleanup that stops watching.
|
|
31
|
+
*/
|
|
32
|
+
function watchForWaitingWorker(registration, onWaiting) {
|
|
33
|
+
if (registration.waiting) onWaiting();
|
|
34
|
+
const handleUpdateFound = () => {
|
|
35
|
+
const installingWorker = registration.installing;
|
|
36
|
+
if (!installingWorker) return;
|
|
37
|
+
installingWorker.addEventListener("statechange", () => {
|
|
38
|
+
// Without a controller this is the first install, not an update.
|
|
39
|
+
if (installingWorker.state === INSTALLED_WORKER_STATE && navigator.serviceWorker.controller) onWaiting();
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
registration.addEventListener("updatefound", handleUpdateFound);
|
|
43
|
+
return () => registration.removeEventListener("updatefound", handleUpdateFound);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Renders the update notice and action once an update is waiting; nothing otherwise, including
|
|
47
|
+
* during server rendering and hydration.
|
|
48
|
+
* @param props - Optional labels, activation message and class name.
|
|
49
|
+
* @returns The update control, or nothing.
|
|
50
|
+
*/
|
|
51
|
+
export function PwaUpdateControl({ labels, skipWaitingMessage = DEFAULT_SKIP_WAITING_MESSAGE, className }) {
|
|
52
|
+
const resolvedLabels = {
|
|
53
|
+
...PWA_UPDATE_DEFAULT_LABELS,
|
|
54
|
+
...labels
|
|
55
|
+
};
|
|
56
|
+
const isHydrated = useIsHydrated();
|
|
57
|
+
const [hasUpdateReady, setHasUpdateReady] = useState(false);
|
|
58
|
+
const [isApplyingUpdate, setIsApplyingUpdate] = useState(false);
|
|
59
|
+
const hasReloadedForControllerChange = useRef(false);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!isHydrated || !canUseServiceWorker()) return;
|
|
62
|
+
let stopWatchingRegistration = () => {};
|
|
63
|
+
let isDisposed = false;
|
|
64
|
+
const markUpdateReady = () => {
|
|
65
|
+
if (!isDisposed) setHasUpdateReady(true);
|
|
66
|
+
};
|
|
67
|
+
const handleControllerChange = () => {
|
|
68
|
+
if (hasReloadedForControllerChange.current) return;
|
|
69
|
+
hasReloadedForControllerChange.current = true;
|
|
70
|
+
window.location.reload();
|
|
71
|
+
};
|
|
72
|
+
navigator.serviceWorker.addEventListener("controllerchange", handleControllerChange);
|
|
73
|
+
const attachUpdateWatcher = async () => {
|
|
74
|
+
const registration = await navigator.serviceWorker.getRegistration();
|
|
75
|
+
if (!registration || isDisposed) return;
|
|
76
|
+
stopWatchingRegistration = watchForWaitingWorker(registration, markUpdateReady);
|
|
77
|
+
// A failed check (offline, server error) keeps the current version without blocking the page.
|
|
78
|
+
await registration.update().catch(() => undefined);
|
|
79
|
+
if (registration.waiting) markUpdateReady();
|
|
80
|
+
};
|
|
81
|
+
void attachUpdateWatcher();
|
|
82
|
+
return () => {
|
|
83
|
+
isDisposed = true;
|
|
84
|
+
stopWatchingRegistration();
|
|
85
|
+
navigator.serviceWorker.removeEventListener("controllerchange", handleControllerChange);
|
|
86
|
+
};
|
|
87
|
+
}, [isHydrated]);
|
|
88
|
+
const applyUpdate = async () => {
|
|
89
|
+
setIsApplyingUpdate(true);
|
|
90
|
+
try {
|
|
91
|
+
const registration = await navigator.serviceWorker.getRegistration();
|
|
92
|
+
if (!registration) return;
|
|
93
|
+
if (!registration.waiting) await registration.update();
|
|
94
|
+
registration.waiting?.postMessage(skipWaitingMessage);
|
|
95
|
+
} catch (updateError) {
|
|
96
|
+
console.error("PwaUpdateControl:applyUpdate failed to activate the waiting service worker", updateError);
|
|
97
|
+
} finally {
|
|
98
|
+
setIsApplyingUpdate(false);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
if (!isHydrated || !canUseServiceWorker() || !hasUpdateReady) return null;
|
|
102
|
+
return /* @__PURE__ */ _jsxs("div", {
|
|
103
|
+
"data-slot": "pwa-update-control",
|
|
104
|
+
className: cn("inline-flex items-center gap-[0.45rem]", className),
|
|
105
|
+
children: [/* @__PURE__ */ _jsx("span", {
|
|
106
|
+
className: "hidden items-center rounded-full border border-[color-mix(in_srgb,var(--border)_84%,var(--primary)_16%)] bg-[color-mix(in_srgb,var(--muted)_90%,var(--primary)_10%)] px-2 py-[0.1rem] text-[0.72rem] leading-tight text-[color-mix(in_srgb,var(--foreground)_78%,var(--primary)_22%)] sm:inline-flex",
|
|
107
|
+
children: resolvedLabels.badge
|
|
108
|
+
}), /* @__PURE__ */ _jsxs(Button, {
|
|
109
|
+
disabled: isApplyingUpdate,
|
|
110
|
+
size: "sm",
|
|
111
|
+
type: "button",
|
|
112
|
+
onClick: () => void applyUpdate(),
|
|
113
|
+
children: [/* @__PURE__ */ _jsx(RefreshCw, {
|
|
114
|
+
"aria-hidden": "true",
|
|
115
|
+
className: cn(isApplyingUpdate && "animate-spin")
|
|
116
|
+
}), resolvedLabels.action]
|
|
117
|
+
})]
|
|
118
|
+
});
|
|
119
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toggle for likes and similar reactions: the icon pops when the viewer reacts and the counter
|
|
3
|
+
* rolls in the direction of the change. The (optimistic) reaction state is owned by the caller.
|
|
4
|
+
*/
|
|
5
|
+
import type { ComponentType, MouseEvent, SVGProps } from "react";
|
|
6
|
+
export interface ReactionButtonProps {
|
|
7
|
+
/** Accessible name, including the current count (for example "Me gusta 3"). */
|
|
8
|
+
ariaLabel: string;
|
|
9
|
+
isActive: boolean;
|
|
10
|
+
count: number;
|
|
11
|
+
onClick: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
12
|
+
isDisabled?: boolean;
|
|
13
|
+
/** Icon of the reaction. While active it is filled with the destructive color; override it with `[&_svg]:` classes. */
|
|
14
|
+
icon?: ComponentType<SVGProps<SVGSVGElement>>;
|
|
15
|
+
/** Formats the visible count, for example with a locale-aware compact format. */
|
|
16
|
+
formatCount?: (count: number) => string;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Renders an outline toggle button exposing its state with `aria-pressed`. The icon starts at
|
|
21
|
+
* rest (`initial={false}`), so server-rendered and already-active reactions never pop on load;
|
|
22
|
+
* only a change to active does, and not at all with reduced motion.
|
|
23
|
+
* @param props - Accessible name, state, count, click handler and optional icon.
|
|
24
|
+
* @returns The reaction button.
|
|
25
|
+
*/
|
|
26
|
+
export declare function ReactionButton({ ariaLabel, isActive, count, onClick, isDisabled, icon: Icon, formatCount, className, }: ReactionButtonProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { c as _c } from "react/compiler-runtime";
|
|
3
|
+
import { HeartIcon } from "lucide-react";
|
|
4
|
+
import { motion } from "motion/react";
|
|
5
|
+
import { usePrefersReducedMotion } from "../hooks/use-prefers-reduced-motion.js";
|
|
6
|
+
import { cn } from "../lib/utils.js";
|
|
7
|
+
import { MOTION_EASE, MOTION_TIMING } from "../motion/tokens.js";
|
|
8
|
+
import { AnimatedCount } from "./animated-count.js";
|
|
9
|
+
import { Button } from "./button.js";
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
/** Peak scale the icon reaches while popping into the active state. */
|
|
12
|
+
const REACTION_ICON_POP_SCALE = 1.28;
|
|
13
|
+
const REACTION_ICON_VARIANTS = {
|
|
14
|
+
idle: { scale: 1 },
|
|
15
|
+
active: {
|
|
16
|
+
scale: [
|
|
17
|
+
1,
|
|
18
|
+
REACTION_ICON_POP_SCALE,
|
|
19
|
+
1
|
|
20
|
+
],
|
|
21
|
+
transition: {
|
|
22
|
+
duration: MOTION_TIMING.pop,
|
|
23
|
+
ease: MOTION_EASE
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Renders an outline toggle button exposing its state with `aria-pressed`. The icon starts at
|
|
29
|
+
* rest (`initial={false}`), so server-rendered and already-active reactions never pop on load;
|
|
30
|
+
* only a change to active does, and not at all with reduced motion.
|
|
31
|
+
* @param props - Accessible name, state, count, click handler and optional icon.
|
|
32
|
+
* @returns The reaction button.
|
|
33
|
+
*/
|
|
34
|
+
export function ReactionButton(t0) {
|
|
35
|
+
const $ = _c(19);
|
|
36
|
+
const { ariaLabel, isActive, count, onClick, isDisabled: t1, icon: t2, formatCount, className } = t0;
|
|
37
|
+
const isDisabled = t1 === undefined ? false : t1;
|
|
38
|
+
const Icon = t2 === undefined ? HeartIcon : t2;
|
|
39
|
+
const shouldReduceMotion = usePrefersReducedMotion();
|
|
40
|
+
const t3 = isActive || undefined;
|
|
41
|
+
let t4;
|
|
42
|
+
if ($[0] !== className) {
|
|
43
|
+
t4 = cn("group/reaction", className);
|
|
44
|
+
$[0] = className;
|
|
45
|
+
$[1] = t4;
|
|
46
|
+
} else {
|
|
47
|
+
t4 = $[1];
|
|
48
|
+
}
|
|
49
|
+
const t5 = isActive && !shouldReduceMotion ? "active" : "idle";
|
|
50
|
+
let t6;
|
|
51
|
+
if ($[2] !== Icon) {
|
|
52
|
+
t6 = /* @__PURE__ */ _jsx(Icon, { className: "size-4 transition-[fill,color] group-data-active/reaction:fill-current group-data-active/reaction:text-destructive" });
|
|
53
|
+
$[2] = Icon;
|
|
54
|
+
$[3] = t6;
|
|
55
|
+
} else {
|
|
56
|
+
t6 = $[3];
|
|
57
|
+
}
|
|
58
|
+
let t7;
|
|
59
|
+
if ($[4] !== t5 || $[5] !== t6) {
|
|
60
|
+
t7 = /* @__PURE__ */ _jsx(motion.span, {
|
|
61
|
+
animate: t5,
|
|
62
|
+
"aria-hidden": true,
|
|
63
|
+
className: "inline-flex origin-center items-center justify-center",
|
|
64
|
+
initial: false,
|
|
65
|
+
variants: REACTION_ICON_VARIANTS,
|
|
66
|
+
children: t6
|
|
67
|
+
});
|
|
68
|
+
$[4] = t5;
|
|
69
|
+
$[5] = t6;
|
|
70
|
+
$[6] = t7;
|
|
71
|
+
} else {
|
|
72
|
+
t7 = $[6];
|
|
73
|
+
}
|
|
74
|
+
let t8;
|
|
75
|
+
if ($[7] !== count || $[8] !== formatCount) {
|
|
76
|
+
t8 = /* @__PURE__ */ _jsx(AnimatedCount, {
|
|
77
|
+
className: "min-w-[1ch]",
|
|
78
|
+
value: count,
|
|
79
|
+
format: formatCount
|
|
80
|
+
});
|
|
81
|
+
$[7] = count;
|
|
82
|
+
$[8] = formatCount;
|
|
83
|
+
$[9] = t8;
|
|
84
|
+
} else {
|
|
85
|
+
t8 = $[9];
|
|
86
|
+
}
|
|
87
|
+
let t9;
|
|
88
|
+
if ($[10] !== ariaLabel || $[11] !== isActive || $[12] !== isDisabled || $[13] !== onClick || $[14] !== t3 || $[15] !== t4 || $[16] !== t7 || $[17] !== t8) {
|
|
89
|
+
t9 = /* @__PURE__ */ _jsxs(Button, {
|
|
90
|
+
"data-slot": "reaction-button",
|
|
91
|
+
"data-active": t3,
|
|
92
|
+
"aria-label": ariaLabel,
|
|
93
|
+
"aria-pressed": isActive,
|
|
94
|
+
className: t4,
|
|
95
|
+
disabled: isDisabled,
|
|
96
|
+
onClick,
|
|
97
|
+
type: "button",
|
|
98
|
+
variant: "outline",
|
|
99
|
+
children: [t7, t8]
|
|
100
|
+
});
|
|
101
|
+
$[10] = ariaLabel;
|
|
102
|
+
$[11] = isActive;
|
|
103
|
+
$[12] = isDisabled;
|
|
104
|
+
$[13] = onClick;
|
|
105
|
+
$[14] = t3;
|
|
106
|
+
$[15] = t4;
|
|
107
|
+
$[16] = t7;
|
|
108
|
+
$[17] = t8;
|
|
109
|
+
$[18] = t9;
|
|
110
|
+
} else {
|
|
111
|
+
t9 = $[18];
|
|
112
|
+
}
|
|
113
|
+
return t9;
|
|
114
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { RichLinkEditorController } from "../hooks/use-rich-link-editor.js";
|
|
2
|
+
/** Copy for the link popover. Every entry is optional and defaults to Spanish. */
|
|
3
|
+
export type RichLinkEditorCopy = {
|
|
4
|
+
editAction: string;
|
|
5
|
+
editCancel: string;
|
|
6
|
+
editSave: string;
|
|
7
|
+
popoverTextLabel: string;
|
|
8
|
+
popoverUrlLabel: string;
|
|
9
|
+
removeAction: string;
|
|
10
|
+
};
|
|
11
|
+
export type RichLinkEditorProps = {
|
|
12
|
+
ariaLabel: string;
|
|
13
|
+
copy?: Partial<RichLinkEditorCopy>;
|
|
14
|
+
editor: RichLinkEditorController;
|
|
15
|
+
placeholder: string;
|
|
16
|
+
ariaDescribedBy?: string;
|
|
17
|
+
isDisabled?: boolean;
|
|
18
|
+
isInvalid?: boolean;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* `contentEditable` rich link editor: renders the controller's preview segments,
|
|
22
|
+
* turning each link into a popover that edits or removes it. All state lives in
|
|
23
|
+
* the `useRichLinkEditor` controller; this component is presentation only.
|
|
24
|
+
*/
|
|
25
|
+
export declare function RichLinkEditor({ ariaDescribedBy, ariaLabel, copy: copyOverrides, editor, isDisabled, isInvalid, placeholder, }: RichLinkEditorProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { c as _c } from "react/compiler-runtime";
|
|
3
|
+
/** `contentEditable` editor for plain text with markdown links, driven by `useRichLinkEditor`. */
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
import { cn } from "../lib/utils.js";
|
|
6
|
+
import { RICH_LINK_POPOVER_MODE, RICH_TEXT_EDITOR_KEY, RICH_TEXT_SEGMENT_TYPE } from "../lib/rich-text/link-markdown-constants.js";
|
|
7
|
+
import { Button } from "./button.js";
|
|
8
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
9
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
|
+
const BUTTON_TYPE = "button";
|
|
11
|
+
const BUTTON_VARIANT = {
|
|
12
|
+
ghost: "ghost",
|
|
13
|
+
outline: "outline"
|
|
14
|
+
};
|
|
15
|
+
const URL_INPUT_TYPE = "url";
|
|
16
|
+
const LINK_TARGET = "_blank";
|
|
17
|
+
const LINK_REL = "noreferrer";
|
|
18
|
+
/**
|
|
19
|
+
* Presentation of each part. iOS Safari zooms the page when a focused editable region renders
|
|
20
|
+
* text below 16px, so the editor and the inputs are pinned to 1rem on phones.
|
|
21
|
+
*/
|
|
22
|
+
const RICH_LINK_EDITOR_CLASS_NAMES = {
|
|
23
|
+
editor: "min-h-28 w-full cursor-text border-0 bg-background p-0 leading-[1.65] whitespace-pre-wrap text-foreground [font:inherit] [overflow-wrap:anywhere] focus:outline-0 empty:before:pointer-events-none empty:before:text-muted-foreground/75 empty:before:content-[attr(data-placeholder)] max-md:text-base",
|
|
24
|
+
editorInvalid: "rounded-[0.35rem] shadow-[inset_0_-2px_0_var(--destructive)] outline-0",
|
|
25
|
+
editorLink: "cursor-pointer rounded-[0.2rem] font-bold text-primary underline decoration-[0.08em] underline-offset-[0.18em] transition-colors hover:text-[color-mix(in_oklab,var(--primary)_72%,var(--foreground))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
|
|
26
|
+
popover: "gap-3",
|
|
27
|
+
editForm: "grid gap-3 animate-in fade-in slide-in-from-bottom-1 duration-200",
|
|
28
|
+
actions: "flex flex-wrap items-center gap-2",
|
|
29
|
+
label: "grid gap-[0.35rem] text-[0.85rem] font-bold text-muted-foreground",
|
|
30
|
+
input: "min-h-8 rounded-lg border border-border/80 bg-background px-[0.65rem] font-normal text-foreground outline-0 transition-[border-color,box-shadow] [font:inherit] focus-visible:border-ring focus-visible:shadow-[0_0_0_3px_color-mix(in_oklab,var(--ring)_35%,transparent)] aria-invalid:border-destructive aria-invalid:focus-visible:shadow-[0_0_0_3px_color-mix(in_oklab,var(--destructive)_25%,transparent)] max-md:text-base"
|
|
31
|
+
};
|
|
32
|
+
const RICH_LINK_EDITOR_DEFAULT_COPY = {
|
|
33
|
+
editAction: "Editar",
|
|
34
|
+
editCancel: "Cancelar",
|
|
35
|
+
editSave: "Guardar",
|
|
36
|
+
popoverTextLabel: "Texto",
|
|
37
|
+
popoverUrlLabel: "Enlace",
|
|
38
|
+
removeAction: "Quitar enlace"
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* `contentEditable` rich link editor: renders the controller's preview segments,
|
|
42
|
+
* turning each link into a popover that edits or removes it. All state lives in
|
|
43
|
+
* the `useRichLinkEditor` controller; this component is presentation only.
|
|
44
|
+
*/
|
|
45
|
+
export function RichLinkEditor(t0) {
|
|
46
|
+
const $ = _c(50);
|
|
47
|
+
const { ariaDescribedBy, ariaLabel, copy: copyOverrides, editor, isDisabled: t1, isInvalid: t2, placeholder } = t0;
|
|
48
|
+
const isDisabled = t1 === undefined ? false : t1;
|
|
49
|
+
const isInvalid = t2 === undefined ? false : t2;
|
|
50
|
+
const { activeLink, closeLinkPopover, handleBeforeInput, handleInput, handleKeyDown, handlePaste, hasContent, hasInvalidLinkUrl, isLinkEditValid, linkTextInput, linkUrlInput, openLinkPopover, popoverMode, removeLink, saveLinkEdit, segments, setEditorElement, setLinkTextInput, setLinkUrlInput, setPopoverMode } = editor;
|
|
51
|
+
let t3;
|
|
52
|
+
if ($[0] !== copyOverrides) {
|
|
53
|
+
t3 = {
|
|
54
|
+
...RICH_LINK_EDITOR_DEFAULT_COPY,
|
|
55
|
+
...copyOverrides
|
|
56
|
+
};
|
|
57
|
+
$[0] = copyOverrides;
|
|
58
|
+
$[1] = t3;
|
|
59
|
+
} else {
|
|
60
|
+
t3 = $[1];
|
|
61
|
+
}
|
|
62
|
+
const copy = t3;
|
|
63
|
+
const linkTextInputRef = useRef(null);
|
|
64
|
+
const isEditingLink = popoverMode === RICH_LINK_POPOVER_MODE.edit;
|
|
65
|
+
const t4 = isInvalid && RICH_LINK_EDITOR_CLASS_NAMES.editorInvalid;
|
|
66
|
+
let t5;
|
|
67
|
+
if ($[2] !== t4) {
|
|
68
|
+
t5 = cn(RICH_LINK_EDITOR_CLASS_NAMES.editor, t4);
|
|
69
|
+
$[2] = t4;
|
|
70
|
+
$[3] = t5;
|
|
71
|
+
} else {
|
|
72
|
+
t5 = $[3];
|
|
73
|
+
}
|
|
74
|
+
const editorClassName = t5;
|
|
75
|
+
let t6;
|
|
76
|
+
let t7;
|
|
77
|
+
if ($[4] !== closeLinkPopover || $[5] !== isDisabled) {
|
|
78
|
+
t6 = () => {
|
|
79
|
+
if (isDisabled) {
|
|
80
|
+
closeLinkPopover();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
t7 = [closeLinkPopover, isDisabled];
|
|
84
|
+
$[4] = closeLinkPopover;
|
|
85
|
+
$[5] = isDisabled;
|
|
86
|
+
$[6] = t6;
|
|
87
|
+
$[7] = t7;
|
|
88
|
+
} else {
|
|
89
|
+
t6 = $[6];
|
|
90
|
+
t7 = $[7];
|
|
91
|
+
}
|
|
92
|
+
useEffect(t6, t7);
|
|
93
|
+
let t8;
|
|
94
|
+
if ($[8] !== isEditingLink) {
|
|
95
|
+
t8 = () => {
|
|
96
|
+
if (isEditingLink) {
|
|
97
|
+
linkTextInputRef.current?.focus();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
$[8] = isEditingLink;
|
|
101
|
+
$[9] = t8;
|
|
102
|
+
} else {
|
|
103
|
+
t8 = $[9];
|
|
104
|
+
}
|
|
105
|
+
const t9 = activeLink?.key;
|
|
106
|
+
let t10;
|
|
107
|
+
if ($[10] !== isEditingLink || $[11] !== t9) {
|
|
108
|
+
t10 = [isEditingLink, t9];
|
|
109
|
+
$[10] = isEditingLink;
|
|
110
|
+
$[11] = t9;
|
|
111
|
+
$[12] = t10;
|
|
112
|
+
} else {
|
|
113
|
+
t10 = $[12];
|
|
114
|
+
}
|
|
115
|
+
useEffect(t8, t10);
|
|
116
|
+
let t11;
|
|
117
|
+
if ($[13] !== activeLink || $[14] !== isLinkEditValid || $[15] !== saveLinkEdit) {
|
|
118
|
+
t11 = (event) => {
|
|
119
|
+
if (event.key !== RICH_TEXT_EDITOR_KEY.enter) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
if (activeLink && isLinkEditValid) {
|
|
124
|
+
saveLinkEdit(activeLink);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
$[13] = activeLink;
|
|
128
|
+
$[14] = isLinkEditValid;
|
|
129
|
+
$[15] = saveLinkEdit;
|
|
130
|
+
$[16] = t11;
|
|
131
|
+
} else {
|
|
132
|
+
t11 = $[16];
|
|
133
|
+
}
|
|
134
|
+
const handleEditFieldKeyDown = t11;
|
|
135
|
+
const t12 = !isDisabled;
|
|
136
|
+
let t13;
|
|
137
|
+
if ($[17] !== activeLink || $[18] !== closeLinkPopover || $[19] !== copy || $[20] !== handleEditFieldKeyDown || $[21] !== hasContent || $[22] !== hasInvalidLinkUrl || $[23] !== isDisabled || $[24] !== isLinkEditValid || $[25] !== linkTextInput || $[26] !== linkUrlInput || $[27] !== openLinkPopover || $[28] !== popoverMode || $[29] !== removeLink || $[30] !== saveLinkEdit || $[31] !== segments || $[32] !== setLinkTextInput || $[33] !== setLinkUrlInput || $[34] !== setPopoverMode) {
|
|
138
|
+
t13 = hasContent ? segments.map((segment) => segment.type === RICH_TEXT_SEGMENT_TYPE.link ? isDisabled ? /* @__PURE__ */ _jsx("a", {
|
|
139
|
+
"aria-disabled": true,
|
|
140
|
+
className: RICH_LINK_EDITOR_CLASS_NAMES.editorLink,
|
|
141
|
+
href: segment.url,
|
|
142
|
+
onClick: _temp,
|
|
143
|
+
rel: LINK_REL,
|
|
144
|
+
tabIndex: -1,
|
|
145
|
+
target: LINK_TARGET,
|
|
146
|
+
children: segment.text
|
|
147
|
+
}, segment.key) : /* @__PURE__ */ _jsxs(Popover, {
|
|
148
|
+
onOpenChange: (isOpen) => {
|
|
149
|
+
if (isOpen) {
|
|
150
|
+
openLinkPopover(segment);
|
|
151
|
+
} else {
|
|
152
|
+
if (activeLink?.key === segment.key) {
|
|
153
|
+
closeLinkPopover();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
open: activeLink?.key === segment.key,
|
|
158
|
+
children: [/* @__PURE__ */ _jsx(PopoverTrigger, {
|
|
159
|
+
asChild: true,
|
|
160
|
+
children: /* @__PURE__ */ _jsx("a", {
|
|
161
|
+
className: RICH_LINK_EDITOR_CLASS_NAMES.editorLink,
|
|
162
|
+
href: segment.url,
|
|
163
|
+
onClick: (event_1) => {
|
|
164
|
+
event_1.preventDefault();
|
|
165
|
+
openLinkPopover(segment);
|
|
166
|
+
},
|
|
167
|
+
rel: LINK_REL,
|
|
168
|
+
target: LINK_TARGET,
|
|
169
|
+
children: segment.text
|
|
170
|
+
})
|
|
171
|
+
}), /* @__PURE__ */ _jsx(PopoverContent, {
|
|
172
|
+
className: RICH_LINK_EDITOR_CLASS_NAMES.popover,
|
|
173
|
+
onBeforeInput: _temp2,
|
|
174
|
+
onInput: _temp3,
|
|
175
|
+
onKeyDown: _temp4,
|
|
176
|
+
onPaste: _temp5,
|
|
177
|
+
children: popoverMode === RICH_LINK_POPOVER_MODE.actions ? /* @__PURE__ */ _jsxs("div", {
|
|
178
|
+
className: RICH_LINK_EDITOR_CLASS_NAMES.actions,
|
|
179
|
+
children: [/* @__PURE__ */ _jsx(Button, {
|
|
180
|
+
onClick: () => {
|
|
181
|
+
setPopoverMode(RICH_LINK_POPOVER_MODE.edit);
|
|
182
|
+
},
|
|
183
|
+
type: BUTTON_TYPE,
|
|
184
|
+
variant: BUTTON_VARIANT.ghost,
|
|
185
|
+
children: copy.editAction
|
|
186
|
+
}), /* @__PURE__ */ _jsx(Button, {
|
|
187
|
+
onClick: () => {
|
|
188
|
+
removeLink(segment);
|
|
189
|
+
},
|
|
190
|
+
type: BUTTON_TYPE,
|
|
191
|
+
variant: BUTTON_VARIANT.ghost,
|
|
192
|
+
children: copy.removeAction
|
|
193
|
+
})]
|
|
194
|
+
}) : /* @__PURE__ */ _jsxs("div", {
|
|
195
|
+
className: RICH_LINK_EDITOR_CLASS_NAMES.editForm,
|
|
196
|
+
children: [
|
|
197
|
+
/* @__PURE__ */ _jsxs("label", {
|
|
198
|
+
className: RICH_LINK_EDITOR_CLASS_NAMES.label,
|
|
199
|
+
children: [/* @__PURE__ */ _jsx("span", { children: copy.popoverTextLabel }), /* @__PURE__ */ _jsx("input", {
|
|
200
|
+
className: RICH_LINK_EDITOR_CLASS_NAMES.input,
|
|
201
|
+
onChange: (event_6) => {
|
|
202
|
+
setLinkTextInput(event_6.currentTarget.value);
|
|
203
|
+
},
|
|
204
|
+
onKeyDown: handleEditFieldKeyDown,
|
|
205
|
+
ref: linkTextInputRef,
|
|
206
|
+
value: linkTextInput
|
|
207
|
+
})]
|
|
208
|
+
}),
|
|
209
|
+
/* @__PURE__ */ _jsxs("label", {
|
|
210
|
+
className: RICH_LINK_EDITOR_CLASS_NAMES.label,
|
|
211
|
+
children: [/* @__PURE__ */ _jsx("span", { children: copy.popoverUrlLabel }), /* @__PURE__ */ _jsx("input", {
|
|
212
|
+
"aria-invalid": hasInvalidLinkUrl,
|
|
213
|
+
className: RICH_LINK_EDITOR_CLASS_NAMES.input,
|
|
214
|
+
onChange: (event_7) => {
|
|
215
|
+
setLinkUrlInput(event_7.currentTarget.value);
|
|
216
|
+
},
|
|
217
|
+
onKeyDown: handleEditFieldKeyDown,
|
|
218
|
+
type: URL_INPUT_TYPE,
|
|
219
|
+
value: linkUrlInput
|
|
220
|
+
})]
|
|
221
|
+
}),
|
|
222
|
+
/* @__PURE__ */ _jsxs("div", {
|
|
223
|
+
className: RICH_LINK_EDITOR_CLASS_NAMES.actions,
|
|
224
|
+
children: [/* @__PURE__ */ _jsx(Button, {
|
|
225
|
+
disabled: !isLinkEditValid,
|
|
226
|
+
onClick: () => {
|
|
227
|
+
if (activeLink) {
|
|
228
|
+
saveLinkEdit(activeLink);
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
type: BUTTON_TYPE,
|
|
232
|
+
children: copy.editSave
|
|
233
|
+
}), /* @__PURE__ */ _jsx(Button, {
|
|
234
|
+
onClick: closeLinkPopover,
|
|
235
|
+
type: BUTTON_TYPE,
|
|
236
|
+
variant: BUTTON_VARIANT.outline,
|
|
237
|
+
children: copy.editCancel
|
|
238
|
+
})]
|
|
239
|
+
})
|
|
240
|
+
]
|
|
241
|
+
})
|
|
242
|
+
})]
|
|
243
|
+
}, segment.key) : segment.text) : null;
|
|
244
|
+
$[17] = activeLink;
|
|
245
|
+
$[18] = closeLinkPopover;
|
|
246
|
+
$[19] = copy;
|
|
247
|
+
$[20] = handleEditFieldKeyDown;
|
|
248
|
+
$[21] = hasContent;
|
|
249
|
+
$[22] = hasInvalidLinkUrl;
|
|
250
|
+
$[23] = isDisabled;
|
|
251
|
+
$[24] = isLinkEditValid;
|
|
252
|
+
$[25] = linkTextInput;
|
|
253
|
+
$[26] = linkUrlInput;
|
|
254
|
+
$[27] = openLinkPopover;
|
|
255
|
+
$[28] = popoverMode;
|
|
256
|
+
$[29] = removeLink;
|
|
257
|
+
$[30] = saveLinkEdit;
|
|
258
|
+
$[31] = segments;
|
|
259
|
+
$[32] = setLinkTextInput;
|
|
260
|
+
$[33] = setLinkUrlInput;
|
|
261
|
+
$[34] = setPopoverMode;
|
|
262
|
+
$[35] = t13;
|
|
263
|
+
} else {
|
|
264
|
+
t13 = $[35];
|
|
265
|
+
}
|
|
266
|
+
let t14;
|
|
267
|
+
if ($[36] !== ariaDescribedBy || $[37] !== ariaLabel || $[38] !== editorClassName || $[39] !== handleBeforeInput || $[40] !== handleInput || $[41] !== handleKeyDown || $[42] !== handlePaste || $[43] !== isDisabled || $[44] !== isInvalid || $[45] !== placeholder || $[46] !== setEditorElement || $[47] !== t12 || $[48] !== t13) {
|
|
268
|
+
t14 = /* @__PURE__ */ _jsx("div", {
|
|
269
|
+
"aria-describedby": ariaDescribedBy,
|
|
270
|
+
"aria-disabled": isDisabled,
|
|
271
|
+
"aria-invalid": isInvalid,
|
|
272
|
+
"aria-label": ariaLabel,
|
|
273
|
+
"aria-multiline": true,
|
|
274
|
+
className: editorClassName,
|
|
275
|
+
"data-slot": "rich-link-editor",
|
|
276
|
+
contentEditable: t12,
|
|
277
|
+
"data-placeholder": placeholder,
|
|
278
|
+
onBeforeInput: handleBeforeInput,
|
|
279
|
+
onInput: handleInput,
|
|
280
|
+
onKeyDown: handleKeyDown,
|
|
281
|
+
onPaste: handlePaste,
|
|
282
|
+
ref: setEditorElement,
|
|
283
|
+
role: "textbox",
|
|
284
|
+
suppressContentEditableWarning: true,
|
|
285
|
+
children: t13
|
|
286
|
+
});
|
|
287
|
+
$[36] = ariaDescribedBy;
|
|
288
|
+
$[37] = ariaLabel;
|
|
289
|
+
$[38] = editorClassName;
|
|
290
|
+
$[39] = handleBeforeInput;
|
|
291
|
+
$[40] = handleInput;
|
|
292
|
+
$[41] = handleKeyDown;
|
|
293
|
+
$[42] = handlePaste;
|
|
294
|
+
$[43] = isDisabled;
|
|
295
|
+
$[44] = isInvalid;
|
|
296
|
+
$[45] = placeholder;
|
|
297
|
+
$[46] = setEditorElement;
|
|
298
|
+
$[47] = t12;
|
|
299
|
+
$[48] = t13;
|
|
300
|
+
$[49] = t14;
|
|
301
|
+
} else {
|
|
302
|
+
t14 = $[49];
|
|
303
|
+
}
|
|
304
|
+
return t14;
|
|
305
|
+
}
|
|
306
|
+
function _temp5(event_5) {
|
|
307
|
+
event_5.stopPropagation();
|
|
308
|
+
}
|
|
309
|
+
function _temp4(event_4) {
|
|
310
|
+
event_4.stopPropagation();
|
|
311
|
+
}
|
|
312
|
+
function _temp3(event_3) {
|
|
313
|
+
event_3.stopPropagation();
|
|
314
|
+
}
|
|
315
|
+
function _temp2(event_2) {
|
|
316
|
+
event_2.stopPropagation();
|
|
317
|
+
}
|
|
318
|
+
function _temp(event_0) {
|
|
319
|
+
event_0.preventDefault();
|
|
320
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface RichMarkdownContentProps {
|
|
2
|
+
content: string;
|
|
3
|
+
className?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Parses and renders the content as paragraphs and lists.
|
|
7
|
+
* @param props - Stored content and optional class name.
|
|
8
|
+
* @returns The rendered blocks.
|
|
9
|
+
*/
|
|
10
|
+
export declare function RichMarkdownContent({ content, className }: RichMarkdownContentProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders long-form content with a safe markdown subset: paragraphs, unordered lists, inline
|
|
3
|
+
* bold and the shared link syntax. Raw HTML in the content is never interpreted as markup.
|
|
4
|
+
*/
|
|
5
|
+
import { cn } from "../lib/utils.js";
|
|
6
|
+
import { RICH_MARKDOWN_BLOCK_TYPE, RICH_MARKDOWN_INLINE_TYPE, parseRichMarkdownBlocks } from "../lib/rich-text/rich-markdown.js";
|
|
7
|
+
import { RICH_TEXT_LINK_CLASS_NAME } from "./rich-text-content.js";
|
|
8
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
|
+
const LINK_TARGET = "_blank";
|
|
10
|
+
const LINK_REL = "noreferrer";
|
|
11
|
+
/**
|
|
12
|
+
* Renders the inline pieces of a paragraph or list item.
|
|
13
|
+
* @param segments - Parsed inline segments.
|
|
14
|
+
* @returns Text, bold runs and links.
|
|
15
|
+
*/
|
|
16
|
+
function renderInlineSegments(segments) {
|
|
17
|
+
return segments.map((segment, segmentIndex) => {
|
|
18
|
+
const segmentKey = segment.type + String(segmentIndex);
|
|
19
|
+
if (segment.type === RICH_MARKDOWN_INLINE_TYPE.link) {
|
|
20
|
+
return /* @__PURE__ */ _jsx("a", {
|
|
21
|
+
"data-slot": "rich-text-link",
|
|
22
|
+
className: RICH_TEXT_LINK_CLASS_NAME,
|
|
23
|
+
href: segment.url,
|
|
24
|
+
rel: LINK_REL,
|
|
25
|
+
target: LINK_TARGET,
|
|
26
|
+
children: segment.text
|
|
27
|
+
}, segmentKey);
|
|
28
|
+
}
|
|
29
|
+
if (segment.type === RICH_MARKDOWN_INLINE_TYPE.bold) {
|
|
30
|
+
return /* @__PURE__ */ _jsx("strong", { children: segment.text }, segmentKey);
|
|
31
|
+
}
|
|
32
|
+
return segment.text;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parses and renders the content as paragraphs and lists.
|
|
37
|
+
* @param props - Stored content and optional class name.
|
|
38
|
+
* @returns The rendered blocks.
|
|
39
|
+
*/
|
|
40
|
+
export function RichMarkdownContent({ content, className }) {
|
|
41
|
+
const blocks = parseRichMarkdownBlocks(content);
|
|
42
|
+
return /* @__PURE__ */ _jsx("div", {
|
|
43
|
+
"data-slot": "rich-markdown-content",
|
|
44
|
+
className: cn("grid gap-[0.9rem]", className),
|
|
45
|
+
children: blocks.map((block, blockIndex) => block.type === RICH_MARKDOWN_BLOCK_TYPE.list ? /* @__PURE__ */ _jsx("ul", {
|
|
46
|
+
className: "m-0 grid list-disc gap-[0.35rem] pl-[1.4rem]",
|
|
47
|
+
children: block.items.map((itemSegments, itemIndex) => /* @__PURE__ */ _jsx("li", {
|
|
48
|
+
className: "leading-[1.7] [overflow-wrap:anywhere] marker:text-muted-foreground",
|
|
49
|
+
children: renderInlineSegments(itemSegments)
|
|
50
|
+
}, block.type + String(blockIndex) + String(itemIndex)))
|
|
51
|
+
}, block.type + String(blockIndex)) : /* @__PURE__ */ _jsx("p", {
|
|
52
|
+
className: "m-0 leading-[1.7] whitespace-pre-wrap [overflow-wrap:anywhere]",
|
|
53
|
+
children: renderInlineSegments(block.segments)
|
|
54
|
+
}, block.type + String(blockIndex)))
|
|
55
|
+
});
|
|
56
|
+
}
|