@timmbr/ui 1.14.0 โ†’ 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/components/RichTextEditor/RichTextContent.d.ts +8 -0
  2. package/dist/components/RichTextEditor/RichTextContent.d.ts.map +1 -0
  3. package/dist/components/RichTextEditor/RichTextContent.js +18 -0
  4. package/dist/components/RichTextEditor/RichTextEditor.context.d.ts +6 -0
  5. package/dist/components/RichTextEditor/RichTextEditor.context.d.ts.map +1 -0
  6. package/dist/components/RichTextEditor/RichTextEditor.context.js +14 -0
  7. package/dist/components/RichTextEditor/RichTextEditor.d.ts +4 -0
  8. package/dist/components/RichTextEditor/RichTextEditor.d.ts.map +1 -0
  9. package/dist/components/RichTextEditor/RichTextEditor.helpers.d.ts +28 -0
  10. package/dist/components/RichTextEditor/RichTextEditor.helpers.d.ts.map +1 -0
  11. package/dist/components/RichTextEditor/RichTextEditor.helpers.js +52 -0
  12. package/dist/components/RichTextEditor/RichTextEditor.js +156 -0
  13. package/dist/components/RichTextEditor/RichTextEditor.styles.d.ts +10 -0
  14. package/dist/components/RichTextEditor/RichTextEditor.styles.d.ts.map +1 -0
  15. package/dist/components/RichTextEditor/RichTextEditor.styles.js +95 -0
  16. package/dist/components/RichTextEditor/RichTextEditor.types.d.ts +73 -0
  17. package/dist/components/RichTextEditor/RichTextEditor.types.d.ts.map +1 -0
  18. package/dist/components/RichTextEditor/RichTextJsonContent.d.ts +9 -0
  19. package/dist/components/RichTextEditor/RichTextJsonContent.d.ts.map +1 -0
  20. package/dist/components/RichTextEditor/RichTextJsonContent.js +27 -0
  21. package/dist/components/RichTextEditor/index.d.ts +14 -0
  22. package/dist/components/RichTextEditor/index.d.ts.map +1 -0
  23. package/dist/components/RichTextEditor/subcomponents/RteCharCount.d.ts +4 -0
  24. package/dist/components/RichTextEditor/subcomponents/RteCharCount.d.ts.map +1 -0
  25. package/dist/components/RichTextEditor/subcomponents/RteCharCount.js +37 -0
  26. package/dist/components/RichTextEditor/subcomponents/RteEmojiPicker.d.ts +3 -0
  27. package/dist/components/RichTextEditor/subcomponents/RteEmojiPicker.d.ts.map +1 -0
  28. package/dist/components/RichTextEditor/subcomponents/RteEmojiPicker.js +274 -0
  29. package/dist/components/RichTextEditor/subcomponents/RteLinkPopover.d.ts +3 -0
  30. package/dist/components/RichTextEditor/subcomponents/RteLinkPopover.d.ts.map +1 -0
  31. package/dist/components/RichTextEditor/subcomponents/RteLinkPopover.js +93 -0
  32. package/dist/components/RichTextEditor/subcomponents/RteToolbar.d.ts +7 -0
  33. package/dist/components/RichTextEditor/subcomponents/RteToolbar.d.ts.map +1 -0
  34. package/dist/components/RichTextEditor/subcomponents/RteToolbar.js +234 -0
  35. package/dist/components/RichTextEditor/subcomponents/RteToolbarButton.d.ts +4 -0
  36. package/dist/components/RichTextEditor/subcomponents/RteToolbarButton.d.ts.map +1 -0
  37. package/dist/components/RichTextEditor/subcomponents/RteToolbarButton.js +37 -0
  38. package/dist/components/RichTextEditor/subcomponents/RteToolbarGroup.d.ts +4 -0
  39. package/dist/components/RichTextEditor/subcomponents/RteToolbarGroup.d.ts.map +1 -0
  40. package/dist/components/RichTextEditor/subcomponents/RteToolbarGroup.js +13 -0
  41. package/dist/index.d.ts +1 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +12 -0
  44. package/package.json +12 -4
