@timmbr/ui 1.14.0 → 1.15.1
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/components/RichTextEditor/RichTextContent.d.ts +8 -0
- package/dist/components/RichTextEditor/RichTextContent.d.ts.map +1 -0
- package/dist/components/RichTextEditor/RichTextContent.js +18 -0
- package/dist/components/RichTextEditor/RichTextEditor.context.d.ts +6 -0
- package/dist/components/RichTextEditor/RichTextEditor.context.d.ts.map +1 -0
- package/dist/components/RichTextEditor/RichTextEditor.context.js +14 -0
- package/dist/components/RichTextEditor/RichTextEditor.d.ts +4 -0
- package/dist/components/RichTextEditor/RichTextEditor.d.ts.map +1 -0
- package/dist/components/RichTextEditor/RichTextEditor.helpers.d.ts +28 -0
- package/dist/components/RichTextEditor/RichTextEditor.helpers.d.ts.map +1 -0
- package/dist/components/RichTextEditor/RichTextEditor.helpers.js +52 -0
- package/dist/components/RichTextEditor/RichTextEditor.js +156 -0
- package/dist/components/RichTextEditor/RichTextEditor.styles.d.ts +10 -0
- package/dist/components/RichTextEditor/RichTextEditor.styles.d.ts.map +1 -0
- package/dist/components/RichTextEditor/RichTextEditor.styles.js +95 -0
- package/dist/components/RichTextEditor/RichTextEditor.types.d.ts +73 -0
- package/dist/components/RichTextEditor/RichTextEditor.types.d.ts.map +1 -0
- package/dist/components/RichTextEditor/RichTextJsonContent.d.ts +9 -0
- package/dist/components/RichTextEditor/RichTextJsonContent.d.ts.map +1 -0
- package/dist/components/RichTextEditor/RichTextJsonContent.js +27 -0
- package/dist/components/RichTextEditor/index.d.ts +14 -0
- package/dist/components/RichTextEditor/index.d.ts.map +1 -0
- package/dist/components/RichTextEditor/subcomponents/RteCharCount.d.ts +4 -0
- package/dist/components/RichTextEditor/subcomponents/RteCharCount.d.ts.map +1 -0
- package/dist/components/RichTextEditor/subcomponents/RteCharCount.js +37 -0
- package/dist/components/RichTextEditor/subcomponents/RteEmojiPicker.d.ts +3 -0
- package/dist/components/RichTextEditor/subcomponents/RteEmojiPicker.d.ts.map +1 -0
- package/dist/components/RichTextEditor/subcomponents/RteEmojiPicker.js +274 -0
- package/dist/components/RichTextEditor/subcomponents/RteLinkPopover.d.ts +3 -0
- package/dist/components/RichTextEditor/subcomponents/RteLinkPopover.d.ts.map +1 -0
- package/dist/components/RichTextEditor/subcomponents/RteLinkPopover.js +93 -0
- package/dist/components/RichTextEditor/subcomponents/RteToolbar.d.ts +7 -0
- package/dist/components/RichTextEditor/subcomponents/RteToolbar.d.ts.map +1 -0
- package/dist/components/RichTextEditor/subcomponents/RteToolbar.js +234 -0
- package/dist/components/RichTextEditor/subcomponents/RteToolbarButton.d.ts +4 -0
- package/dist/components/RichTextEditor/subcomponents/RteToolbarButton.d.ts.map +1 -0
- package/dist/components/RichTextEditor/subcomponents/RteToolbarButton.js +37 -0
- package/dist/components/RichTextEditor/subcomponents/RteToolbarGroup.d.ts +4 -0
- package/dist/components/RichTextEditor/subcomponents/RteToolbarGroup.d.ts.map +1 -0
- package/dist/components/RichTextEditor/subcomponents/RteToolbarGroup.js +13 -0
- package/dist/components/SideBarNavigation/SideBarNavigation.js +1 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/package.json +10 -2
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { RichTextContentProps } from './RichTextEditor.types';
|
|
3
|
+
/**
|
|
4
|
+
* SSR-safe viewer for HTML content produced by RichTextEditor.
|
|
5
|
+
* Applies the same DS prose typography as the editor without loading TipTap.
|
|
6
|
+
*/
|
|
7
|
+
export declare function RichTextContent({ content, className, style }: RichTextContentProps): React.JSX.Element | null;
|
|
8
|
+
//# sourceMappingURL=RichTextContent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RichTextContent.d.ts","sourceRoot":"","sources":["../../../src/components/RichTextEditor/RichTextContent.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,oBAAoB,4BAYlF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { cn } from '@timmbr/utils';
|
|
3
|
+
import { proseViewerStyles } from './RichTextEditor.styles.js';
|
|
4
|
+
|
|
5
|
+
function RichTextContent({ content, className, style }) {
|
|
6
|
+
if (!content) return null;
|
|
7
|
+
return /* @__PURE__ */ jsx(
|
|
8
|
+
"div",
|
|
9
|
+
{
|
|
10
|
+
"data-slot": "rte-content",
|
|
11
|
+
className: cn(proseViewerStyles, className),
|
|
12
|
+
style,
|
|
13
|
+
dangerouslySetInnerHTML: { __html: content }
|
|
14
|
+
}
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { RichTextContent };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { RteContextValue } from './RichTextEditor.types';
|
|
3
|
+
declare const RteContext: React.Context<RteContextValue>;
|
|
4
|
+
export { RteContext };
|
|
5
|
+
export declare function useRteContext(): RteContextValue;
|
|
6
|
+
//# sourceMappingURL=RichTextEditor.context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RichTextEditor.context.d.ts","sourceRoot":"","sources":["../../../src/components/RichTextEditor/RichTextEditor.context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,QAAA,MAAM,UAAU,gCAKd,CAAC;AAEH,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,wBAAgB,aAAa,IAAI,eAAe,CAE/C"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
const RteContext = React.createContext({
|
|
5
|
+
editor: null,
|
|
6
|
+
shouldAnimate: true,
|
|
7
|
+
disabled: false,
|
|
8
|
+
readOnly: false
|
|
9
|
+
});
|
|
10
|
+
function useRteContext() {
|
|
11
|
+
return React.useContext(RteContext);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { RteContext, useRteContext };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { RichTextEditorProps } from './RichTextEditor.types';
|
|
3
|
+
export declare function RichTextEditor({ value, defaultValue, onChange, onChangeJson, onFocus, onBlur, placeholder, maxCharacters, showCharacterCount, showWordCount, toolbar, toolbarClassName, disabled, readOnly, className, editorClassName, style, id, motion: motionProp, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, }: RichTextEditorProps): React.JSX.Element;
|
|
4
|
+
//# sourceMappingURL=RichTextEditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RichTextEditor.d.ts","sourceRoot":"","sources":["../../../src/components/RichTextEditor/RichTextEditor.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAW/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAElE,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,MAAM,EACN,WAAW,EACX,aAAa,EACb,kBAA0B,EAC1B,aAAqB,EACrB,OAAc,EACd,gBAAgB,EAChB,QAAgB,EAChB,QAAgB,EAChB,SAAS,EACT,eAAe,EACf,KAAK,EACL,EAAE,EACF,MAAM,EAAE,UAAU,EAClB,YAAY,EAAE,SAAS,EACvB,iBAAiB,EAAE,cAAc,EACjC,kBAAkB,EAAE,eAAe,GACpC,EAAE,mBAAmB,qBAoIrB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Extensions } from '@tiptap/core';
|
|
2
|
+
interface EditorExtensionConfig {
|
|
3
|
+
placeholder?: string;
|
|
4
|
+
maxCharacters?: number;
|
|
5
|
+
/** When true, loads CharacterCount extension even without a hard limit */
|
|
6
|
+
showCount?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Full extension set for the interactive editor.
|
|
10
|
+
* TextAlign must come after StarterKit to correctly extend paragraph/heading nodes.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getEditorExtensions(config?: EditorExtensionConfig): Extensions;
|
|
13
|
+
/**
|
|
14
|
+
* Minimal extension set for SSR/viewer use with generateHTML.
|
|
15
|
+
* Excludes extensions that require browser APIs (Placeholder, CharacterCount).
|
|
16
|
+
*/
|
|
17
|
+
export declare function getViewerExtensions(): Extensions;
|
|
18
|
+
/**
|
|
19
|
+
* TipTap can emit several representations of an empty document.
|
|
20
|
+
* Strip all <br> variants and whitespace, then check for a single empty paragraph.
|
|
21
|
+
*/
|
|
22
|
+
export declare function normalizeHtml(html: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Normalise a user-typed URL: add https:// if no scheme and not a relative path.
|
|
25
|
+
*/
|
|
26
|
+
export declare function normalizeUrl(url: string): string;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=RichTextEditor.helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RichTextEditor.helpers.d.ts","sourceRoot":"","sources":["../../../src/components/RichTextEditor/RichTextEditor.helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAQ/C,UAAU,qBAAqB;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,GAAE,qBAA0B,GAAG,UAAU,CAqBlF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,UAAU,CAUhD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIlD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMhD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
2
|
+
import Underline from '@tiptap/extension-underline';
|
|
3
|
+
import Link from '@tiptap/extension-link';
|
|
4
|
+
import TextAlign from '@tiptap/extension-text-align';
|
|
5
|
+
import Placeholder from '@tiptap/extension-placeholder';
|
|
6
|
+
import CharacterCount from '@tiptap/extension-character-count';
|
|
7
|
+
|
|
8
|
+
function getEditorExtensions(config = {}) {
|
|
9
|
+
return [
|
|
10
|
+
StarterKit.configure({
|
|
11
|
+
codeBlock: false,
|
|
12
|
+
heading: { levels: [2, 3] }
|
|
13
|
+
}),
|
|
14
|
+
Underline,
|
|
15
|
+
Link.configure({
|
|
16
|
+
openOnClick: false,
|
|
17
|
+
autolink: true,
|
|
18
|
+
HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" }
|
|
19
|
+
}),
|
|
20
|
+
TextAlign.configure({ types: ["heading", "paragraph"] }),
|
|
21
|
+
Placeholder.configure({
|
|
22
|
+
placeholder: config.placeholder ?? "Start writing…",
|
|
23
|
+
showOnlyWhenEditable: true
|
|
24
|
+
}),
|
|
25
|
+
...config.showCount || config.maxCharacters !== void 0 ? [CharacterCount.configure({ limit: config.maxCharacters })] : []
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
function getViewerExtensions() {
|
|
29
|
+
return [
|
|
30
|
+
StarterKit.configure({
|
|
31
|
+
codeBlock: false,
|
|
32
|
+
heading: { levels: [2, 3] }
|
|
33
|
+
}),
|
|
34
|
+
Underline,
|
|
35
|
+
Link.configure({ openOnClick: false }),
|
|
36
|
+
TextAlign.configure({ types: ["heading", "paragraph"] })
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
function normalizeHtml(html) {
|
|
40
|
+
const stripped = html.replace(/<br\s*\/?>/gi, "").replace(/\s/g, "");
|
|
41
|
+
if (stripped === "<p></p>" || stripped === "") return "";
|
|
42
|
+
return html;
|
|
43
|
+
}
|
|
44
|
+
function normalizeUrl(url) {
|
|
45
|
+
const trimmed = url.trim();
|
|
46
|
+
if (!trimmed) return "";
|
|
47
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("#")) return trimmed;
|
|
48
|
+
if (/^https?:\/\//i.test(trimmed)) return trimmed;
|
|
49
|
+
return `https://${trimmed}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { getEditorExtensions, getViewerExtensions, normalizeHtml, normalizeUrl };
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { useEditor, EditorContent } from '@tiptap/react';
|
|
5
|
+
import { cn } from '@timmbr/utils';
|
|
6
|
+
import { resolveMotion } from '../../types/motion.js';
|
|
7
|
+
import { RteContext } from './RichTextEditor.context.js';
|
|
8
|
+
import { proseEditorStyles, editorWrapperVariants } from './RichTextEditor.styles.js';
|
|
9
|
+
import { getEditorExtensions, normalizeHtml } from './RichTextEditor.helpers.js';
|
|
10
|
+
import { RteToolbar } from './subcomponents/RteToolbar.js';
|
|
11
|
+
import { RteCharCount } from './subcomponents/RteCharCount.js';
|
|
12
|
+
import { useGlobalAnimation } from '../../providers/AnimationProvider.js';
|
|
13
|
+
|
|
14
|
+
function RichTextEditor({
|
|
15
|
+
value,
|
|
16
|
+
defaultValue,
|
|
17
|
+
onChange,
|
|
18
|
+
onChangeJson,
|
|
19
|
+
onFocus,
|
|
20
|
+
onBlur,
|
|
21
|
+
placeholder,
|
|
22
|
+
maxCharacters,
|
|
23
|
+
showCharacterCount = false,
|
|
24
|
+
showWordCount = false,
|
|
25
|
+
toolbar = true,
|
|
26
|
+
toolbarClassName,
|
|
27
|
+
disabled = false,
|
|
28
|
+
readOnly = false,
|
|
29
|
+
className,
|
|
30
|
+
editorClassName,
|
|
31
|
+
style,
|
|
32
|
+
id,
|
|
33
|
+
motion: motionProp,
|
|
34
|
+
"aria-label": ariaLabel,
|
|
35
|
+
"aria-labelledby": ariaLabelledby,
|
|
36
|
+
"aria-describedby": ariaDescribedby
|
|
37
|
+
}) {
|
|
38
|
+
const globalMotion = useGlobalAnimation();
|
|
39
|
+
const { shouldAnimate, motionClass, dataAttributes } = resolveMotion(motionProp, globalMotion);
|
|
40
|
+
const isControlled = value !== void 0;
|
|
41
|
+
const lastEmittedHtmlRef = React.useRef(
|
|
42
|
+
isControlled ? value ?? "" : defaultValue ?? ""
|
|
43
|
+
);
|
|
44
|
+
const onChangeRef = React.useRef(onChange);
|
|
45
|
+
const onChangeJsonRef = React.useRef(onChangeJson);
|
|
46
|
+
const onFocusRef = React.useRef(onFocus);
|
|
47
|
+
const onBlurRef = React.useRef(onBlur);
|
|
48
|
+
React.useEffect(() => {
|
|
49
|
+
onChangeRef.current = onChange;
|
|
50
|
+
onChangeJsonRef.current = onChangeJson;
|
|
51
|
+
onFocusRef.current = onFocus;
|
|
52
|
+
onBlurRef.current = onBlur;
|
|
53
|
+
});
|
|
54
|
+
const [mounted, setMounted] = React.useState(false);
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
setMounted(true);
|
|
57
|
+
}, []);
|
|
58
|
+
const extensions = React.useMemo(
|
|
59
|
+
() => getEditorExtensions({
|
|
60
|
+
placeholder,
|
|
61
|
+
maxCharacters,
|
|
62
|
+
showCount: showCharacterCount || showWordCount
|
|
63
|
+
}),
|
|
64
|
+
// intentionally omit showCharacterCount/showWordCount from deps: extensions are
|
|
65
|
+
// frozen at mount and cannot be hot-swapped; changing count visibility after mount
|
|
66
|
+
// requires remounting the editor, which is not a supported use-case.
|
|
67
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
68
|
+
[placeholder, maxCharacters]
|
|
69
|
+
);
|
|
70
|
+
const editor = useEditor({
|
|
71
|
+
immediatelyRender: false,
|
|
72
|
+
// SSR safety — defers DOM init to useEffect
|
|
73
|
+
editable: !disabled && !readOnly,
|
|
74
|
+
content: isControlled ? value ?? "" : defaultValue ?? "",
|
|
75
|
+
extensions,
|
|
76
|
+
onUpdate: ({ editor: e }) => {
|
|
77
|
+
const html = normalizeHtml(e.getHTML());
|
|
78
|
+
const json = e.getJSON();
|
|
79
|
+
lastEmittedHtmlRef.current = html;
|
|
80
|
+
onChangeRef.current?.(html);
|
|
81
|
+
onChangeJsonRef.current?.(json);
|
|
82
|
+
},
|
|
83
|
+
onFocus: () => {
|
|
84
|
+
onFocusRef.current?.();
|
|
85
|
+
},
|
|
86
|
+
onBlur: () => {
|
|
87
|
+
onBlurRef.current?.();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
if (!editor || !isControlled || value === void 0) return;
|
|
92
|
+
if (value === lastEmittedHtmlRef.current) return;
|
|
93
|
+
editor.commands.setContent(value, false);
|
|
94
|
+
lastEmittedHtmlRef.current = value;
|
|
95
|
+
}, [value, editor]);
|
|
96
|
+
React.useEffect(() => {
|
|
97
|
+
if (!editor) return;
|
|
98
|
+
editor.setEditable(!disabled && !readOnly);
|
|
99
|
+
}, [editor, disabled, readOnly]);
|
|
100
|
+
const showCount = showCharacterCount || showWordCount;
|
|
101
|
+
const contextValue = React.useMemo(
|
|
102
|
+
() => ({ editor, shouldAnimate, disabled, readOnly }),
|
|
103
|
+
[editor, shouldAnimate, disabled, readOnly]
|
|
104
|
+
);
|
|
105
|
+
return /* @__PURE__ */ jsx(RteContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
|
|
106
|
+
"div",
|
|
107
|
+
{
|
|
108
|
+
"data-slot": "rte",
|
|
109
|
+
style,
|
|
110
|
+
className: cn(
|
|
111
|
+
editorWrapperVariants({ disabled }),
|
|
112
|
+
motionClass,
|
|
113
|
+
className
|
|
114
|
+
),
|
|
115
|
+
...dataAttributes,
|
|
116
|
+
children: [
|
|
117
|
+
toolbar && /* @__PURE__ */ jsx(
|
|
118
|
+
RteToolbar,
|
|
119
|
+
{
|
|
120
|
+
className: cn(
|
|
121
|
+
shouldAnimate && mounted ? "animate-in fade-in duration-150" : void 0,
|
|
122
|
+
toolbarClassName
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
),
|
|
126
|
+
/* @__PURE__ */ jsx(
|
|
127
|
+
EditorContent,
|
|
128
|
+
{
|
|
129
|
+
editor,
|
|
130
|
+
id,
|
|
131
|
+
"aria-label": ariaLabel,
|
|
132
|
+
"aria-labelledby": ariaLabelledby,
|
|
133
|
+
"aria-describedby": ariaDescribedby,
|
|
134
|
+
"aria-multiline": "true",
|
|
135
|
+
"aria-readonly": readOnly || void 0,
|
|
136
|
+
className: cn(
|
|
137
|
+
proseEditorStyles,
|
|
138
|
+
"min-h-[120px] px-4 py-3 flex-1",
|
|
139
|
+
editorClassName
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
),
|
|
143
|
+
showCount && /* @__PURE__ */ jsx(
|
|
144
|
+
RteCharCount,
|
|
145
|
+
{
|
|
146
|
+
maxCharacters,
|
|
147
|
+
showCharacterCount,
|
|
148
|
+
showWordCount
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
) });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export { RichTextEditor };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const editorWrapperVariants: (props?: ({
|
|
2
|
+
disabled?: boolean | null | undefined;
|
|
3
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
4
|
+
export declare const toolbarVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
5
|
+
export declare const charCountVariants: (props?: ({
|
|
6
|
+
overLimit?: boolean | null | undefined;
|
|
7
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
|
+
export declare const proseEditorStyles: string;
|
|
9
|
+
export declare const proseViewerStyles: string;
|
|
10
|
+
//# sourceMappingURL=RichTextEditor.styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RichTextEditor.styles.d.ts","sourceRoot":"","sources":["../../../src/components/RichTextEditor/RichTextEditor.styles.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,qBAAqB;;8EAejC,CAAC;AAIF,eAAO,MAAM,eAAe,oFAE3B,CAAC;AAIF,eAAO,MAAM,iBAAiB;;8EAW7B,CAAC;AAKF,eAAO,MAAM,iBAAiB,QAgC7B,CAAC;AAKF,eAAO,MAAM,iBAAiB,QAyB7B,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { cva } from 'class-variance-authority';
|
|
2
|
+
import { cn } from '@timmbr/utils';
|
|
3
|
+
|
|
4
|
+
const editorWrapperVariants = cva(
|
|
5
|
+
[
|
|
6
|
+
"flex flex-col w-full rounded-md border border-border bg-card text-foreground font-sans",
|
|
7
|
+
"transition-shadow duration-150",
|
|
8
|
+
"focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-1"
|
|
9
|
+
],
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
disabled: {
|
|
13
|
+
true: "opacity-50 pointer-events-none select-none",
|
|
14
|
+
false: ""
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: { disabled: false }
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
const toolbarVariants = cva(
|
|
21
|
+
"flex items-center flex-wrap gap-0.5 px-2 py-1.5 border-b border-border bg-card rounded-t-[2px]"
|
|
22
|
+
);
|
|
23
|
+
const charCountVariants = cva(
|
|
24
|
+
"flex items-center justify-end gap-3 px-3 py-1.5 border-t border-border text-caption text-muted-foreground select-none rounded-b-[2px]",
|
|
25
|
+
{
|
|
26
|
+
variants: {
|
|
27
|
+
overLimit: {
|
|
28
|
+
true: "text-destructive",
|
|
29
|
+
false: ""
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: { overLimit: false }
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
const proseEditorStyles = cn(
|
|
36
|
+
// Editor focus reset
|
|
37
|
+
"[&_.ProseMirror]:outline-none",
|
|
38
|
+
"[&_.ProseMirror]:cursor-text",
|
|
39
|
+
// Paragraph
|
|
40
|
+
"[&_.ProseMirror_p]:text-body-1 [&_.ProseMirror_p]:text-foreground [&_.ProseMirror_p]:my-2 [&_.ProseMirror_p]:leading-relaxed",
|
|
41
|
+
// Headings — DS display font + scale
|
|
42
|
+
"[&_.ProseMirror_h2]:font-display [&_.ProseMirror_h2]:text-h2 [&_.ProseMirror_h2]:font-normal [&_.ProseMirror_h2]:text-foreground [&_.ProseMirror_h2]:my-4 [&_.ProseMirror_h2]:leading-tight",
|
|
43
|
+
"[&_.ProseMirror_h3]:font-display [&_.ProseMirror_h3]:text-h3 [&_.ProseMirror_h3]:font-normal [&_.ProseMirror_h3]:text-foreground [&_.ProseMirror_h3]:my-3 [&_.ProseMirror_h3]:leading-tight",
|
|
44
|
+
// Lists
|
|
45
|
+
"[&_.ProseMirror_ul]:list-disc [&_.ProseMirror_ul]:pl-6 [&_.ProseMirror_ul]:my-2",
|
|
46
|
+
"[&_.ProseMirror_ol]:list-decimal [&_.ProseMirror_ol]:pl-6 [&_.ProseMirror_ol]:my-2",
|
|
47
|
+
"[&_.ProseMirror_li]:text-body-1 [&_.ProseMirror_li]:text-foreground [&_.ProseMirror_li]:mb-1",
|
|
48
|
+
"[&_.ProseMirror_li_p]:my-0",
|
|
49
|
+
// Blockquote
|
|
50
|
+
"[&_.ProseMirror_blockquote]:border-l-4 [&_.ProseMirror_blockquote]:border-primary [&_.ProseMirror_blockquote]:pl-4 [&_.ProseMirror_blockquote]:italic [&_.ProseMirror_blockquote]:text-muted-foreground [&_.ProseMirror_blockquote]:my-3",
|
|
51
|
+
// Inline marks
|
|
52
|
+
"[&_.ProseMirror_strong]:font-semibold",
|
|
53
|
+
"[&_.ProseMirror_em]:italic",
|
|
54
|
+
"[&_.ProseMirror_u]:underline [&_.ProseMirror_u]:underline-offset-2",
|
|
55
|
+
"[&_.ProseMirror_s]:line-through",
|
|
56
|
+
// Links
|
|
57
|
+
"[&_.ProseMirror_a]:text-primary [&_.ProseMirror_a]:underline [&_.ProseMirror_a]:underline-offset-2 [&_.ProseMirror_a]:cursor-pointer [&_.ProseMirror_a]:transition-opacity [&_.ProseMirror_a:hover]:opacity-80",
|
|
58
|
+
// Horizontal rule
|
|
59
|
+
"[&_.ProseMirror_hr]:border-t [&_.ProseMirror_hr]:border-border [&_.ProseMirror_hr]:my-6",
|
|
60
|
+
// Placeholder via TipTap's .is-empty class on the paragraph node
|
|
61
|
+
"[&_.ProseMirror_p.is-empty:first-child::before]:content-[attr(data-placeholder)] [&_.ProseMirror_p.is-empty:first-child::before]:text-muted-foreground [&_.ProseMirror_p.is-empty:first-child::before]:pointer-events-none [&_.ProseMirror_p.is-empty:first-child::before]:float-left [&_.ProseMirror_p.is-empty:first-child::before]:h-0",
|
|
62
|
+
// Selected text
|
|
63
|
+
"[&_.ProseMirror_::selection]:bg-primary/20",
|
|
64
|
+
// First/last margin reset so the editor doesn't have extra outer gap
|
|
65
|
+
"[&_.ProseMirror_>*:first-child]:mt-0",
|
|
66
|
+
"[&_.ProseMirror_>*:last-child]:mb-0"
|
|
67
|
+
);
|
|
68
|
+
const proseViewerStyles = cn(
|
|
69
|
+
// Paragraph
|
|
70
|
+
"[&_p]:text-body-1 [&_p]:text-foreground [&_p]:my-2 [&_p]:leading-relaxed",
|
|
71
|
+
// Headings
|
|
72
|
+
"[&_h2]:font-display [&_h2]:text-h2 [&_h2]:font-normal [&_h2]:text-foreground [&_h2]:my-4 [&_h2]:leading-tight",
|
|
73
|
+
"[&_h3]:font-display [&_h3]:text-h3 [&_h3]:font-normal [&_h3]:text-foreground [&_h3]:my-3 [&_h3]:leading-tight",
|
|
74
|
+
// Lists
|
|
75
|
+
"[&_ul]:list-disc [&_ul]:pl-6 [&_ul]:my-2",
|
|
76
|
+
"[&_ol]:list-decimal [&_ol]:pl-6 [&_ol]:my-2",
|
|
77
|
+
"[&_li]:text-body-1 [&_li]:text-foreground [&_li]:mb-1",
|
|
78
|
+
"[&_li_p]:my-0",
|
|
79
|
+
// Blockquote
|
|
80
|
+
"[&_blockquote]:border-l-4 [&_blockquote]:border-primary [&_blockquote]:pl-4 [&_blockquote]:italic [&_blockquote]:text-muted-foreground [&_blockquote]:my-3",
|
|
81
|
+
// Inline marks
|
|
82
|
+
"[&_strong]:font-semibold",
|
|
83
|
+
"[&_em]:italic",
|
|
84
|
+
"[&_u]:underline [&_u]:underline-offset-2",
|
|
85
|
+
"[&_s]:line-through",
|
|
86
|
+
// Links
|
|
87
|
+
"[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-2 [&_a]:cursor-pointer [&_a]:transition-opacity [&_a:hover]:opacity-80",
|
|
88
|
+
// Horizontal rule
|
|
89
|
+
"[&_hr]:border-t [&_hr]:border-border [&_hr]:my-6",
|
|
90
|
+
// First/last margin reset
|
|
91
|
+
"[&_>*:first-child]:mt-0",
|
|
92
|
+
"[&_>*:last-child]:mb-0"
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
export { charCountVariants, editorWrapperVariants, proseEditorStyles, proseViewerStyles, toolbarVariants };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
import type { Editor, JSONContent } from '@tiptap/core';
|
|
3
|
+
import type { MotionProps } from '../../types/motion';
|
|
4
|
+
export interface RichTextEditorProps extends MotionProps {
|
|
5
|
+
/** Controlled HTML string value */
|
|
6
|
+
value?: string;
|
|
7
|
+
/** Uncontrolled initial HTML string */
|
|
8
|
+
defaultValue?: string;
|
|
9
|
+
/** Called with the HTML string on every content change */
|
|
10
|
+
onChange?: (html: string) => void;
|
|
11
|
+
/** Called with TipTap's JSON document on every content change */
|
|
12
|
+
onChangeJson?: (json: JSONContent) => void;
|
|
13
|
+
onFocus?: () => void;
|
|
14
|
+
onBlur?: () => void;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
/** Hard character limit; enables the CharacterCount extension limit */
|
|
17
|
+
maxCharacters?: number;
|
|
18
|
+
/** Show character count footer */
|
|
19
|
+
showCharacterCount?: boolean;
|
|
20
|
+
/** Show word count in the footer */
|
|
21
|
+
showWordCount?: boolean;
|
|
22
|
+
/** Show the toolbar — default true */
|
|
23
|
+
toolbar?: boolean;
|
|
24
|
+
toolbarClassName?: string;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
readOnly?: boolean;
|
|
27
|
+
className?: string;
|
|
28
|
+
editorClassName?: string;
|
|
29
|
+
style?: React.CSSProperties;
|
|
30
|
+
id?: string;
|
|
31
|
+
'aria-label'?: string;
|
|
32
|
+
'aria-labelledby'?: string;
|
|
33
|
+
'aria-describedby'?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface RichTextContentProps {
|
|
36
|
+
/** Raw HTML string to render */
|
|
37
|
+
content: string;
|
|
38
|
+
className?: string;
|
|
39
|
+
style?: React.CSSProperties;
|
|
40
|
+
}
|
|
41
|
+
export interface RichTextJsonContentProps {
|
|
42
|
+
/** TipTap JSONContent document to render */
|
|
43
|
+
content: JSONContent;
|
|
44
|
+
className?: string;
|
|
45
|
+
style?: React.CSSProperties;
|
|
46
|
+
}
|
|
47
|
+
export interface RteContextValue {
|
|
48
|
+
editor: Editor | null;
|
|
49
|
+
shouldAnimate: boolean;
|
|
50
|
+
disabled: boolean;
|
|
51
|
+
readOnly: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface RteToolbarButtonProps {
|
|
54
|
+
/** Tooltip label and aria-label */
|
|
55
|
+
label: string;
|
|
56
|
+
icon: React.ReactNode;
|
|
57
|
+
onClick: () => void;
|
|
58
|
+
isActive?: boolean;
|
|
59
|
+
disabled?: boolean;
|
|
60
|
+
className?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface RteToolbarGroupProps {
|
|
63
|
+
children: React.ReactNode;
|
|
64
|
+
/** Show leading vertical divider — default true */
|
|
65
|
+
showLeadingDivider?: boolean;
|
|
66
|
+
}
|
|
67
|
+
export interface RteCharCountProps {
|
|
68
|
+
maxCharacters?: number;
|
|
69
|
+
showCharacterCount?: boolean;
|
|
70
|
+
showWordCount?: boolean;
|
|
71
|
+
className?: string;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=RichTextEditor.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RichTextEditor.types.d.ts","sourceRoot":"","sources":["../../../src/components/RichTextEditor/RichTextEditor.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,iEAAiE;IACjE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IAC3C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,oCAAoC;IACpC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sCAAsC;IACtC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACvC,4CAA4C;IAC5C,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,mDAAmD;IACnD,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { RichTextJsonContentProps } from './RichTextEditor.types';
|
|
3
|
+
/**
|
|
4
|
+
* SSR-safe viewer for TipTap JSONContent.
|
|
5
|
+
* Uses generateHTML from @tiptap/core — a pure function with no browser dependency.
|
|
6
|
+
* Falls back silently on malformed input.
|
|
7
|
+
*/
|
|
8
|
+
export declare function RichTextJsonContent({ content, className, style }: RichTextJsonContentProps): React.JSX.Element | null;
|
|
9
|
+
//# sourceMappingURL=RichTextJsonContent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RichTextJsonContent.d.ts","sourceRoot":"","sources":["../../../src/components/RichTextEditor/RichTextJsonContent.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAEvE;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,wBAAwB,4BAqB1F"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { generateHTML } from '@tiptap/core';
|
|
3
|
+
import { cn } from '@timmbr/utils';
|
|
4
|
+
import { proseViewerStyles } from './RichTextEditor.styles.js';
|
|
5
|
+
import { getViewerExtensions } from './RichTextEditor.helpers.js';
|
|
6
|
+
|
|
7
|
+
function RichTextJsonContent({ content, className, style }) {
|
|
8
|
+
if (!content) return null;
|
|
9
|
+
let html = "";
|
|
10
|
+
try {
|
|
11
|
+
html = generateHTML(content, getViewerExtensions());
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
if (!html) return null;
|
|
16
|
+
return /* @__PURE__ */ jsx(
|
|
17
|
+
"div",
|
|
18
|
+
{
|
|
19
|
+
"data-slot": "rte-json-content",
|
|
20
|
+
className: cn(proseViewerStyles, className),
|
|
21
|
+
style,
|
|
22
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { RichTextJsonContent };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './RichTextEditor';
|
|
2
|
+
export * from './RichTextContent';
|
|
3
|
+
export * from './RichTextJsonContent';
|
|
4
|
+
export * from './RichTextEditor.types';
|
|
5
|
+
export * from './RichTextEditor.styles';
|
|
6
|
+
export * from './RichTextEditor.helpers';
|
|
7
|
+
export * from './RichTextEditor.context';
|
|
8
|
+
export * from './subcomponents/RteToolbar';
|
|
9
|
+
export * from './subcomponents/RteToolbarButton';
|
|
10
|
+
export * from './subcomponents/RteToolbarGroup';
|
|
11
|
+
export * from './subcomponents/RteLinkPopover';
|
|
12
|
+
export * from './subcomponents/RteEmojiPicker';
|
|
13
|
+
export * from './subcomponents/RteCharCount';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/RichTextEditor/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,iCAAiC,CAAC;AAChD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { RteCharCountProps } from '../RichTextEditor.types';
|
|
3
|
+
export declare function RteCharCount({ maxCharacters, showCharacterCount, showWordCount, className, }: RteCharCountProps): React.JSX.Element | null;
|
|
4
|
+
//# sourceMappingURL=RteCharCount.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RteCharCount.d.ts","sourceRoot":"","sources":["../../../../src/components/RichTextEditor/subcomponents/RteCharCount.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAOjE,wBAAgB,YAAY,CAAC,EAC3B,aAAa,EACb,kBAAyB,EACzB,aAAqB,EACrB,SAAS,GACV,EAAE,iBAAiB,4BA0BnB"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { cn } from '@timmbr/utils';
|
|
4
|
+
import { useRteContext } from '../RichTextEditor.context.js';
|
|
5
|
+
import { charCountVariants } from '../RichTextEditor.styles.js';
|
|
6
|
+
|
|
7
|
+
function RteCharCount({
|
|
8
|
+
maxCharacters,
|
|
9
|
+
showCharacterCount = true,
|
|
10
|
+
showWordCount = false,
|
|
11
|
+
className
|
|
12
|
+
}) {
|
|
13
|
+
const { editor } = useRteContext();
|
|
14
|
+
if (!editor) return null;
|
|
15
|
+
const storage = editor.storage.characterCount;
|
|
16
|
+
const charCount = storage?.characters?.() ?? 0;
|
|
17
|
+
const wordCount = storage?.words?.() ?? 0;
|
|
18
|
+
const isOverLimit = maxCharacters !== void 0 && charCount > maxCharacters;
|
|
19
|
+
return /* @__PURE__ */ jsxs("div", { className: cn(charCountVariants({ overLimit: isOverLimit }), className), children: [
|
|
20
|
+
showWordCount && /* @__PURE__ */ jsxs("span", { children: [
|
|
21
|
+
wordCount,
|
|
22
|
+
" ",
|
|
23
|
+
wordCount === 1 ? "word" : "words"
|
|
24
|
+
] }),
|
|
25
|
+
showCharacterCount && /* @__PURE__ */ jsxs("span", { children: [
|
|
26
|
+
charCount,
|
|
27
|
+
maxCharacters !== void 0 && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
28
|
+
" / ",
|
|
29
|
+
maxCharacters
|
|
30
|
+
] }),
|
|
31
|
+
" ",
|
|
32
|
+
"chars"
|
|
33
|
+
] })
|
|
34
|
+
] });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { RteCharCount };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RteEmojiPicker.d.ts","sourceRoot":"","sources":["../../../../src/components/RichTextEditor/subcomponents/RteEmojiPicker.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAqL/B,wBAAgB,cAAc,sBA0H7B"}
|