ssovee-os-web-ui 0.1.74 → 0.1.77

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/README.md CHANGED
@@ -84,27 +84,29 @@ Use `theme="vs-dark"` for a dark editor. The component also accepts Monaco `lang
84
84
 
85
85
  ### RichEditor
86
86
 
87
- `RichEditor` provides WYSIWYG text editing for rich content and HTML output:
87
+ `RichEditor` is a TinyMCE-based rich text editor for formatted content and HTML editing.
88
88
 
89
89
  ```tsx
90
90
  import { useState } from "react";
91
91
  import { RichEditor } from "ssovee-os-web-ui";
92
92
 
93
- export function StoryEditor() {
94
- const [value, setValue] = useState("<p><strong>Hello</strong> world</p>");
93
+ export function NotesEditor() {
94
+ const [value, setValue] = useState("<p>Start writing...</p>");
95
95
 
96
96
  return (
97
97
  <RichEditor
98
+ label="Message"
98
99
  value={value}
99
100
  onChange={setValue}
100
- placeholder="Write something rich..."
101
- height={220}
101
+ height="260px"
102
+ placeholder="Write something beautiful..."
103
+ helperText="Supports basic formatting, links, tables, and media."
102
104
  />
103
105
  );
104
106
  }
105
107
  ```
106
108
 
107
- `RichEditor` supports formatted text output in HTML, along with placeholder, height, and readOnly support.
109
+ The component supports `toolbar`, `plugins`, `fullWidth`, `error`, and `disabled` props for common form usage.
108
110
 
109
111
  ## Window components
110
112
 
@@ -230,8 +232,8 @@ The package exports these components and types for external use:
230
232
  - `Button`
231
233
  - `Input`
232
234
  - `TextArea`
233
- - `CodeEditor`
234
235
  - `RichEditor`
236
+ - `CodeEditor`
235
237
  - `Select`
236
238
  - `SelectOption`
237
239
  - `Checkbox`
package/dist/index.cjs CHANGED
@@ -3,10 +3,11 @@
3
3
  var React23 = require('react');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
  var react = require('@phosphor-icons/react');
6
+ require('tinymce/tinymce');
7
+ require('tinymce/icons/default/icons');
8
+ require('tinymce/themes/silver');
9
+ require('tinymce/models/dom/model');
6
10
  var Editor = require('@monaco-editor/react');
7
- var react$1 = require('@tiptap/react');
8
- var Placeholder = require('@tiptap/extension-placeholder');
9
- var StarterKit = require('@tiptap/starter-kit');
10
11
  var uaParserJs = require('ua-parser-js');
11
12
 
12
13
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -31,8 +32,6 @@ function _interopNamespace(e) {
31
32
 
32
33
  var React23__namespace = /*#__PURE__*/_interopNamespace(React23);
33
34
  var Editor__default = /*#__PURE__*/_interopDefault(Editor);
34
- var Placeholder__default = /*#__PURE__*/_interopDefault(Placeholder);
35
- var StarterKit__default = /*#__PURE__*/_interopDefault(StarterKit);
36
35
 
37
36
  // src/components/Button.tsx
38
37
 
@@ -311,6 +310,144 @@ var TextArea = React23__namespace.default.forwardRef(
311
310
  );
312
311
  TextArea.displayName = "TextArea";
313
312
  var TextArea_default = TextArea;
313
+ var DEFAULT_PLUGINS = [
314
+ "advlist",
315
+ "autolink",
316
+ "lists",
317
+ "link",
318
+ "image",
319
+ "charmap",
320
+ "preview",
321
+ "anchor",
322
+ "searchreplace",
323
+ "visualblocks",
324
+ "code",
325
+ "fullscreen",
326
+ "insertdatetime",
327
+ "media",
328
+ "table",
329
+ "wordcount"
330
+ ];
331
+ var DEFAULT_TOOLBAR = "undo redo | blocks | bold italic underline strikethrough | alignleft aligncenter alignright | bullist numlist outdent indent | link image media | code fullscreen";
332
+ var RichEditor = React23__namespace.default.forwardRef(
333
+ ({
334
+ value = "",
335
+ onChange,
336
+ label,
337
+ helperText,
338
+ error,
339
+ fullWidth = false,
340
+ height = "220px",
341
+ menubar = false,
342
+ toolbar = DEFAULT_TOOLBAR,
343
+ className,
344
+ plugins = DEFAULT_PLUGINS,
345
+ disabled = false,
346
+ placeholder,
347
+ rows,
348
+ ...props
349
+ }, ref) => {
350
+ const editorId = React23.useId().replace(/:/g, "");
351
+ const textareaRef = React23.useRef(null);
352
+ const hasTinymce = typeof window !== "undefined" && Boolean(window.tinymce);
353
+ React23.useEffect(() => {
354
+ if (typeof window === "undefined" || !textareaRef.current || !window.tinymce) {
355
+ return;
356
+ }
357
+ const tinymceApi = window.tinymce;
358
+ const existingEditor = tinymceApi.get(editorId);
359
+ if (existingEditor) {
360
+ existingEditor.remove();
361
+ }
362
+ tinymceApi.init({
363
+ target: textareaRef.current,
364
+ selector: void 0,
365
+ height,
366
+ menubar,
367
+ toolbar,
368
+ plugins,
369
+ statusbar: false,
370
+ resize: false,
371
+ branding: false,
372
+ promotion: false,
373
+ placeholder,
374
+ readonly: disabled,
375
+ content_style: "body { font-family: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif; font-size: 14px; line-height: 1.6; }",
376
+ setup: (editor) => {
377
+ editor.on("init", () => {
378
+ editor.setContent(value ?? "");
379
+ });
380
+ editor.on("input change keyup", () => {
381
+ onChange?.(editor.getContent());
382
+ });
383
+ }
384
+ });
385
+ return () => {
386
+ const activeEditor = tinymceApi.get(editorId);
387
+ activeEditor?.remove();
388
+ };
389
+ }, [disabled, editorId, height, menubar, onChange, placeholder, plugins, toolbar, value]);
390
+ React23.useEffect(() => {
391
+ const tinymceApi = window?.tinymce;
392
+ const activeEditor = tinymceApi?.get(editorId);
393
+ if (activeEditor && activeEditor.getContent() !== (value ?? "")) {
394
+ activeEditor.setContent(value ?? "");
395
+ }
396
+ }, [editorId, value]);
397
+ const fallbackTextArea = /* @__PURE__ */ jsxRuntime.jsx(
398
+ "textarea",
399
+ {
400
+ ref: textareaRef,
401
+ id: editorId,
402
+ value,
403
+ onChange: (event) => onChange?.(event.currentTarget.value),
404
+ disabled,
405
+ placeholder,
406
+ rows: rows ?? 7,
407
+ "aria-label": label ?? props["aria-label"] ?? "Rich text editor",
408
+ className: cn(
409
+ "w-full min-h-[200px] rounded-[6px] border border-surface-1 bg-primary px-3 py-2 text-base text-neutral-700 transition-all duration-200 focus:border-brand-color focus:outline-none focus:ring-1 focus:ring-brand-color/50 placeholder:text-neutral-400",
410
+ error && "border-red-500 focus:border-red-500 focus:ring-red-500/50",
411
+ disabled && "cursor-not-allowed opacity-60",
412
+ fullWidth && "w-full"
413
+ ),
414
+ ...props
415
+ }
416
+ );
417
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: cn("flex flex-col gap-1", fullWidth && "w-full", className), children: [
418
+ label && /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium text-neutral-500 dark:text-white", htmlFor: editorId, children: label }),
419
+ /* @__PURE__ */ jsxRuntime.jsx(
420
+ "div",
421
+ {
422
+ className: cn(
423
+ "overflow-hidden rounded-[6px] border border-surface-1 bg-white transition-all duration-200",
424
+ fullWidth && "w-full",
425
+ error && "border-red-500"
426
+ ),
427
+ style: { minHeight: height },
428
+ children: hasTinymce ? /* @__PURE__ */ jsxRuntime.jsx(
429
+ "textarea",
430
+ {
431
+ ref: textareaRef,
432
+ id: editorId,
433
+ defaultValue: value,
434
+ "aria-label": label ?? props["aria-label"] ?? "Rich text editor",
435
+ disabled,
436
+ style: { display: "none" },
437
+ ...props
438
+ }
439
+ ) : fallbackTextArea
440
+ }
441
+ ),
442
+ (error || helperText) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1", children: [
443
+ error && /* @__PURE__ */ jsxRuntime.jsx(Typography_default, { variant: "p", className: "text-xs text-red-500", children: error }),
444
+ helperText && !error && /* @__PURE__ */ jsxRuntime.jsx(Typography_default, { variant: "p", className: "text-xs text-neutral-400", children: helperText })
445
+ ] })
446
+ ] });
447
+ }
448
+ );
449
+ RichEditor.displayName = "RichEditor";
450
+ var RichEditor_default = RichEditor;
314
451
  var CodeEditor = ({
315
452
  value,
316
453
  onChange,
@@ -333,238 +470,6 @@ var CodeEditor = ({
333
470
  ) });
334
471
  };
335
472
  var CodeEditor_default = CodeEditor;
336
- var RichEditor = ({
337
- value,
338
- onChange,
339
- placeholder = "Write something...",
340
- readOnly = false,
341
- className,
342
- height = 220
343
- }) => {
344
- const editor = react$1.useEditor({
345
- editable: !readOnly,
346
- extensions: [
347
- StarterKit__default.default,
348
- Placeholder__default.default.configure({
349
- placeholder
350
- })
351
- ],
352
- content: value,
353
- editorProps: {
354
- attributes: {
355
- class: "rich-editor-prose"
356
- }
357
- },
358
- onUpdate: ({ editor: editor2 }) => {
359
- onChange(editor2.getHTML());
360
- }
361
- });
362
- React23.useEffect(() => {
363
- if (!editor) {
364
- return;
365
- }
366
- if (value !== editor.getHTML()) {
367
- editor.commands.setContent(value, { emitUpdate: false });
368
- }
369
- }, [editor, value]);
370
- const editorHeight = typeof height === "number" ? `${height}px` : height;
371
- editor ? [
372
- { label: "Bold", action: () => editor.chain().focus().toggleBold().run(), isActive: editor.isActive("bold") },
373
- { label: "Italic", action: () => editor.chain().focus().toggleItalic().run(), isActive: editor.isActive("italic") },
374
- { label: "Bullet List", action: () => editor.chain().focus().toggleBulletList().run(), isActive: editor.isActive("bulletList") },
375
- { label: "Ordered List", action: () => editor.chain().focus().toggleOrderedList().run(), isActive: editor.isActive("orderedList") }
376
- ] : [];
377
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, children: [
378
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
379
- .rich-editor-shell {
380
- border: 1px solid #d5d7db;
381
- border-radius: 18px;
382
- overflow: hidden;
383
- background: #f3f4f6;
384
- box-shadow: inset 0 1px 0 rgba(255,255,255,0.6);
385
- }
386
- .rich-editor-topbar {
387
- display: flex;
388
- align-items: center;
389
- justify-content: center;
390
- padding: 16px 20px 12px;
391
- }
392
- .rich-editor-tabs {
393
- display: inline-flex;
394
- align-items: center;
395
- gap: 10px;
396
- padding: 6px;
397
- border-radius: 12px;
398
- background: rgba(15, 23, 42, 0.04);
399
- border: 1px solid rgba(15, 23, 42, 0.08);
400
- }
401
- .rich-editor-tab {
402
- border: none;
403
- background: transparent;
404
- color: rgba(15, 23, 42, 0.7);
405
- font-size: 14px;
406
- padding: 8px 12px;
407
- border-radius: 8px;
408
- cursor: pointer;
409
- font-weight: 500;
410
- }
411
- .rich-editor-tab.is-active {
412
- background: rgba(255,255,255,0.8);
413
- color: #111827;
414
- box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
415
- }
416
- .rich-editor-toolbar {
417
- display: flex;
418
- align-items: center;
419
- gap: 8px;
420
- padding: 10px 14px;
421
- border-top: 1px solid rgba(15, 23, 42, 0.08);
422
- border-bottom: 1px solid rgba(15, 23, 42, 0.08);
423
- background: rgba(255,255,255,0.65);
424
- }
425
- .rich-editor-toolbar button {
426
- border: none;
427
- background: transparent;
428
- color: #374151;
429
- border-radius: 6px;
430
- min-width: 26px;
431
- height: 26px;
432
- font-size: 14px;
433
- font-weight: 600;
434
- cursor: pointer;
435
- display: inline-flex;
436
- align-items: center;
437
- justify-content: center;
438
- transition: all 0.2s ease;
439
- }
440
- .rich-editor-toolbar button:hover:not(:disabled) {
441
- background: rgba(15, 23, 42, 0.04);
442
- }
443
- .rich-editor-toolbar button.is-active {
444
- background: rgba(59, 130, 246, 0.12);
445
- color: #1d4ed8;
446
- }
447
- .rich-editor-toolbar button:disabled {
448
- opacity: 0.4;
449
- cursor: not-allowed;
450
- }
451
- .rich-editor-toolbar .toolbar-spacer {
452
- width: 1px;
453
- height: 20px;
454
- background: rgba(15, 23, 42, 0.12);
455
- margin: 0 4px;
456
- }
457
- .rich-editor-toolbar .toolbar-actions {
458
- margin-left: auto;
459
- display: flex;
460
- align-items: center;
461
- gap: 8px;
462
- }
463
- .rich-editor-prose {
464
- min-height: ${editorHeight};
465
- padding: 40px 52px 52px;
466
- background: rgba(255,255,255,0.78);
467
- color: #111827;
468
- outline: none;
469
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
470
- }
471
- .rich-editor-prose:focus {
472
- outline: none;
473
- }
474
- .rich-editor-prose h1 {
475
- font-size: clamp(2.2rem, 2vw + 1.1rem, 3.2rem);
476
- line-height: 1.1;
477
- font-weight: 700;
478
- margin: 0 0 1.5rem;
479
- letter-spacing: -0.05em;
480
- }
481
- .rich-editor-prose h2 {
482
- font-size: 2rem;
483
- line-height: 1.2;
484
- font-weight: 700;
485
- margin: 2rem 0 1rem;
486
- }
487
- .rich-editor-prose p {
488
- margin: 0 0 1rem;
489
- font-size: 1.04rem;
490
- line-height: 1.7;
491
- color: rgba(17, 24, 39, 0.9);
492
- }
493
- .rich-editor-prose p strong,
494
- .rich-editor-prose p b {
495
- font-weight: 700;
496
- }
497
- .rich-editor-prose p em,
498
- .rich-editor-prose p i {
499
- font-style: italic;
500
- }
501
- .rich-editor-prose a {
502
- color: #4f46e5;
503
- text-decoration: underline;
504
- }
505
- .rich-editor-prose ul,
506
- .rich-editor-prose ol {
507
- padding-left: 1.5rem;
508
- margin: 0 0 1rem;
509
- }
510
- .rich-editor-prose li {
511
- margin-bottom: 0.45rem;
512
- }
513
- .rich-editor-prose code {
514
- display: inline-block;
515
- padding: 0.5rem 0.9rem;
516
- background: rgba(15, 23, 42, 0.06);
517
- border-radius: 12px;
518
- border: 1px solid rgba(15, 23, 42, 0.08);
519
- color: #111827;
520
- font-size: 0.96rem;
521
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
522
- }
523
- .rich-editor-prose blockquote {
524
- border-left: 3px solid #d1d5db;
525
- padding-left: 0.9rem;
526
- color: #4b5563;
527
- margin: 1.25rem 0;
528
- }
529
- ` }),
530
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rich-editor-shell", children: [
531
- !readOnly && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
532
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rich-editor-topbar", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rich-editor-tabs", "aria-label": "Editor mode selector", children: ["Agent editor", "Docx editor", "Notion-like editor", "Simple editor"].map((tab) => /* @__PURE__ */ jsxRuntime.jsx(
533
- "button",
534
- {
535
- type: "button",
536
- className: `rich-editor-tab${tab === "Simple editor" ? " is-active" : ""}`,
537
- "aria-label": tab,
538
- children: tab
539
- },
540
- tab
541
- )) }) }),
542
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rich-editor-toolbar", "aria-label": "Rich editor toolbar", children: [
543
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Undo", title: "Undo", children: "\u21B6" }),
544
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Redo", title: "Redo", children: "\u21B7" }),
545
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "toolbar-spacer" }),
546
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Heading", title: "Heading", children: "H" }),
547
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Bold", title: "Bold", className: editor?.isActive("bold") ? "is-active" : "", onClick: () => editor?.chain().focus().toggleBold().run(), children: "B" }),
548
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Italic", title: "Italic", className: editor?.isActive("italic") ? "is-active" : "", onClick: () => editor?.chain().focus().toggleItalic().run(), children: "I" }),
549
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Strike", title: "Strike", onClick: () => editor?.chain().focus().toggleStrike().run(), children: "S" }),
550
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Code", title: "Code", onClick: () => editor?.chain().focus().toggleCode().run(), children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: "<" }) }),
551
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Link", title: "Link", children: "\u2301" }),
552
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Bullet list", title: "Bullet list", className: editor?.isActive("bulletList") ? "is-active" : "", onClick: () => editor?.chain().focus().toggleBulletList().run(), children: "\u2022" }),
553
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Ordered list", title: "Ordered list", className: editor?.isActive("orderedList") ? "is-active" : "", onClick: () => editor?.chain().focus().toggleOrderedList().run(), children: "1." }),
554
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "toolbar-spacer" }),
555
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Align left", title: "Align left", children: "\u2261" }),
556
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Quote", title: "Quote", onClick: () => editor?.chain().focus().toggleBlockquote().run(), children: "\u275D" }),
557
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "toolbar-actions", children: [
558
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Search", title: "Search", children: "\u2315" }),
559
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Theme", title: "Theme", children: "\u263C" })
560
- ] })
561
- ] })
562
- ] }),
563
- /* @__PURE__ */ jsxRuntime.jsx(react$1.EditorContent, { editor })
564
- ] })
565
- ] });
566
- };
567
- var RichEditor_default = RichEditor;
568
473
  var Select = React23__namespace.default.forwardRef(
569
474
  ({
570
475
  className,
@@ -3496,8 +3401,8 @@ var useGridClasses = (windowSize, gridRules) => {
3496
3401
  const gridClasses = React23.useMemo(() => {
3497
3402
  const classes = {};
3498
3403
  for (const element in gridRules) {
3499
- const classRules = gridRules[element];
3500
- const colSpan = classRules["col-span"][deviceType];
3404
+ const classRules = gridRules[element] ?? {};
3405
+ const colSpan = classRules["col-span"]?.[deviceType] ?? "12";
3501
3406
  classes[element] = `col-span-${deviceType}-${colSpan}`;
3502
3407
  }
3503
3408
  return classes;