@skalfa/skalfa-component 1.0.25 → 1.0.27

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/index.d.ts CHANGED
@@ -25,6 +25,8 @@ export * from "./input/InputTime.component";
25
25
  export * from "./input/InputValues.component";
26
26
  export * from "./input/Radio.component";
27
27
  export * from "./input/Select.component";
28
+ export * from "./input/Textarea.component";
29
+ export * from "./input/InputContent.component";
28
30
  export * from "./modal/BottomSheet.component";
29
31
  export * from "./modal/FloatingPage.component";
30
32
  export * from "./modal/Modal.component";
@@ -47,6 +49,7 @@ export * from "./typography/TypographyArticle.component";
47
49
  export * from "./typography/TypographyColumn.component";
48
50
  export * from "./typography/TypographyContent.component";
49
51
  export * from "./typography/TypographyTips.component";
52
+ export * from "./wrap/ContentWrapper.component";
50
53
  export * from "./wrap/Draggable.component";
51
54
  export * from "./wrap/Image.component";
52
55
  export * from "./wrap/OutsideClick.component";
package/dist/index.js CHANGED
@@ -99,6 +99,13 @@ exports.RadioComponent = require("./input/Radio.component").RadioComponent;
99
99
  // Static export for ./input/Select.component
100
100
  exports.SelectComponent = require("./input/Select.component").SelectComponent;
101
101
 
102
+ // Static export for ./input/Textarea.component
103
+ exports.TextareaComponent = require("./input/Textarea.component").TextareaComponent;
104
+
105
+ // Static export for ./input/InputContent.component
106
+ exports.DEFAULT_TOOLBAR_CONTROLS = require("./input/InputContent.component").DEFAULT_TOOLBAR_CONTROLS;
107
+ exports.InputContentComponent = require("./input/InputContent.component").InputContentComponent;
108
+
102
109
  // Static export for ./modal/BottomSheet.component
103
110
  exports.BottomSheetComponent = require("./modal/BottomSheet.component").BottomSheetComponent;
104
111
 