@@ -0,0 +1,274 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import * as React from 'react';
4
+ import { SmilePlus } from '@timmbr/icons';
5
+ import { cn } from '@timmbr/utils';
6
+ import { useRteContext } from '../RichTextEditor.context.js';
7
+ import { Popover, PopoverTrigger, PopoverContent } from '../../Popover/Popover.js';
8
+ import { Tooltip } from '../../Tooltip/Tooltip.js';
9
+ import { Button } from '../../Button/Button.js';
10
+ import { Input } from '../../Input/Input.js';
11
+
12
+ const CATEGORIES = [
13
+ {
14
+ id: "smileys",
15
+ label: "Smileys",
16
+ entries: [
17
+ { e: "๐Ÿ˜Š", k: "smile happy" },
18
+ { e: "๐Ÿ˜‚", k: "laugh funny joy" },
19
+ { e: "๐Ÿฅฐ", k: "love adore hearts" },
20
+ { e: "๐Ÿ˜", k: "love eyes star" },
21
+ { e: "๐Ÿคฉ", k: "star struck amazing" },
22
+ { e: "๐Ÿ˜Ž", k: "cool sunglasses" },
23
+ { e: "๐Ÿฅณ", k: "party celebrate" },
24
+ { e: "๐Ÿค”", k: "thinking hmm" },
25
+ { e: "๐Ÿ˜…", k: "sweat nervous" },
26
+ { e: "๐Ÿ˜†", k: "grin laugh" },
27
+ { e: "๐Ÿ™‚", k: "slight smile" },
28
+ { e: "๐Ÿ˜‰", k: "wink" },
29
+ { e: "๐Ÿคฃ", k: "rolling laugh floor" },
30
+ { e: "๐Ÿ˜‡", k: "angel innocent" },
31
+ { e: "๐Ÿฅน", k: "tearful touched" },
32
+ { e: "๐Ÿค—", k: "hug hugging" },
33
+ { e: "๐Ÿ˜‹", k: "yum delicious" },
34
+ { e: "๐Ÿ™ƒ", k: "upside down silly" },
35
+ { e: "๐Ÿ˜ค", k: "triumph steam" },
36
+ { e: "๐Ÿฅบ", k: "pleading eyes cute" }
37
+ ]
38
+ },
39
+ {
40
+ id: "hands",
41
+ label: "Hands",
42
+ entries: [
43
+ { e: "๐Ÿ‘", k: "thumbs up good like" },
44
+ { e: "๐Ÿ‘Œ", k: "ok perfect" },
45
+ { e: "๐Ÿ™Œ", k: "raising hands celebrate" },
46
+ { e: "๐Ÿ’ช", k: "muscle strong flex" },
47
+ { e: "๐Ÿค", k: "handshake deal" },
48
+ { e: "๐Ÿ‘", k: "clap applause" },
49
+ { e: "๐Ÿ™", k: "pray thanks please" },
50
+ { e: "โœŒ๏ธ", k: "peace victory two" },
51
+ { e: "๐Ÿคž", k: "fingers crossed luck" },
52
+ { e: "๐Ÿซถ", k: "heart hands love" },
53
+ { e: "๐Ÿ‘‹", k: "wave hello hi" },
54
+ { e: "โ˜๏ธ", k: "point one up" },
55
+ { e: "๐Ÿค™", k: "call shaka hang loose" },
56
+ { e: "๐Ÿ‘†", k: "point up" },
57
+ { e: "โœ‹", k: "raised hand stop" },
58
+ { e: "๐Ÿ–๏ธ", k: "five fingers" }
59
+ ]
60
+ },
61
+ {
62
+ id: "hearts",
63
+ label: "Hearts",
64
+ entries: [
65
+ { e: "โค๏ธ", k: "red heart love" },
66
+ { e: "๐Ÿงก", k: "orange heart" },
67
+ { e: "๐Ÿ’›", k: "yellow heart" },
68
+ { e: "๐Ÿ’š", k: "green heart" },
69
+ { e: "๐Ÿ’™", k: "blue heart" },
70
+ { e: "๐Ÿ’œ", k: "purple heart" },
71
+ { e: "๐Ÿ–ค", k: "black heart" },
72
+ { e: "๐Ÿค", k: "white heart" },
73
+ { e: "๐Ÿ’•", k: "two hearts" },
74
+ { e: "๐Ÿ’–", k: "sparkling heart" },
75
+ { e: "๐Ÿ’—", k: "growing heart" },
76
+ { e: "๐Ÿ’“", k: "beating heart" },
77
+ { e: "๐Ÿ’", k: "heart ribbon" },
78
+ { e: "โค๏ธโ€๐Ÿ”ฅ", k: "heart fire passion" },
79
+ { e: "๐Ÿ’ž", k: "revolving hearts" },
80
+ { e: "๐Ÿ’˜", k: "heart arrow cupid" }
81
+ ]
82
+ },
83
+ {
84
+ id: "stars",
85
+ label: "Stars",
86
+ entries: [
87
+ { e: "โญ", k: "star gold" },
88
+ { e: "๐ŸŒŸ", k: "glowing star" },
89
+ { e: "โœจ", k: "sparkles magic" },
90
+ { e: "๐Ÿ’ซ", k: "dizzy star spin" },
91
+ { e: "๐Ÿ†", k: "trophy winner award" },
92
+ { e: "๐Ÿฅ‡", k: "gold medal first" },
93
+ { e: "๐ŸŽฏ", k: "bullseye target" },
94
+ { e: "๐Ÿ”ฅ", k: "fire hot trending" },
95
+ { e: "๐Ÿ’ฅ", k: "boom explosion" },
96
+ { e: "๐ŸŒˆ", k: "rainbow colors" },
97
+ { e: "๐ŸŽ‰", k: "party popper celebration" },
98
+ { e: "๐ŸŽŠ", k: "confetti ball party" },
99
+ { e: "๐Ÿš€", k: "rocket launch" },
100
+ { e: "๐Ÿ’ก", k: "lightbulb idea" }
101
+ ]
102
+ },
103
+ {
104
+ id: "nature",
105
+ label: "Nature",
106
+ entries: [
107
+ { e: "๐ŸŒธ", k: "cherry blossom flower pink" },
108
+ { e: "๐ŸŒบ", k: "hibiscus flower" },
109
+ { e: "๐ŸŒป", k: "sunflower yellow" },
110
+ { e: "๐ŸŒน", k: "rose red flower" },
111
+ { e: "๐ŸŒท", k: "tulip flower" },
112
+ { e: "๐ŸŒฟ", k: "herb leaf green" },
113
+ { e: "๐ŸŒฑ", k: "seedling sprout grow" },
114
+ { e: "๐ŸŒฒ", k: "tree pine evergreen" },
115
+ { e: "๐Ÿƒ", k: "leaves wind" },
116
+ { e: "๐ŸŒŠ", k: "wave ocean sea" },
117
+ { e: "๐Ÿ’ง", k: "droplet water" },
118
+ { e: "๐Ÿ€", k: "four leaf clover luck" },
119
+ { e: "๐Ÿชด", k: "potted plant" },
120
+ { e: "๐ŸŒ™", k: "moon night" },
121
+ { e: "โ˜€๏ธ", k: "sun sunny bright" },
122
+ { e: "๐ŸŒค๏ธ", k: "sun behind cloud" }
123
+ ]
124
+ },
125
+ {
126
+ id: "objects",
127
+ label: "Objects",
128
+ entries: [
129
+ { e: "๐ŸŽ", k: "gift present wrapped" },
130
+ { e: "๐Ÿ›๏ธ", k: "shopping bag store" },
131
+ { e: "๐Ÿ’Ž", k: "diamond gem jewel" },
132
+ { e: "๐Ÿท๏ธ", k: "label tag price" },
133
+ { e: "๐Ÿ”‘", k: "key unlock" },
134
+ { e: "๐Ÿช„", k: "magic wand" },
135
+ { e: "๐ŸŽจ", k: "art paint palette" },
136
+ { e: "๐Ÿ“ฆ", k: "box package delivery" },
137
+ { e: "๐Ÿงฟ", k: "eye protection amulet" },
138
+ { e: "๐Ÿ‘—", k: "dress fashion clothing" },
139
+ { e: "๐Ÿ‘Ÿ", k: "sneaker shoe running" },
140
+ { e: "๐Ÿ‘’", k: "hat fashion" },
141
+ { e: "๐Ÿ’", k: "ring engagement jewel" },
142
+ { e: "๐Ÿ‘œ", k: "handbag purse" },
143
+ { e: "๐Ÿ•ถ๏ธ", k: "sunglasses cool" },
144
+ { e: "๐Ÿงด", k: "lotion cream bottle" }
145
+ ]
146
+ },
147
+ {
148
+ id: "symbols",
149
+ label: "Symbols",
150
+ entries: [
151
+ { e: "โœ…", k: "check done complete" },
152
+ { e: "โŒ", k: "cross no wrong" },
153
+ { e: "๐Ÿ’ฏ", k: "hundred percent perfect" },
154
+ { e: "โ™ป๏ธ", k: "recycle eco sustainable" },
155
+ { e: "โžก๏ธ", k: "right arrow" },
156
+ { e: "โฌ†๏ธ", k: "up arrow" },
157
+ { e: "โฌ‡๏ธ", k: "down arrow" },
158
+ { e: "โฌ…๏ธ", k: "left arrow" },
159
+ { e: "๐Ÿ”", k: "top up above" },
160
+ { e: "๐Ÿ†•", k: "new" },
161
+ { e: "๐Ÿ†“", k: "free" },
162
+ { e: "๐Ÿ”ด", k: "red circle dot" },
163
+ { e: "๐ŸŸก", k: "yellow circle dot" },
164
+ { e: "๐ŸŸข", k: "green circle dot" },
165
+ { e: "๐Ÿ”ต", k: "blue circle dot" },
166
+ { e: "โšก", k: "lightning bolt fast energy" }
167
+ ]
168
+ }
169
+ ];
170
+ const ALL_ENTRIES = CATEGORIES.flatMap((c) => c.entries);
171
+ function RteEmojiPicker() {
172
+ const { editor, disabled, readOnly } = useRteContext();
173
+ const [open, setOpen] = React.useState(false);
174
+ const [search, setSearch] = React.useState("");
175
+ const [activeCategory, setActiveCategory] = React.useState("all");
176
+ const searchRef = React.useRef(null);
177
+ const isDisabled = disabled || readOnly || !editor;
178
+ const displayEmojis = React.useMemo(() => {
179
+ const pool = activeCategory === "all" ? ALL_ENTRIES : CATEGORIES.find((c) => c.id === activeCategory)?.entries ?? [];
180
+ if (!search.trim()) return pool.map((e) => e.e);
181
+ const q = search.toLowerCase();
182
+ return pool.filter((e) => e.k.includes(q) || e.e.includes(q)).map((e) => e.e);
183
+ }, [activeCategory, search]);
184
+ function handleSelect(emoji) {
185
+ editor?.chain().focus().insertContent(emoji).run();
186
+ setOpen(false);
187
+ }
188
+ function handleOpenChange(next) {
189
+ setOpen(next);
190
+ if (!next) {
191
+ setSearch("");
192
+ setActiveCategory("all");
193
+ }
194
+ }
195
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
196
+ /* @__PURE__ */ jsx(Tooltip, { content: "Insert emoji", side: "bottom", delayDuration: 400, children: /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
197
+ Button,
198
+ {
199
+ variant: "ghost",
200
+ size: "icon-sm",
201
+ "aria-label": "Insert emoji",
202
+ disabled: isDisabled,
203
+ className: "h-7 w-7 rounded-[2px] data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
204
+ children: /* @__PURE__ */ jsx(SmilePlus, { size: 14 })
205
+ }
206
+ ) }) }),
207
+ /* @__PURE__ */ jsx(
208
+ PopoverContent,
209
+ {
210
+ className: "w-72 max-w-[calc(100vw-1.5rem)] p-3",
211
+ side: "bottom",
212
+ align: "start",
213
+ onOpenAutoFocus: (e) => {
214
+ e.preventDefault();
215
+ searchRef.current?.focus();
216
+ },
217
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
218
+ /* @__PURE__ */ jsx(
219
+ Input,
220
+ {
221
+ ref: searchRef,
222
+ placeholder: "Search emojiโ€ฆ",
223
+ value: search,
224
+ onChange: (e) => setSearch(e.target.value),
225
+ size: "sm"
226
+ }
227
+ ),
228
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1", children: [
229
+ /* @__PURE__ */ jsx(
230
+ "button",
231
+ {
232
+ type: "button",
233
+ onClick: () => setActiveCategory("all"),
234
+ className: cn(
235
+ "rounded px-2 py-0.5 text-caption transition-colors outline-none",
236
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
237
+ activeCategory === "all" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
238
+ ),
239
+ children: "All"
240
+ }
241
+ ),
242
+ CATEGORIES.map((cat) => /* @__PURE__ */ jsx(
243
+ "button",
244
+ {
245
+ type: "button",
246
+ onClick: () => setActiveCategory(cat.id),
247
+ className: cn(
248
+ "rounded px-2 py-0.5 text-caption transition-colors outline-none",
249
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
250
+ activeCategory === cat.id ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
251
+ ),
252
+ children: cat.label
253
+ },
254
+ cat.id
255
+ ))
256
+ ] }),
257
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-[repeat(auto-fill,minmax(2rem,1fr))] gap-0.5 max-h-44 overflow-y-auto", children: displayEmojis.length > 0 ? displayEmojis.map((emoji) => /* @__PURE__ */ jsx(
258
+ "button",
259
+ {
260
+ type: "button",
261
+ onClick: () => handleSelect(emoji),
262
+ "aria-label": emoji,
263
+ className: "flex h-8 w-full items-center justify-center rounded hover:bg-accent transition-colors select-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 outline-none",
264
+ children: emoji
265
+ },
266
+ emoji
267
+ )) : /* @__PURE__ */ jsx("p", { className: "col-span-full text-center text-caption text-muted-foreground py-4", children: "No results" }) })
268
+ ] })
269
+ }
270
+ )
271
+ ] });
272
+ }
273
+
274
+ export { RteEmojiPicker };
@@ -0,0 +1,3 @@
1
+ import * as React from 'react';
2
+ export declare function RteLinkPopover(): React.JSX.Element;
3
+ //# sourceMappingURL=RteLinkPopover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RteLinkPopover.d.ts","sourceRoot":"","sources":["../../../../src/components/RichTextEditor/subcomponents/RteLinkPopover.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAS/B,wBAAgB,cAAc,sBAwF7B"}
@@ -0,0 +1,93 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import * as React from 'react';
4
+ import { Link2, Unlink2 } from '@timmbr/icons';
5
+ import { useRteContext } from '../RichTextEditor.context.js';
6
+ import { normalizeUrl } from '../RichTextEditor.helpers.js';
7
+ import { Popover, PopoverTrigger, PopoverContent } from '../../Popover/Popover.js';
8
+ import { Tooltip } from '../../Tooltip/Tooltip.js';
9
+ import { Button } from '../../Button/Button.js';
10
+ import { Input } from '../../Input/Input.js';
11
+
12
+ function RteLinkPopover() {
13
+ const { editor, disabled, readOnly } = useRteContext();
14
+ const [open, setOpen] = React.useState(false);
15
+ const [urlInput, setUrlInput] = React.useState("");
16
+ const isActive = editor?.isActive("link") ?? false;
17
+ const isDisabled = disabled || readOnly || !editor;
18
+ function handleOpenChange(nextOpen) {
19
+ if (nextOpen && editor) {
20
+ const existing = editor.getAttributes("link").href ?? "";
21
+ setUrlInput(existing);
22
+ } else {
23
+ setUrlInput("");
24
+ }
25
+ setOpen(nextOpen);
26
+ }
27
+ function handleSubmit(e) {
28
+ e.preventDefault();
29
+ if (!editor || !urlInput.trim()) return;
30
+ const href = normalizeUrl(urlInput);
31
+ editor.chain().focus().extendMarkRange("link").setLink({ href }).run();
32
+ setOpen(false);
33
+ }
34
+ function handleUnlink() {
35
+ editor?.chain().focus().extendMarkRange("link").unsetLink().run();
36
+ }
37
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
38
+ /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
39
+ /* @__PURE__ */ jsx(Tooltip, { content: "Insert link", side: "bottom", delayDuration: 400, children: /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
40
+ Button,
41
+ {
42
+ variant: "ghost",
43
+ size: "icon-sm",
44
+ "aria-label": "Insert link",
45
+ "aria-pressed": isActive,
46
+ disabled: isDisabled,
47
+ className: "h-7 w-7 rounded-[2px] data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
48
+ children: /* @__PURE__ */ jsx(Link2, { size: 14 })
49
+ }
50
+ ) }) }),
51
+ /* @__PURE__ */ jsx(PopoverContent, { className: "w-80 max-w-[calc(100vw-1.5rem)] p-3", side: "bottom", align: "start", children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-2", children: [
52
+ /* @__PURE__ */ jsx("p", { className: "text-body-2-semibold text-foreground mb-1", children: "Insert link" }),
53
+ /* @__PURE__ */ jsx(
54
+ Input,
55
+ {
56
+ placeholder: "https://example.com",
57
+ value: urlInput,
58
+ onChange: (e) => setUrlInput(e.target.value),
59
+ autoFocus: true,
60
+ size: "sm"
61
+ }
62
+ ),
63
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 justify-end", children: [
64
+ /* @__PURE__ */ jsx(
65
+ Button,
66
+ {
67
+ type: "button",
68
+ variant: "ghost",
69
+ size: "sm",
70
+ onClick: () => setOpen(false),
71
+ children: "Cancel"
72
+ }
73
+ ),
74
+ /* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", disabled: !urlInput.trim(), children: "Apply" })
75
+ ] })
76
+ ] }) })
77
+ ] }),
78
+ /* @__PURE__ */ jsx(Tooltip, { content: "Remove link", side: "bottom", delayDuration: 400, children: /* @__PURE__ */ jsx(
79
+ Button,
80
+ {
81
+ variant: "ghost",
82
+ size: "icon-sm",
83
+ "aria-label": "Remove link",
84
+ disabled: isDisabled || !isActive,
85
+ onClick: handleUnlink,
86
+ className: "h-7 w-7 rounded-[2px]",
87
+ children: /* @__PURE__ */ jsx(Unlink2, { size: 14 })
88
+ }
89
+ ) })
90
+ ] });
91
+ }
92
+
93
+ export { RteLinkPopover };
@@ -0,0 +1,7 @@
1
+ import * as React from 'react';
2
+ interface RteToolbarProps {
3
+ className?: string;
4
+ }
5
+ export declare function RteToolbar({ className }: RteToolbarProps): React.JSX.Element | null;
6
+ export {};
7
+ //# sourceMappingURL=RteToolbar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RteToolbar.d.ts","sourceRoot":"","sources":["../../../../src/components/RichTextEditor/subcomponents/RteToolbar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA+B/B,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAoBD,wBAAgB,UAAU,CAAC,EAAE,SAAS,EAAE,EAAE,eAAe,4BAwKxD"}
@@ -0,0 +1,234 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { cn } from '@timmbr/utils';
4
+ import { Undo2, Redo2, Pilcrow, Heading2, Heading3, Bold, Italic, Underline, Strikethrough, List, ListOrdered, Quote, AlignLeft, AlignCenter, AlignRight, AlignJustify, Minus, RemoveFormatting } from '@timmbr/icons';
5
+ import { useRteContext } from '../RichTextEditor.context.js';
6
+ import { toolbarVariants } from '../RichTextEditor.styles.js';
7
+ import { RteToolbarButton } from './RteToolbarButton.js';
8
+ import { RteToolbarGroup } from './RteToolbarGroup.js';
9
+ import { RteLinkPopover } from './RteLinkPopover.js';
10
+ import { RteEmojiPicker } from './RteEmojiPicker.js';
11
+
12
+ const ICON_SIZE = 14;
13
+ function handleToolbarKeyDown(e) {
14
+ if (!["ArrowRight", "ArrowLeft", "Home", "End"].includes(e.key)) return;
15
+ const toolbar = e.currentTarget;
16
+ const focusable = Array.from(
17
+ toolbar.querySelectorAll("button:not([disabled])")
18
+ );
19
+ const idx = focusable.indexOf(document.activeElement);
20
+ if (idx === -1) return;
21
+ e.preventDefault();
22
+ const len = focusable.length;
23
+ const next = e.key === "ArrowRight" ? focusable[(idx + 1) % len] : e.key === "ArrowLeft" ? focusable[(idx - 1 + len) % len] : e.key === "Home" ? focusable[0] : focusable[len - 1];
24
+ next?.focus();
25
+ }
26
+ function RteToolbar({ className }) {
27
+ const { editor, disabled, readOnly } = useRteContext();
28
+ if (!editor) return null;
29
+ const isDisabled = disabled || readOnly;
30
+ return /* @__PURE__ */ jsxs(
31
+ "div",
32
+ {
33
+ "data-slot": "rte-toolbar",
34
+ role: "toolbar",
35
+ "aria-label": "Text formatting",
36
+ onKeyDown: handleToolbarKeyDown,
37
+ className: cn(toolbarVariants(), className),
38
+ children: [
39
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
40
+ /* @__PURE__ */ jsx(
41
+ RteToolbarButton,
42
+ {
43
+ label: "Undo",
44
+ icon: /* @__PURE__ */ jsx(Undo2, { size: ICON_SIZE }),
45
+ onClick: () => editor.chain().focus().undo().run(),
46
+ disabled: isDisabled || !editor.can().undo()
47
+ }
48
+ ),
49
+ /* @__PURE__ */ jsx(
50
+ RteToolbarButton,
51
+ {
52
+ label: "Redo",
53
+ icon: /* @__PURE__ */ jsx(Redo2, { size: ICON_SIZE }),
54
+ onClick: () => editor.chain().focus().redo().run(),
55
+ disabled: isDisabled || !editor.can().redo()
56
+ }
57
+ )
58
+ ] }),
59
+ /* @__PURE__ */ jsxs(RteToolbarGroup, { children: [
60
+ /* @__PURE__ */ jsx(
61
+ RteToolbarButton,
62
+ {
63
+ label: "Paragraph",
64
+ icon: /* @__PURE__ */ jsx(Pilcrow, { size: ICON_SIZE }),
65
+ onClick: () => editor.chain().focus().setParagraph().run(),
66
+ isActive: editor.isActive("paragraph") && !editor.isActive("heading"),
67
+ disabled: isDisabled
68
+ }
69
+ ),
70
+ /* @__PURE__ */ jsx(
71
+ RteToolbarButton,
72
+ {
73
+ label: "Heading 2",
74
+ icon: /* @__PURE__ */ jsx(Heading2, { size: ICON_SIZE }),
75
+ onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
76
+ isActive: editor.isActive("heading", { level: 2 }),
77
+ disabled: isDisabled
78
+ }
79
+ ),
80
+ /* @__PURE__ */ jsx(
81
+ RteToolbarButton,
82
+ {
83
+ label: "Heading 3",
84
+ icon: /* @__PURE__ */ jsx(Heading3, { size: ICON_SIZE }),
85
+ onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
86
+ isActive: editor.isActive("heading", { level: 3 }),
87
+ disabled: isDisabled
88
+ }
89
+ )
90
+ ] }),
91
+ /* @__PURE__ */ jsxs(RteToolbarGroup, { children: [
92
+ /* @__PURE__ */ jsx(
93
+ RteToolbarButton,
94
+ {
95
+ label: "Bold",
96
+ icon: /* @__PURE__ */ jsx(Bold, { size: ICON_SIZE }),
97
+ onClick: () => editor.chain().focus().toggleBold().run(),
98
+ isActive: editor.isActive("bold"),
99
+ disabled: isDisabled
100
+ }
101
+ ),
102
+ /* @__PURE__ */ jsx(
103
+ RteToolbarButton,
104
+ {
105
+ label: "Italic",
106
+ icon: /* @__PURE__ */ jsx(Italic, { size: ICON_SIZE }),
107
+ onClick: () => editor.chain().focus().toggleItalic().run(),
108
+ isActive: editor.isActive("italic"),
109
+ disabled: isDisabled
110
+ }
111
+ ),
112
+ /* @__PURE__ */ jsx(
113
+ RteToolbarButton,
114
+ {
115
+ label: "Underline",
116
+ icon: /* @__PURE__ */ jsx(Underline, { size: ICON_SIZE }),
117
+ onClick: () => editor.chain().focus().toggleUnderline().run(),
118
+ isActive: editor.isActive("underline"),
119
+ disabled: isDisabled
120
+ }
121
+ ),
122
+ /* @__PURE__ */ jsx(
123
+ RteToolbarButton,
124
+ {
125
+ label: "Strikethrough",
126
+ icon: /* @__PURE__ */ jsx(Strikethrough, { size: ICON_SIZE }),
127
+ onClick: () => editor.chain().focus().toggleStrike().run(),
128
+ isActive: editor.isActive("strike"),
129
+ disabled: isDisabled
130
+ }
131
+ )
132
+ ] }),
133
+ /* @__PURE__ */ jsxs(RteToolbarGroup, { children: [
134
+ /* @__PURE__ */ jsx(
135
+ RteToolbarButton,
136
+ {
137
+ label: "Bullet list",
138
+ icon: /* @__PURE__ */ jsx(List, { size: ICON_SIZE }),
139
+ onClick: () => editor.chain().focus().toggleBulletList().run(),
140
+ isActive: editor.isActive("bulletList"),
141
+ disabled: isDisabled
142
+ }
143
+ ),
144
+ /* @__PURE__ */ jsx(
145
+ RteToolbarButton,
146
+ {
147
+ label: "Ordered list",
148
+ icon: /* @__PURE__ */ jsx(ListOrdered, { size: ICON_SIZE }),
149
+ onClick: () => editor.chain().focus().toggleOrderedList().run(),
150
+ isActive: editor.isActive("orderedList"),
151
+ disabled: isDisabled
152
+ }
153
+ ),
154
+ /* @__PURE__ */ jsx(
155
+ RteToolbarButton,
156
+ {
157
+ label: "Blockquote",
158
+ icon: /* @__PURE__ */ jsx(Quote, { size: ICON_SIZE }),
159
+ onClick: () => editor.chain().focus().toggleBlockquote().run(),
160
+ isActive: editor.isActive("blockquote"),
161
+ disabled: isDisabled
162
+ }
163
+ )
164
+ ] }),
165
+ /* @__PURE__ */ jsxs(RteToolbarGroup, { children: [
166
+ /* @__PURE__ */ jsx(
167
+ RteToolbarButton,
168
+ {
169
+ label: "Align left",
170
+ icon: /* @__PURE__ */ jsx(AlignLeft, { size: ICON_SIZE }),
171
+ onClick: () => editor.chain().focus().setTextAlign("left").run(),
172
+ isActive: editor.isActive({ textAlign: "left" }),
173
+ disabled: isDisabled
174
+ }
175
+ ),
176
+ /* @__PURE__ */ jsx(
177
+ RteToolbarButton,
178
+ {
179
+ label: "Align center",
180
+ icon: /* @__PURE__ */ jsx(AlignCenter, { size: ICON_SIZE }),
181
+ onClick: () => editor.chain().focus().setTextAlign("center").run(),
182
+ isActive: editor.isActive({ textAlign: "center" }),
183
+ disabled: isDisabled
184
+ }
185
+ ),
186
+ /* @__PURE__ */ jsx(
187
+ RteToolbarButton,
188
+ {
189
+ label: "Align right",
190
+ icon: /* @__PURE__ */ jsx(AlignRight, { size: ICON_SIZE }),
191
+ onClick: () => editor.chain().focus().setTextAlign("right").run(),
192
+ isActive: editor.isActive({ textAlign: "right" }),
193
+ disabled: isDisabled
194
+ }
195
+ ),
196
+ /* @__PURE__ */ jsx(
197
+ RteToolbarButton,
198
+ {
199
+ label: "Justify",
200
+ icon: /* @__PURE__ */ jsx(AlignJustify, { size: ICON_SIZE }),
201
+ onClick: () => editor.chain().focus().setTextAlign("justify").run(),
202
+ isActive: editor.isActive({ textAlign: "justify" }),
203
+ disabled: isDisabled
204
+ }
205
+ )
206
+ ] }),
207
+ /* @__PURE__ */ jsxs(RteToolbarGroup, { children: [
208
+ /* @__PURE__ */ jsx(RteLinkPopover, {}),
209
+ /* @__PURE__ */ jsx(RteEmojiPicker, {}),
210
+ /* @__PURE__ */ jsx(
211
+ RteToolbarButton,
212
+ {
213
+ label: "Horizontal rule",
214
+ icon: /* @__PURE__ */ jsx(Minus, { size: ICON_SIZE }),
215
+ onClick: () => editor.chain().focus().setHorizontalRule().run(),
216
+ disabled: isDisabled
217
+ }
218
+ )
219
+ ] }),
220
+ /* @__PURE__ */ jsx(RteToolbarGroup, { children: /* @__PURE__ */ jsx(
221
+ RteToolbarButton,
222
+ {
223
+ label: "Clear formatting",
224
+ icon: /* @__PURE__ */ jsx(RemoveFormatting, { size: ICON_SIZE }),
225
+ onClick: () => editor.chain().focus().clearNodes().unsetAllMarks().run(),
226
+ disabled: isDisabled
227
+ }
228
+ ) })
229
+ ]
230
+ }
231
+ );
232
+ }
233
+
234
+ export { RteToolbar };
@@ -0,0 +1,4 @@
1
+ import * as React from 'react';
2
+ import type { RteToolbarButtonProps } from '../RichTextEditor.types';
3
+ export declare function RteToolbarButton({ label, icon, onClick, isActive, disabled, className, }: RteToolbarButtonProps): React.JSX.Element;
4
+ //# sourceMappingURL=RteToolbarButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RteToolbarButton.d.ts","sourceRoot":"","sources":["../../../../src/components/RichTextEditor/subcomponents/RteToolbarButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,QAAgB,EAChB,SAAS,GACV,EAAE,qBAAqB,qBAuBvB"}
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { cn } from '@timmbr/utils';
4
+ import { useRteContext } from '../RichTextEditor.context.js';
5
+ import { Tooltip } from '../../Tooltip/Tooltip.js';
6
+ import { Button } from '../../Button/Button.js';
7
+
8
+ function RteToolbarButton({
9
+ label,
10
+ icon,
11
+ onClick,
12
+ isActive,
13
+ disabled = false,
14
+ className
15
+ }) {
16
+ const { shouldAnimate } = useRteContext();
17
+ return /* @__PURE__ */ jsx(Tooltip, { content: label, side: "bottom", delayDuration: 400, children: /* @__PURE__ */ jsx(
18
+ Button,
19
+ {
20
+ variant: "ghost",
21
+ size: "icon-sm",
22
+ "aria-label": label,
23
+ "aria-pressed": isActive !== void 0 ? isActive : void 0,
24
+ disabled,
25
+ onClick,
26
+ className: cn(
27
+ "h-7 w-7 rounded-[2px]",
28
+ isActive === true && "bg-accent text-accent-foreground",
29
+ shouldAnimate ? "active:scale-[0.92]" : "active:scale-100",
30
+ className
31
+ ),
32
+ children: icon
33
+ }
34
+ ) });
35
+ }
36
+
37
+ export { RteToolbarButton };
@@ -0,0 +1,4 @@
1
+ import * as React from 'react';
2
+ import type { RteToolbarGroupProps } from '../RichTextEditor.types';
3
+ export declare function RteToolbarGroup({ children, showLeadingDivider }: RteToolbarGroupProps): React.JSX.Element;
4
+ //# sourceMappingURL=RteToolbarGroup.d.ts.map