@@ -167,6 +174,13 @@ exports.TypographyContentComponent = require("./typography/TypographyContent.com
167
174
  // Static export for ./typography/TypographyTips.component
168
175
  exports.TypographyTipsComponent = require("./typography/TypographyTips.component").TypographyTipsComponent;
169
176
 
177
+ // Static export for ./wrap/ContentWrapper.component
178
+ exports.COLOR_MAP = require("./wrap/ContentWrapper.component").COLOR_MAP;
179
+ exports.parseInlineFormats = require("./wrap/ContentWrapper.component").parseInlineFormats;
180
+ exports.parseContentToHtml = require("./wrap/ContentWrapper.component").parseContentToHtml;
181
+ exports.parseHtmlToContent = require("./wrap/ContentWrapper.component").parseHtmlToContent;
182
+ exports.ContentWrapperComponent = require("./wrap/ContentWrapper.component").ContentWrapperComponent;
183
+
170
184
  // Static export for ./wrap/Draggable.component
171
185
  exports.DraggableComponent = require("./wrap/Draggable.component").DraggableComponent;
172
186
 
@@ -0,0 +1,22 @@
1
+ import { ReactNode, Ref } from "react";
2
+ import { ValidationRules } from "@utils";
3
+ export type ToolbarControlName = "HEADER" | "BOLD" | "ITALIC" | "UNDERLINE" | "STRIKETHROUGH" | "TEXT_SIZE" | "FONT_SIZE" | "ALIGN_LEFT" | "ALIGN_CENTER" | "ALIGN_RIGHT" | "ALIGN_JUSTIFY" | "LINK" | "COLOR" | "LIST_BULLET" | "BULLET_LIST" | "LIST_NUMBER" | "NUMBER_LIST" | "DIVIDER" | "SEP";
4
+ export type ToolbarControlItem = ToolbarControlName | ReactNode;
5
+ export declare const DEFAULT_TOOLBAR_CONTROLS: ToolbarControlItem[];
6
+ export interface InputContentProps {
7
+ label?: string;
8
+ tip?: string | ReactNode;
9
+ name?: string;
10
+ placeholder?: string;
11
+ disabled?: boolean;
12
+ value?: string;
13
+ invalid?: string;
14
+ validations?: ValidationRules;
15
+ toolbarControl?: Array<ToolbarControlItem>;
16
+ onChange?: (value: string) => any;
17
+ register?: (name: string, validations?: ValidationRules) => void;
18
+ unregister?: (name: string) => void;
19
+ ref?: Ref<HTMLDivElement>;
20
+ className?: string;
21
+ }
22
+ export declare function InputContentComponent({ label, tip, className, value, invalid, validations, toolbarControl, register, unregister, onChange, ref, ...props }: InputContentProps): import("@types/react").JSX.Element;
@@ -0,0 +1,514 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.DEFAULT_TOOLBAR_CONTROLS = void 0;
5
+ exports.InputContentComponent = InputContentComponent;
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const _utils_1 = require("@utils");
9
+ const ContentWrapper_component_1 = require("../wrap/ContentWrapper.component");
10
+ const Button_component_1 = require("../button/Button.component");
11
+ exports.DEFAULT_TOOLBAR_CONTROLS = [
12
+ "HEADER",
13
+ "TEXT_SIZE",
14
+ "BOLD",
15
+ "ITALIC",
16
+ "UNDERLINE",
17
+ "STRIKETHROUGH",
18
+ "ALIGN_LEFT",
19
+ "ALIGN_CENTER",
20
+ "ALIGN_RIGHT",
21
+ "ALIGN_JUSTIFY",
22
+ "LINK",
23
+ "COLOR",
24
+ "LIST_BULLET",
25
+ "LIST_NUMBER",
26
+ "DIVIDER",
27
+ ];
28
+ function InputContentComponent({ label, tip, className = "", value, invalid, validations, toolbarControl, register, unregister, onChange, ref, ...props }) {
29
+ const inputHandler = (0, _utils_1.useInputHandler)(props.name, value, validations, register, false, unregister);
30
+ const randomId = (0, _utils_1.useInputRandomId)();
31
+ const [invalidMessage] = (0, _utils_1.useValidation)(inputHandler.value, validations, invalid, inputHandler.idle);
32
+ const editorRef = (0, react_1.useRef)(null);
33
+ const isInternalChange = (0, react_1.useRef)(false);
34
+ const [showColorPicker, setShowColorPicker] = (0, react_1.useState)(false);
35
+ const [showLinkInput, setShowLinkInput] = (0, react_1.useState)(false);
36
+ const [linkUrl, setLinkUrl] = (0, react_1.useState)("");
37
+ const savedSelectionRef = (0, react_1.useRef)(null);
38
+ const [textSize, setTextSize] = (0, react_1.useState)(14);
39
+ const [activeColor, setActiveColor] = (0, react_1.useState)("normal");
40
+ const [activeStates, setActiveStates] = (0, react_1.useState)({
41
+ header: false,
42
+ bold: false,
43
+ italic: false,
44
+ underline: false,
45
+ strikeThrough: false,
46
+ justifyLeft: false,
47
+ justifyCenter: false,
48
+ justifyRight: false,
49
+ justifyFull: false,
50
+ insertUnorderedList: false,
51
+ insertOrderedList: false,
52
+ });
53
+ (0, react_1.useEffect)(() => {
54
+ if (isInternalChange.current) {
55
+ isInternalChange.current = false;
56
+ return;
57
+ }
58
+ if (editorRef.current) {
59
+ const html = (0, ContentWrapper_component_1.parseContentToHtml)(inputHandler.value || "");
60
+ if (editorRef.current.innerHTML !== html) {
61
+ editorRef.current.innerHTML = html;
62
+ }
63
+ }
64
+ }, [inputHandler.value]);
65
+ const saveSelection = (0, react_1.useCallback)(() => {
66
+ const sel = window.getSelection();
67
+ if (sel && sel.rangeCount > 0) {
68
+ savedSelectionRef.current = sel.getRangeAt(0).cloneRange();
69
+ }
70
+ }, []);
71
+ const restoreSelection = (0, react_1.useCallback)(() => {
72
+ const sel = window.getSelection();
73
+ if (sel && savedSelectionRef.current) {
74
+ sel.removeAllRanges();
75
+ sel.addRange(savedSelectionRef.current);
76
+ }
77
+ }, []);
78
+ const handleEditorChange = (0, react_1.useCallback)(() => {
79
+ if (!editorRef.current)
80
+ return;
81
+ const html = editorRef.current.innerHTML;
82
+ const customFormat = (0, ContentWrapper_component_1.parseHtmlToContent)(html);
83
+ isInternalChange.current = true;
84
+ inputHandler.setValue(customFormat);
85
+ inputHandler.setIdle(false);
86
+ if (onChange)
87
+ onChange(customFormat);
88
+ }, [onChange, inputHandler]);
89
+ const execCommand = (0, react_1.useCallback)((command, value) => {
90
+ editorRef.current?.focus();
91
+ restoreSelection();
92
+ document.execCommand(command, false, value);
93
+ saveSelection();
94
+ handleEditorChange();
95
+ }, [restoreSelection, saveSelection, handleEditorChange]);
96
+ const isCommandActive = (0, react_1.useCallback)((command) => {
97
+ try {
98
+ return document.queryCommandState(command);
99
+ }
100
+ catch {
101
+ return false;
102
+ }
103
+ }, []);
104
+ const handleBold = (0, react_1.useCallback)(() => execCommand("bold"), [execCommand]);
105
+ const handleItalic = (0, react_1.useCallback)(() => execCommand("italic"), [execCommand]);
106
+ const handleUnderline = (0, react_1.useCallback)(() => execCommand("underline"), [execCommand]);
107
+ const handleStrikethrough = (0, react_1.useCallback)(() => execCommand("strikeThrough"), [execCommand]);
108
+ const handleHeader = (0, react_1.useCallback)(() => {
109
+ editorRef.current?.focus();
110
+ restoreSelection();
111
+ const sel = window.getSelection();
112
+ if (!sel || sel.rangeCount === 0)
113
+ return;
114
+ let node = sel.anchorNode;
115
+ let isInHeader = false;
116
+ while (node && node !== editorRef.current) {
117
+ if (node.nodeType === Node.ELEMENT_NODE && node.tagName === "H2") {
118
+ isInHeader = true;
119
+ break;
120
+ }
121
+ node = node.parentNode;
122
+ }
123
+ if (isInHeader) {
124
+ document.execCommand("formatBlock", false, "p");
125
+ }
126
+ else {
127
+ document.execCommand("formatBlock", false, "h2");
128
+ }
129
+ saveSelection();
130
+ handleEditorChange();
131
+ }, [restoreSelection, saveSelection, handleEditorChange]);
132
+ const handleFontSizeChange = (0, react_1.useCallback)((newSize) => {
133
+ const clampedSize = Math.max(8, Math.min(72, newSize));
134
+ setTextSize(clampedSize);
135
+ editorRef.current?.focus();
136
+ restoreSelection();
137
+ const sel = window.getSelection();
138
+ if (!sel || sel.rangeCount === 0)
139
+ return;
140
+ const range = sel.getRangeAt(0);
141
+ if (sel.isCollapsed) {
142
+ let containerNode = range.commonAncestorContainer;
143
+ if (containerNode.nodeType === Node.TEXT_NODE)
144
+ containerNode = containerNode.parentNode;
145
+ if (containerNode && containerNode.dataset?.size) {
146
+ containerNode.style.fontSize = `${clampedSize}px`;
147
+ containerNode.dataset.size = clampedSize.toString();
148
+ }
149
+ else {
150
+ const span = document.createElement("span");
151
+ span.style.fontSize = `${clampedSize}px`;
152
+ span.dataset.size = clampedSize.toString();
153
+ span.appendChild(document.createTextNode("\u200B"));
154
+ range.insertNode(span);
155
+ const newRange = document.createRange();
156
+ newRange.setStart(span.firstChild, 1);
157
+ newRange.collapse(true);
158
+ sel.removeAllRanges();
159
+ sel.addRange(newRange);
160
+ }
161
+ }
162
+ else {
163
+ const span = document.createElement("span");
164
+ span.style.fontSize = `${clampedSize}px`;
165
+ span.dataset.size = clampedSize.toString();
166
+ try {
167
+ range.surroundContents(span);
168
+ }
169
+ catch {
170
+ const fragment = range.extractContents();
171
+ span.appendChild(fragment);
172
+ range.insertNode(span);
173
+ }
174
+ sel.removeAllRanges();
175
+ const newRange = document.createRange();
176
+ newRange.selectNodeContents(span);
177
+ sel.addRange(newRange);
178
+ }
179
+ saveSelection();
180
+ handleEditorChange();
181
+ }, [restoreSelection, saveSelection, handleEditorChange]);
182
+ const handleAlign = (0, react_1.useCallback)((align) => {
183
+ const commandMap = {
184
+ left: "justifyLeft",
185
+ center: "justifyCenter",
186
+ right: "justifyRight",
187
+ justify: "justifyFull",
188
+ };
189
+ const targetCmd = commandMap[align] || "justifyLeft";
190
+ if (isCommandActive(targetCmd)) {
191
+ execCommand("justifyLeft");
192
+ }
193
+ else {
194
+ execCommand(targetCmd);
195
+ }
196
+ }, [execCommand, isCommandActive]);
197
+ const handleColor = (0, react_1.useCallback)((colorKey) => {
198
+ const targetColor = activeColor === colorKey ? "normal" : colorKey;
199
+ const colorInfo = ContentWrapper_component_1.COLOR_MAP[targetColor];
200
+ if (!colorInfo)
201
+ return;
202
+ editorRef.current?.focus();
203
+ restoreSelection();
204
+ const sel = window.getSelection();
205
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) {
206
+ setShowColorPicker(false);
207
+ return;
208
+ }
209
+ const range = sel.getRangeAt(0);
210
+ if (targetColor === "normal") {
211
+ let containerNode = range.commonAncestorContainer;
212
+ if (containerNode.nodeType === Node.TEXT_NODE)
213
+ containerNode = containerNode.parentNode;
214
+ if (containerNode && containerNode.dataset?.color) {
215
+ const parent = containerNode.parentNode;
216
+ while (containerNode.firstChild) {
217
+ parent?.insertBefore(containerNode.firstChild, containerNode);
218
+ }
219
+ parent?.removeChild(containerNode);
220
+ }
221
+ }
222
+ else {
223
+ const span = document.createElement("span");
224
+ span.className = colorInfo.tw;
225
+ span.style.color = colorInfo.css;
226
+ span.dataset.color = targetColor;
227
+ try {
228
+ range.surroundContents(span);
229
+ }
230
+ catch {
231
+ const fragment = range.extractContents();
232
+ span.appendChild(fragment);
233
+ range.insertNode(span);
234
+ }
235
+ sel.removeAllRanges();
236
+ const newRange = document.createRange();
237
+ newRange.selectNodeContents(span);
238
+ sel.addRange(newRange);
239
+ }
240
+ setActiveColor(targetColor);
241
+ saveSelection();
242
+ setShowColorPicker(false);
243
+ handleEditorChange();
244
+ }, [activeColor, restoreSelection, saveSelection, handleEditorChange]);
245
+ const handleBulletList = (0, react_1.useCallback)(() => execCommand("insertUnorderedList"), [execCommand]);
246
+ const handleNumberList = (0, react_1.useCallback)(() => execCommand("insertOrderedList"), [execCommand]);
247
+ const handleLink = (0, react_1.useCallback)(() => {
248
+ saveSelection();
249
+ setShowLinkInput(true);
250
+ setLinkUrl("");
251
+ }, [saveSelection]);
252
+ const handleLinkSubmit = (0, react_1.useCallback)(() => {
253
+ if (!linkUrl) {
254
+ setShowLinkInput(false);
255
+ return;
256
+ }
257
+ editorRef.current?.focus();
258
+ restoreSelection();
259
+ const sel = window.getSelection();
260
+ if (!sel || sel.rangeCount === 0) {
261
+ setShowLinkInput(false);
262
+ return;
263
+ }
264
+ const range = sel.getRangeAt(0);
265
+ const linkText = sel.isCollapsed ? linkUrl : range.toString();
266
+ const a = document.createElement("a");
267
+ a.href = linkUrl;
268
+ a.className = "text-primary underline";
269
+ a.dataset.link = linkUrl;
270
+ a.target = "_blank";
271
+ a.rel = "noopener";
272
+ a.textContent = linkText;
273
+ if (!sel.isCollapsed) {
274
+ range.deleteContents();
275
+ }
276
+ range.insertNode(a);
277
+ const newRange = document.createRange();
278
+ newRange.setStartAfter(a);
279
+ newRange.collapse(true);
280
+ sel.removeAllRanges();
281
+ sel.addRange(newRange);
282
+ saveSelection();
283
+ setShowLinkInput(false);
284
+ setLinkUrl("");
285
+ handleEditorChange();
286
+ }, [linkUrl, restoreSelection, saveSelection, handleEditorChange]);
287
+ const handleDivider = (0, react_1.useCallback)(() => {
288
+ editorRef.current?.focus();
289
+ restoreSelection();
290
+ const sel = window.getSelection();
291
+ if (!sel || sel.rangeCount === 0)
292
+ return;
293
+ const range = sel.getRangeAt(0);
294
+ const dividerWrapper = document.createElement("div");
295
+ dividerWrapper.className = "skcontent-divider";
296
+ const hr = document.createElement("hr");
297
+ dividerWrapper.appendChild(hr);
298
+ const newP = document.createElement("p");
299
+ newP.innerHTML = "<br>";
300
+ range.deleteContents();
301
+ range.insertNode(newP);
302
+ range.insertNode(dividerWrapper);
303
+ const newRange = document.createRange();
304
+ newRange.setStart(newP, 0);
305
+ newRange.collapse(true);
306
+ sel.removeAllRanges();
307
+ sel.addRange(newRange);
308
+ saveSelection();
309
+ handleEditorChange();
310
+ }, [restoreSelection, saveSelection, handleEditorChange]);
311
+ const handleKeyDown = (0, react_1.useCallback)((e) => {
312
+ if (e.key === "Backspace") {
313
+ const sel = window.getSelection();
314
+ if (sel && sel.rangeCount > 0 && sel.isCollapsed) {
315
+ const range = sel.getRangeAt(0);
316
+ if (range.startOffset === 0) {
317
+ let node = range.startContainer;
318
+ let currentBlock = null;
319
+ while (node && node !== editorRef.current) {
320
+ if (node.nodeType === Node.ELEMENT_NODE && node.parentNode === editorRef.current) {
321
+ currentBlock = node;
322
+ break;
323
+ }
324
+ node = node.parentNode;
325
+ }
326
+ if (currentBlock) {
327
+ const prev = currentBlock.previousElementSibling;
328
+ if (prev && (prev.classList.contains("skcontent-divider") || prev.tagName === "HR")) {
329
+ e.preventDefault();
330
+ prev.remove();
331
+ handleEditorChange();
332
+ return;
333
+ }
334
+ }
335
+ }
336
+ }
337
+ }
338
+ if (e.key === "Delete") {
339
+ const sel = window.getSelection();
340
+ if (sel && sel.rangeCount > 0 && sel.isCollapsed) {
341
+ const range = sel.getRangeAt(0);
342
+ let node = range.startContainer;
343
+ const textLen = node.textContent?.length || 0;
344
+ if (range.startOffset >= textLen) {
345
+ let currentBlock = null;
346
+ while (node && node !== editorRef.current) {
347
+ if (node.nodeType === Node.ELEMENT_NODE && node.parentNode === editorRef.current) {
348
+ currentBlock = node;
349
+ break;
350
+ }
351
+ node = node.parentNode;
352
+ }
353
+ if (currentBlock) {
354
+ const next = currentBlock.nextElementSibling;
355
+ if (next && (next.classList.contains("skcontent-divider") || next.tagName === "HR")) {
356
+ e.preventDefault();
357
+ next.remove();
358
+ handleEditorChange();
359
+ return;
360
+ }
361
+ }
362
+ }
363
+ }
364
+ }
365
+ }, [handleEditorChange]);
366
+ const updateActiveStates = (0, react_1.useCallback)(() => {
367
+ const sel = window.getSelection();
368
+ let inH2 = false;
369
+ if (sel && sel.rangeCount > 0) {
370
+ let node = sel.anchorNode;
371
+ while (node && node !== editorRef.current) {
372
+ if (node.nodeType === Node.ELEMENT_NODE && node.tagName === "H2") {
373
+ inH2 = true;
374
+ break;
375
+ }
376
+ node = node.parentNode;
377
+ }
378
+ }
379
+ setActiveStates({
380
+ header: inH2,
381
+ bold: isCommandActive("bold"),
382
+ italic: isCommandActive("italic"),
383
+ underline: isCommandActive("underline"),
384
+ strikeThrough: isCommandActive("strikeThrough"),
385
+ justifyLeft: isCommandActive("justifyLeft"),
386
+ justifyCenter: isCommandActive("justifyCenter"),
387
+ justifyRight: isCommandActive("justifyRight"),
388
+ justifyFull: isCommandActive("justifyFull"),
389
+ insertUnorderedList: isCommandActive("insertUnorderedList"),
390
+ insertOrderedList: isCommandActive("insertOrderedList"),
391
+ });
392
+ if (sel && sel.rangeCount > 0) {
393
+ let node = sel.anchorNode;
394
+ let detectedColor = "normal";
395
+ let detectedSize = null;
396
+ while (node && node !== editorRef.current) {
397
+ if (node.nodeType === Node.ELEMENT_NODE) {
398
+ const el = node;
399
+ if (el.dataset.color && detectedColor === "normal") {
400
+ detectedColor = el.dataset.color;
401
+ }
402
+ if (el.dataset.size && detectedSize === null) {
403
+ const parsed = parseInt(el.dataset.size);
404
+ if (!isNaN(parsed))
405
+ detectedSize = parsed;
406
+ }
407
+ }
408
+ node = node.parentNode;
409
+ }
410
+ setActiveColor(detectedColor);
411
+ if (detectedSize !== null) {
412
+ setTextSize(detectedSize);
413
+ }
414
+ }
415
+ }, [isCommandActive]);
416
+ const renderControlItem = (0, react_1.useCallback)((item, index) => {
417
+ if (typeof item !== "string") {
418
+ return (0, jsx_runtime_1.jsx)("div", { children: item }, index);
419
+ }
420
+ switch (item) {
421
+ case "HEADER":
422
+ return ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: activeStates.header ? "light" : "simple", paint: "primary", size: "sm", icon: "solid/text-h", tips: "Header", className: (0, _utils_1.cn)("toolbar-btn", activeStates.header && "toolbar-btn-active"), onClick: (e) => { e?.preventDefault?.(); handleHeader(); } }, index));
423
+ case "TEXT_SIZE":
424
+ case "FONT_SIZE":
425
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-0.5", children: [(0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: "simple", paint: "primary", size: "sm", icon: "solid/minus", tips: "Decrease Font Size", className: "toolbar-btn", onClick: (e) => { e?.preventDefault?.(); handleFontSizeChange(textSize - 1); } }), (0, jsx_runtime_1.jsx)("input", { type: "number", value: textSize, onChange: (e) => handleFontSizeChange(parseInt(e.target.value) || 14), onFocus: () => saveSelection(), className: "w-10 h-7 text-xs text-center border border-stroke rounded focus:outline-none focus:border-primary bg-background text-foreground select-none" }), (0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: "simple", paint: "primary", size: "sm", icon: "solid/plus", tips: "Increase Font Size", className: "toolbar-btn", onClick: (e) => { e?.preventDefault?.(); handleFontSizeChange(textSize + 1); } })] }, index));
426
+ case "BOLD":
427
+ return ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: activeStates.bold ? "light" : "simple", paint: "primary", size: "sm", icon: "solid/text-b", tips: "Bold", className: (0, _utils_1.cn)("toolbar-btn", activeStates.bold && "toolbar-btn-active"), onClick: (e) => { e?.preventDefault?.(); handleBold(); } }, index));
428
+ case "ITALIC":
429
+ return ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: activeStates.italic ? "light" : "simple", paint: "primary", size: "sm", icon: "solid/text-italic", tips: "Italic", className: (0, _utils_1.cn)("toolbar-btn", activeStates.italic && "toolbar-btn-active"), onClick: (e) => { e?.preventDefault?.(); handleItalic(); } }, index));
430
+ case "UNDERLINE":
431
+ return ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: activeStates.underline ? "light" : "simple", paint: "primary", size: "sm", icon: "solid/text-underline", tips: "Underline", className: (0, _utils_1.cn)("toolbar-btn", activeStates.underline && "toolbar-btn-active"), onClick: (e) => { e?.preventDefault?.(); handleUnderline(); } }, index));
432
+ case "STRIKETHROUGH":
433
+ return ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: activeStates.strikeThrough ? "light" : "simple", paint: "primary", size: "sm", icon: "solid/text-slash", tips: "Strikethrough", className: (0, _utils_1.cn)("toolbar-btn", activeStates.strikeThrough && "toolbar-btn-active"), onClick: (e) => { e?.preventDefault?.(); handleStrikethrough(); } }, index));
434
+ case "ALIGN_LEFT":
435
+ return ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: activeStates.justifyLeft ? "light" : "simple", paint: "primary", size: "sm", icon: "solid/text-left", tips: "Align Left", className: (0, _utils_1.cn)("toolbar-btn", activeStates.justifyLeft && "toolbar-btn-active"), onClick: (e) => { e?.preventDefault?.(); handleAlign("left"); } }, index));
436
+ case "ALIGN_CENTER":
437
+ return ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: activeStates.justifyCenter ? "light" : "simple", paint: "primary", size: "sm", icon: "solid/text-center", tips: "Align Center", className: (0, _utils_1.cn)("toolbar-btn", activeStates.justifyCenter && "toolbar-btn-active"), onClick: (e) => { e?.preventDefault?.(); handleAlign("center"); } }, index));
438
+ case "ALIGN_RIGHT":
439
+ return ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: activeStates.justifyRight ? "light" : "simple", paint: "primary", size: "sm", icon: "solid/text-right", tips: "Align Right", className: (0, _utils_1.cn)("toolbar-btn", activeStates.justifyRight && "toolbar-btn-active"), onClick: (e) => { e?.preventDefault?.(); handleAlign("right"); } }, index));
440
+ case "ALIGN_JUSTIFY":
441
+ return ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: activeStates.justifyFull ? "light" : "simple", paint: "primary", size: "sm", icon: "solid/text-justify", tips: "Align Justify", className: (0, _utils_1.cn)("toolbar-btn", activeStates.justifyFull && "toolbar-btn-active"), onClick: (e) => { e?.preventDefault?.(); handleAlign("justify"); } }, index));
442
+ case "LINK":
443
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: showLinkInput ? "light" : "simple", paint: "primary", size: "sm", icon: "solid/link", tips: "Link", className: (0, _utils_1.cn)("toolbar-btn", showLinkInput && "toolbar-btn-active"), onClick: (e) => { e?.preventDefault?.(); handleLink(); } }), showLinkInput && ((0, jsx_runtime_1.jsxs)("div", { className: "skcontent-dropdown skcontent-link-input", children: [(0, jsx_runtime_1.jsx)("input", { type: "url", placeholder: "https://...", value: linkUrl, onChange: (e) => setLinkUrl(e.target.value), onKeyDown: (e) => {
444
+ if (e.key === "Enter") {
445
+ e.preventDefault();
446
+ handleLinkSubmit();
447
+ }
448
+ if (e.key === "Escape") {
449
+ setShowLinkInput(false);
450
+ }
451
+ }, className: "skcontent-link-url-input", autoFocus: true }), (0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: "solid", paint: "primary", size: "xs", icon: "solid/check", onClick: (e) => { e?.preventDefault?.(); handleLinkSubmit(); } })] }))] }, index));
452
+ case "COLOR":
453
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: activeColor !== "normal" ? "light" : "simple", paint: "primary", size: "sm", label: (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("div", { className: "skcontent-color-dot", style: { backgroundColor: ContentWrapper_component_1.COLOR_MAP[activeColor]?.css || ContentWrapper_component_1.COLOR_MAP.normal.css } }) }), tips: `Text Color (${ContentWrapper_component_1.COLOR_MAP[activeColor]?.label || "Normal"})`, className: (0, _utils_1.cn)("toolbar-btn", (activeColor !== "normal" || showColorPicker) && "toolbar-btn-active"), onClick: (e) => { e?.preventDefault?.(); setShowColorPicker(!showColorPicker); } }), showColorPicker && ((0, jsx_runtime_1.jsx)("div", { className: "skcontent-dropdown skcontent-color-picker", children: Object.entries(ContentWrapper_component_1.COLOR_MAP).map(([key, info]) => ((0, jsx_runtime_1.jsx)("button", { type: "button", title: info.label, className: (0, _utils_1.cn)("skcontent-color-dot", activeColor === key && "scale-125 border-2 border-primary"), style: { backgroundColor: info.css }, onMouseDown: (e) => {
454
+ e.preventDefault();
455
+ handleColor(key);
456
+ } }, key))) }))] }, index));
457
+ case "LIST_BULLET":
458
+ case "BULLET_LIST":
459
+ return ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: activeStates.insertUnorderedList ? "light" : "simple", paint: "primary", size: "sm", icon: "solid/list-bullet", tips: "Bullet List", className: (0, _utils_1.cn)("toolbar-btn", activeStates.insertUnorderedList && "toolbar-btn-active"), onClick: (e) => { e?.preventDefault?.(); handleBulletList(); } }, index));
460
+ case "LIST_NUMBER":
461
+ case "NUMBER_LIST":
462
+ return ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: activeStates.insertOrderedList ? "light" : "simple", paint: "primary", size: "sm", icon: "solid/list-number", tips: "Numbered List", className: (0, _utils_1.cn)("toolbar-btn", activeStates.insertOrderedList && "toolbar-btn-active"), onClick: (e) => { e?.preventDefault?.(); handleNumberList(); } }, index));
463
+ case "DIVIDER":
464
+ return ((0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { variant: "simple", paint: "primary", size: "sm", icon: "solid/minus", tips: "Divider", className: "toolbar-btn", onClick: (e) => { e?.preventDefault?.(); handleDivider(); } }, index));
465
+ case "SEP":
466
+ return (0, jsx_runtime_1.jsx)("div", { className: "skcontent-toolbar-sep" }, index);
467
+ default:
468
+ return null;
469
+ }
470
+ }, [
471
+ activeStates,
472
+ showLinkInput,
473
+ linkUrl,
474
+ activeColor,
475
+ showColorPicker,
476
+ textSize,
477
+ handleHeader,
478
+ handleFontSizeChange,
479
+ handleBold,
480
+ handleItalic,
481
+ handleUnderline,
482
+ handleStrikethrough,
483
+ handleAlign,
484
+ handleLink,
485
+ handleLinkSubmit,
486
+ handleColor,
487
+ handleBulletList,
488
+ handleNumberList,
489
+ handleDivider,
490
+ saveSelection,
491
+ ]);
492
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "relative flex flex-col gap-y-0.5 w-full", children: [label && ((0, jsx_runtime_1.jsxs)("label", { htmlFor: randomId, className: (0, _utils_1.cn)("input-label", props.disabled && "input-label-disabled", !!invalidMessage && "input-label-error", (0, _utils_1.pcn)(className, "label"), props.disabled && (0, _utils_1.pcn)(className, "label", "disabled"), !!invalidMessage && (0, _utils_1.pcn)(className, "label", "error")), children: [label, validations && validations?.required && (0, jsx_runtime_1.jsx)("span", { className: "text-danger ml-1", children: "*" })] })), tip && ((0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-tip", props.disabled && "input-tip-disabled", (0, _utils_1.pcn)(className, "tip"), props.disabled && (0, _utils_1.pcn)(className, "tip", "disabled")), children: tip })), (0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("skcontent-container", props.disabled && "skcontent-container-disabled", !!invalidMessage && "skcontent-container-error", (0, _utils_1.pcn)(className, "base"), !!invalidMessage && (0, _utils_1.pcn)(className, "base", "error")), children: [(0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)("skcontent-toolbar", (0, _utils_1.pcn)(className, "toolbar")), children: (toolbarControl || exports.DEFAULT_TOOLBAR_CONTROLS).map(renderControlItem) }), (0, jsx_runtime_1.jsx)("div", { ref: (node) => {
493
+ editorRef.current = node;
494
+ if (typeof ref === "function")
495
+ ref(node);
496
+ else if (ref && "current" in ref)
497
+ ref.current = node;
498
+ }, id: randomId, contentEditable: !props.disabled, suppressContentEditableWarning: true, className: (0, _utils_1.cn)("skcontent-editor", props.disabled && "skcontent-editor-disabled", (0, _utils_1.pcn)(className, "editor")), onInput: () => {
499
+ saveSelection();
500
+ handleEditorChange();
501
+ updateActiveStates();
502
+ }, onKeyDown: handleKeyDown, onMouseUp: () => {
503
+ saveSelection();
504
+ updateActiveStates();
505
+ }, onKeyUp: () => {
506
+ saveSelection();
507
+ updateActiveStates();
508
+ }, onFocus: () => {
509
+ inputHandler.setFocus(true);
510
+ setShowColorPicker(false);
511
+ }, onBlur: () => {
512
+ setTimeout(() => inputHandler.setFocus(false), 150);
513
+ }, "data-placeholder": props.placeholder || "Write content..." })] }), invalidMessage && ((0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-error-message", (0, _utils_1.pcn)(className, "error")), children: invalidMessage }))] }));
514
+ }
@@ -0,0 +1,22 @@
1
+ import { TextareaHTMLAttributes, ReactNode, Ref } from "react";
2
+ import { type IconName } from "@skalfa/skalfa-icon";
3
+ import { ValidationRules } from "@utils";
4
+ export interface TextareaProps extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "onChange"> {
5
+ label?: string;
6
+ tip?: string | ReactNode;
7
+ leftIcon?: IconName | any;
8
+ rightIcon?: IconName | any;
9
+ value?: any;
10
+ invalid?: string;
11
+ validations?: ValidationRules;
12
+ onlyAlphabet?: boolean;
13
+ uppercase?: boolean;
14
+ lowercase?: boolean;
15
+ onChange?: (value: any) => any;
16
+ register?: (name: string, validations?: ValidationRules) => void;
17
+ unregister?: (name: string) => void;
18
+ ref?: Ref<HTMLTextAreaElement>;
19
+ /** Use custom class with: "label::", "tip::", "error::", "base::", "icon::". */
20
+ className?: string;
21
+ }
22
+ export declare function TextareaComponent({ label, tip, leftIcon, rightIcon, className, value, invalid, validations, onlyAlphabet, uppercase, lowercase, register, unregister, onChange, ref, ...props }: TextareaProps): import("@types/react").JSX.Element;