@jikjo/ui-kit 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -31,11 +31,7 @@ function Btn({ active, label, onMouseDown, children }) {
31
31
  type: "button",
32
32
  "aria-label": label,
33
33
  onMouseDown,
34
- className: [
35
- "flex items-center justify-center w-8 h-8 rounded-md",
36
- "transition-colors duration-75",
37
- active ? "bg-white/15 text-white" : "text-zinc-400 hover:bg-white/10 hover:text-zinc-200"
38
- ].join(" "),
34
+ className: `jikjo-bubble-menu__btn${active ? " jikjo-bubble-menu__btn--active" : ""}`,
39
35
  children
40
36
  });
41
37
  }
@@ -91,7 +87,7 @@ function BubbleMenu({ isVisible, format, onToggleFormat, editor }) {
91
87
  duration: .1,
92
88
  ease: "easeOut"
93
89
  },
94
- className: "flex items-center gap-0.5 px-1.5 py-1.5 rounded-lg bg-zinc-800 shadow-xl shadow-black/50",
90
+ className: "jikjo-bubble-menu",
95
91
  children: [
96
92
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Btn, {
97
93
  active: format.bold,
@@ -129,7 +125,7 @@ function BubbleMenu({ isVisible, format, onToggleFormat, editor }) {
129
125
  strokeWidth: 2
130
126
  })
131
127
  }),
132
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "w-px h-4 bg-zinc-600/50 mx-0.5 shrink-0" }),
128
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "jikjo-bubble-menu__divider" }),
133
129
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Btn, {
134
130
  active: format.strikethrough,
135
131
  label: "Strikethrough",
@@ -165,22 +161,6 @@ const BTN = 24;
165
161
  const GAP = 4;
166
162
  const GUTTER_LEFT = 6;
167
163
  GUTTER_LEFT + 2 * BTN + GAP;
168
- const btnBase = {
169
- display: "flex",
170
- alignItems: "center",
171
- justifyContent: "center",
172
- borderRadius: 4,
173
- border: "none",
174
- background: "transparent",
175
- color: "#a1a1aa",
176
- cursor: "pointer",
177
- padding: 0,
178
- transition: "background 80ms, color 80ms"
179
- };
180
- const btnHover = {
181
- background: "rgba(63,63,70,0.7)",
182
- color: "#e4e4e7"
183
- };
184
164
  const ICON_MAP$1 = {
185
165
  paragraph: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Text, {
186
166
  size: 13,
@@ -445,31 +425,12 @@ function BlockToolbar({ isVisible, nodeKey, focusedNodeKey, items, editor, showD
445
425
  setPanelOpen((p) => !p);
446
426
  }, []);
447
427
  if (!container) return null;
448
- const dragBtnStyle = {
449
- ...btnBase,
450
- width: BTN,
451
- height: BTN,
452
- cursor: "grab",
453
- ...dragHovered ? btnHover : {}
454
- };
455
- const addBtnStyle = {
456
- ...btnBase,
457
- width: BTN,
458
- height: BTN,
459
- ...panelOpen || addHovered ? btnHover : {}
460
- };
461
428
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
462
429
  (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(motion_react.AnimatePresence, { children: focusedTop !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(motion_react.motion.div, {
430
+ className: "jikjo-block-toolbar__cursor-indicator",
463
431
  style: {
464
- position: "absolute",
465
432
  top: focusedTop,
466
- left: 0,
467
- width: 2,
468
- height: focusedHeight,
469
- background: "#818cf8",
470
- borderRadius: 1,
471
- zIndex: 2,
472
- pointerEvents: "none"
433
+ height: focusedHeight
473
434
  },
474
435
  initial: { opacity: 0 },
475
436
  animate: { opacity: 1 },
@@ -501,7 +462,12 @@ function BlockToolbar({ isVisible, nodeKey, focusedNodeKey, items, editor, showD
501
462
  "data-drag-handle": true,
502
463
  onDragStart: handleDragStart,
503
464
  "aria-label": "Drag to reorder",
504
- style: dragBtnStyle,
465
+ className: `jikjo-block-toolbar__btn${dragHovered ? " jikjo-block-toolbar__btn--hovered" : ""}`,
466
+ style: {
467
+ width: BTN,
468
+ height: BTN,
469
+ cursor: "grab"
470
+ },
505
471
  onMouseEnter: () => setDragHovered(true),
506
472
  onMouseLeave: () => setDragHovered(false),
507
473
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.GripVertical, {
@@ -513,7 +479,11 @@ function BlockToolbar({ isVisible, nodeKey, focusedNodeKey, items, editor, showD
513
479
  type: "button",
514
480
  "aria-label": "Add block",
515
481
  "aria-expanded": panelOpen,
516
- style: addBtnStyle,
482
+ className: `jikjo-block-toolbar__btn${panelOpen || addHovered ? " jikjo-block-toolbar__btn--hovered" : ""}`,
483
+ style: {
484
+ width: BTN,
485
+ height: BTN
486
+ },
517
487
  onMouseDown: handleAddClick,
518
488
  onMouseEnter: () => setAddHovered(true),
519
489
  onMouseLeave: () => setAddHovered(false),
@@ -524,35 +494,13 @@ function BlockToolbar({ isVisible, nodeKey, focusedNodeKey, items, editor, showD
524
494
  })]
525
495
  }, nodeKey ?? "toolbar") }), container),
526
496
  (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(motion_react.AnimatePresence, { children: dropLine !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(motion_react.motion.div, {
527
- style: {
528
- position: "absolute",
529
- top: dropLine.top,
530
- left: 0,
531
- right: 0,
532
- height: 2,
533
- zIndex: 3,
534
- pointerEvents: "none",
535
- display: "flex",
536
- alignItems: "center",
537
- transform: "translateY(-1px)"
538
- },
497
+ className: "jikjo-block-toolbar__drop-line",
498
+ style: { top: dropLine.top },
539
499
  initial: { opacity: 0 },
540
500
  animate: { opacity: 1 },
541
501
  exit: { opacity: 0 },
542
502
  transition: { duration: .06 },
543
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
544
- width: 6,
545
- height: 6,
546
- borderRadius: "50%",
547
- background: "#818cf8",
548
- flexShrink: 0,
549
- marginLeft: 4
550
- } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
551
- flex: 1,
552
- height: 2,
553
- background: "#818cf8",
554
- marginRight: 8
555
- } })]
503
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "jikjo-block-toolbar__drop-line-dot" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "jikjo-block-toolbar__drop-line-bar" })]
556
504
  }, "drop-line") }), container),
557
505
  (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(motion_react.AnimatePresence, { children: isVisible && panelOpen && top !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(motion_react.motion.div, {
558
506
  ref: panelRef,
@@ -582,11 +530,11 @@ function BlockToolbar({ isVisible, nodeKey, focusedNodeKey, items, editor, showD
582
530
  },
583
531
  role: "dialog",
584
532
  "aria-label": "Insert block",
585
- className: "rounded-lg bg-zinc-800 shadow-xl shadow-black/50 py-1.5",
533
+ className: "jikjo-menu-panel",
586
534
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
587
535
  role: "listbox",
588
536
  "aria-label": "Block type",
589
- className: "w-full px-1.5 flex flex-col",
537
+ className: "jikjo-menu-panel__list",
590
538
  children: items.map((item, index) => {
591
539
  const icon = ICON_MAP$1[item.id] ?? item.icon;
592
540
  const isActive = index === activeIndex;
@@ -602,16 +550,12 @@ function BlockToolbar({ isVisible, nodeKey, focusedNodeKey, items, editor, showD
602
550
  });
603
551
  },
604
552
  onMouseEnter: () => setActiveIndex(index),
605
- className: [
606
- "w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors duration-75",
607
- "outline-none",
608
- isActive ? "bg-zinc-700/60 text-zinc-100" : "text-zinc-400 hover:bg-zinc-700/40 hover:text-zinc-200"
609
- ].join(" "),
553
+ className: `jikjo-menu-panel__item${isActive ? " jikjo-menu-panel__item--active" : ""}`,
610
554
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
611
- className: "flex items-center justify-center shrink-0 w-4 text-current",
555
+ className: "jikjo-menu-panel__item-icon",
612
556
  children: icon
613
557
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
614
- className: "text-sm font-normal leading-none",
558
+ className: "jikjo-menu-panel__item-label",
615
559
  children: item.label
616
560
  })]
617
561
  }, item.id);
@@ -756,9 +700,9 @@ function SlashMenu({ isVisible, query, items, editor, onClose }) {
756
700
  },
757
701
  role: "dialog",
758
702
  "aria-label": "Insert block",
759
- className: "rounded-lg bg-zinc-800 shadow-xl shadow-black/50 py-1.5 overflow-hidden",
703
+ className: "jikjo-menu-panel",
760
704
  children: filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
761
- className: "px-4 py-2 text-xs text-zinc-500",
705
+ className: "jikjo-menu-panel__empty",
762
706
  children: [
763
707
  "No results for “",
764
708
  query,
@@ -768,7 +712,7 @@ function SlashMenu({ isVisible, query, items, editor, onClose }) {
768
712
  ref: containerRef,
769
713
  role: "listbox",
770
714
  "aria-label": "Block type",
771
- className: "w-full max-h-72 overflow-y-auto px-1.5 flex flex-col",
715
+ className: "jikjo-menu-panel__list jikjo-menu-panel__list--scrollable",
772
716
  children: filtered.map((item, index) => {
773
717
  const icon = ICON_MAP[item.id] ?? item.icon;
774
718
  const isActive = index === activeIndex;
@@ -785,16 +729,12 @@ function SlashMenu({ isVisible, query, items, editor, onClose }) {
785
729
  });
786
730
  },
787
731
  onMouseEnter: () => setActiveIndex(index),
788
- className: [
789
- "w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors duration-75",
790
- "outline-none focus-visible:outline-none",
791
- isActive ? "bg-zinc-700/80 text-zinc-100" : "text-zinc-400 hover:bg-zinc-700/60 hover:text-zinc-200 focus-visible:bg-zinc-700/80 focus-visible:text-zinc-100"
792
- ].join(" "),
732
+ className: `jikjo-menu-panel__item${isActive ? " jikjo-menu-panel__item--active" : ""}`,
793
733
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
794
- className: "flex items-center justify-center shrink-0 w-4 text-current",
734
+ className: "jikjo-menu-panel__item-icon",
795
735
  children: icon
796
736
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
797
- className: "text-sm font-normal leading-none",
737
+ className: "jikjo-menu-panel__item-label",
798
738
  children: item.label
799
739
  })]
800
740
  }, item.id);
package/dist/index.css ADDED
@@ -0,0 +1,178 @@
1
+ :root {
2
+ --jikjo-menu-bg: #27272a;
3
+ --jikjo-menu-shadow: #00000080;
4
+ --jikjo-menu-item-text: #a1a1aa;
5
+ --jikjo-menu-item-text-active: #f4f4f5;
6
+ --jikjo-menu-item-bg-active: #3f3f46b3;
7
+ --jikjo-menu-divider: #52525b80;
8
+ --jikjo-accent: #818cf8;
9
+ --jikjo-btn-text: #a1a1aa;
10
+ --jikjo-btn-bg-hover: #3f3f46b3;
11
+ --jikjo-btn-text-hover: #e4e4e7;
12
+ }
13
+
14
+ .jikjo-bubble-menu {
15
+ background: var(--jikjo-menu-bg);
16
+ box-shadow: 0 20px 25px -5px var(--jikjo-menu-shadow),
17
+ 0 8px 10px -6px var(--jikjo-menu-shadow);
18
+ border-radius: 8px;
19
+ align-items: center;
20
+ gap: 2px;
21
+ padding: 6px;
22
+ display: flex;
23
+ }
24
+
25
+ .jikjo-bubble-menu__divider {
26
+ background: var(--jikjo-menu-divider);
27
+ flex-shrink: 0;
28
+ width: 1px;
29
+ height: 16px;
30
+ margin: 0 2px;
31
+ }
32
+
33
+ .jikjo-bubble-menu__btn {
34
+ cursor: pointer;
35
+ width: 32px;
36
+ height: 32px;
37
+ color: var(--jikjo-menu-item-text);
38
+ background: none;
39
+ border: none;
40
+ border-radius: 6px;
41
+ justify-content: center;
42
+ align-items: center;
43
+ transition: background 75ms, color 75ms;
44
+ display: flex;
45
+ }
46
+
47
+ .jikjo-bubble-menu__btn:hover {
48
+ color: var(--jikjo-btn-text-hover);
49
+ background: #ffffff1a;
50
+ }
51
+
52
+ .jikjo-bubble-menu__btn[aria-pressed="true"], .jikjo-bubble-menu__btn--active {
53
+ color: #fff;
54
+ background: #ffffff26;
55
+ }
56
+
57
+ .jikjo-block-toolbar__btn {
58
+ color: var(--jikjo-btn-text);
59
+ cursor: pointer;
60
+ background: none;
61
+ border: none;
62
+ border-radius: 4px;
63
+ justify-content: center;
64
+ align-items: center;
65
+ padding: 0;
66
+ transition: background 80ms, color 80ms;
67
+ display: flex;
68
+ }
69
+
70
+ .jikjo-block-toolbar__btn:hover, .jikjo-block-toolbar__btn--hovered {
71
+ background: var(--jikjo-btn-bg-hover);
72
+ color: var(--jikjo-btn-text-hover);
73
+ }
74
+
75
+ .jikjo-block-toolbar__cursor-indicator {
76
+ background: var(--jikjo-accent);
77
+ pointer-events: none;
78
+ z-index: 2;
79
+ border-radius: 1px;
80
+ width: 2px;
81
+ position: absolute;
82
+ left: 0;
83
+ }
84
+
85
+ .jikjo-block-toolbar__drop-line {
86
+ pointer-events: none;
87
+ z-index: 3;
88
+ align-items: center;
89
+ height: 2px;
90
+ display: flex;
91
+ position: absolute;
92
+ left: 0;
93
+ right: 0;
94
+ transform: translateY(-1px);
95
+ }
96
+
97
+ .jikjo-block-toolbar__drop-line-dot {
98
+ background: var(--jikjo-accent);
99
+ border-radius: 50%;
100
+ flex-shrink: 0;
101
+ width: 6px;
102
+ height: 6px;
103
+ margin-left: 4px;
104
+ }
105
+
106
+ .jikjo-block-toolbar__drop-line-bar {
107
+ background: var(--jikjo-accent);
108
+ flex: 1;
109
+ height: 2px;
110
+ margin-right: 8px;
111
+ }
112
+
113
+ .jikjo-menu-panel {
114
+ background: var(--jikjo-menu-bg);
115
+ box-shadow: 0 20px 25px -5px var(--jikjo-menu-shadow),
116
+ 0 8px 10px -6px var(--jikjo-menu-shadow);
117
+ border-radius: 8px;
118
+ padding: 6px 0;
119
+ overflow: hidden;
120
+ }
121
+
122
+ .jikjo-menu-panel__list {
123
+ flex-direction: column;
124
+ width: 100%;
125
+ padding: 0 6px;
126
+ display: flex;
127
+ }
128
+
129
+ .jikjo-menu-panel__list--scrollable {
130
+ max-height: 288px;
131
+ overflow-y: auto;
132
+ }
133
+
134
+ .jikjo-menu-panel__empty {
135
+ color: var(--jikjo-menu-item-text);
136
+ margin: 0;
137
+ padding: 8px 16px;
138
+ font-size: 12px;
139
+ }
140
+
141
+ .jikjo-menu-panel__item {
142
+ text-align: left;
143
+ cursor: pointer;
144
+ width: 100%;
145
+ color: var(--jikjo-menu-item-text);
146
+ background: none;
147
+ border: none;
148
+ border-radius: 6px;
149
+ outline: none;
150
+ align-items: center;
151
+ gap: 12px;
152
+ padding: 8px 12px;
153
+ transition: background 75ms, color 75ms;
154
+ display: flex;
155
+ }
156
+
157
+ .jikjo-menu-panel__item:hover, .jikjo-menu-panel__item--active {
158
+ background: var(--jikjo-menu-item-bg-active);
159
+ color: var(--jikjo-menu-item-text-active);
160
+ }
161
+
162
+ .jikjo-menu-panel__item-icon {
163
+ color: currentColor;
164
+ flex-shrink: 0;
165
+ justify-content: center;
166
+ align-items: center;
167
+ width: 16px;
168
+ display: flex;
169
+ }
170
+
171
+ .jikjo-menu-panel__item-label {
172
+ font-size: 14px;
173
+ font-weight: 400;
174
+ line-height: 1;
175
+ }
176
+
177
+
178
+ /*# sourceMappingURL=index.css.map*/
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.css","names":[],"sources":["../src/styles/variables.css","../src/styles/bubble-menu.css","../src/styles/block-toolbar.css","../src/styles/menu-panel.css"],"sourcesContent":[":root {\n --jikjo-menu-bg: #27272a;\n --jikjo-menu-shadow: #00000080;\n --jikjo-menu-item-text: #a1a1aa;\n --jikjo-menu-item-text-active: #f4f4f5;\n --jikjo-menu-item-bg-active: #3f3f46b3;\n --jikjo-menu-divider: #52525b80;\n --jikjo-accent: #818cf8;\n --jikjo-btn-text: #a1a1aa;\n --jikjo-btn-bg-hover: #3f3f46b3;\n --jikjo-btn-text-hover: #e4e4e7;\n}\n",".jikjo-bubble-menu {\n background: var(--jikjo-menu-bg);\n box-shadow: 0 20px 25px -5px var(--jikjo-menu-shadow),\n 0 8px 10px -6px var(--jikjo-menu-shadow);\n border-radius: 8px;\n align-items: center;\n gap: 2px;\n padding: 6px;\n display: flex;\n}\n\n.jikjo-bubble-menu__divider {\n background: var(--jikjo-menu-divider);\n flex-shrink: 0;\n width: 1px;\n height: 16px;\n margin: 0 2px;\n}\n\n.jikjo-bubble-menu__btn {\n cursor: pointer;\n width: 32px;\n height: 32px;\n color: var(--jikjo-menu-item-text);\n background: none;\n border: none;\n border-radius: 6px;\n justify-content: center;\n align-items: center;\n transition: background 75ms, color 75ms;\n display: flex;\n}\n\n.jikjo-bubble-menu__btn:hover {\n color: var(--jikjo-btn-text-hover);\n background: #ffffff1a;\n}\n\n.jikjo-bubble-menu__btn[aria-pressed=\"true\"], .jikjo-bubble-menu__btn--active {\n color: #fff;\n background: #ffffff26;\n}\n",".jikjo-block-toolbar__btn {\n color: var(--jikjo-btn-text);\n cursor: pointer;\n background: none;\n border: none;\n border-radius: 4px;\n justify-content: center;\n align-items: center;\n padding: 0;\n transition: background 80ms, color 80ms;\n display: flex;\n}\n\n.jikjo-block-toolbar__btn:hover, .jikjo-block-toolbar__btn--hovered {\n background: var(--jikjo-btn-bg-hover);\n color: var(--jikjo-btn-text-hover);\n}\n\n.jikjo-block-toolbar__cursor-indicator {\n background: var(--jikjo-accent);\n pointer-events: none;\n z-index: 2;\n border-radius: 1px;\n width: 2px;\n position: absolute;\n left: 0;\n}\n\n.jikjo-block-toolbar__drop-line {\n pointer-events: none;\n z-index: 3;\n align-items: center;\n height: 2px;\n display: flex;\n position: absolute;\n left: 0;\n right: 0;\n transform: translateY(-1px);\n}\n\n.jikjo-block-toolbar__drop-line-dot {\n background: var(--jikjo-accent);\n border-radius: 50%;\n flex-shrink: 0;\n width: 6px;\n height: 6px;\n margin-left: 4px;\n}\n\n.jikjo-block-toolbar__drop-line-bar {\n background: var(--jikjo-accent);\n flex: 1;\n height: 2px;\n margin-right: 8px;\n}\n",".jikjo-menu-panel {\n background: var(--jikjo-menu-bg);\n box-shadow: 0 20px 25px -5px var(--jikjo-menu-shadow),\n 0 8px 10px -6px var(--jikjo-menu-shadow);\n border-radius: 8px;\n padding: 6px 0;\n overflow: hidden;\n}\n\n.jikjo-menu-panel__list {\n flex-direction: column;\n width: 100%;\n padding: 0 6px;\n display: flex;\n}\n\n.jikjo-menu-panel__list--scrollable {\n max-height: 288px;\n overflow-y: auto;\n}\n\n.jikjo-menu-panel__empty {\n color: var(--jikjo-menu-item-text);\n margin: 0;\n padding: 8px 16px;\n font-size: 12px;\n}\n\n.jikjo-menu-panel__item {\n text-align: left;\n cursor: pointer;\n width: 100%;\n color: var(--jikjo-menu-item-text);\n background: none;\n border: none;\n border-radius: 6px;\n outline: none;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n transition: background 75ms, color 75ms;\n display: flex;\n}\n\n.jikjo-menu-panel__item:hover, .jikjo-menu-panel__item--active {\n background: var(--jikjo-menu-item-bg-active);\n color: var(--jikjo-menu-item-text-active);\n}\n\n.jikjo-menu-panel__item-icon {\n color: currentColor;\n flex-shrink: 0;\n justify-content: center;\n align-items: center;\n width: 16px;\n display: flex;\n}\n\n.jikjo-menu-panel__item-label {\n font-size: 14px;\n font-weight: 400;\n line-height: 1;\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACXA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;ACzCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;ACtDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/editor-ui.tsx","../src/components/bubble-menu.tsx","../src/components/slash-menu.tsx","../src/components/block-toolbar.tsx"],"sourcesContent":[],"mappings":";;;;;;;UAqGiB,aAAA;eACF;;;;AADf;;;gBAQmB,CAAA,EAAA,SAAA,GAAA,KAAA;WAMN,CAAA,EAAA,MAAA;EAAK;AA4QlB;;;UAEE,CAAA,EA9QW,KA8QX,CAAA,aAAA,GAAA,WAAA,GAAA,cAAA,GAAA,YAAA,CAAA;;AAEA,iBAJc,QAAA,CAId;EAAA,UAAA;EAAA,SAAA;EAAA,cAAA;EAAA,SAAA;EAAA;AAAA,CAAA,EAEC,aAFD,CAAA,EAEc,kBAAA,CAAA,GAAA,CAAA,OAFd;;;;UCxXe,eAAA;;UAEP;iCACuB;UACvB;ADsFV;AAA8B,iBCpBd,UAAA,CDoBc;EAAA,SAAA;EAAA,MAAA;EAAA,cAAA;EAAA;AAAA,CAAA,ECf3B,eDe2B,CAAA,ECfZ,MAAA,CAAA,WAAA,GDeY,IAAA;;;;UEzFb,cAAA;;;SAGR;UACC;EFqFO,OAAA,EAAA,GAAA,GAAA,IAAa;;AACf,iBExCC,SAAA,CFwCD;EAAA,SAAA;EAAA,KAAA;EAAA,KAAA;EAAA,MAAA;EAAA;AAAA,CAAA,EElCZ,cFkCY,CAAA,EElCE,MAAA,CAAA,WAAA,GFkCF,IAAA;;;;UGvEE,iBAAA;;;;;EHsEA,KAAA,EGjER,aHiEqB,EAAA;EAAA,MAAA,EGhEpB,aHgEoB;gBACf,CAAA,EAAA,OAAA;eAOI,CAAA,EAAA,OAAA;;EAMD,aAAA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EA4QF;EAAQ,SAAA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;;AAEtB,iBG7Rc,YAAA,CH6Rd;EAAA,SAAA;EAAA,OAAA;EAAA,cAAA;EAAA,KAAA;EAAA,MAAA;EAAA,cAAA;EAAA,aAAA;EAAA,aAAA;EAAA;AAAA,CAAA,EGnRC,iBHmRD,CAAA,EGnRkB,kBAAA,CAAA,GAAA,CAAA,OAAA,GHmRlB,IAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/editor-ui.tsx","../src/components/bubble-menu.tsx","../src/components/slash-menu.tsx","../src/components/block-toolbar.tsx"],"sourcesContent":[],"mappings":";;;;;;;UAqGiB,aAAA;eACF;;;;AADf;;;gBAQmB,CAAA,EAAA,SAAA,GAAA,KAAA;WAMN,CAAA,EAAA,MAAA;EAAK;AA4QlB;;;UAEE,CAAA,EA9QW,KA8QX,CAAA,aAAA,GAAA,WAAA,GAAA,cAAA,GAAA,YAAA,CAAA;;AAEA,iBAJc,QAAA,CAId;EAAA,UAAA;EAAA,SAAA;EAAA,cAAA;EAAA,SAAA;EAAA;AAAA,CAAA,EAEC,aAFD,CAAA,EAEc,kBAAA,CAAA,GAAA,CAAA,OAFd;;;;UCtXe,eAAA;;UAEP;EDsFO,cAAA,EAAa,CAAA,MAAA,EAAA,MCrFG,oBDqFH,EAAA,GAAA,IAAA;EAAA,MAAA,ECpFpB,aDoFoB;;AAQX,iBChCH,UAAA,CDgCG;EAAA,SAAA;EAAA,MAAA;EAAA,cAAA;EAAA;AAAA,CAAA,EC3BhB,eD2BgB,CAAA,EC3BD,MAAA,CAAA,WAAA,GD2BC,IAAA;;;;UE/FF,cAAA;;;EFuFA,KAAA,EEpFR,aFoFqB,EAAA;EAAA,MAAA,EEnFpB,aFmFoB;SACf,EAAA,GAAA,GAAA,IAAA;;AAaF,iBEnDG,SAAA,CFmDH;EAAA,SAAA;EAAA,KAAA;EAAA,KAAA;EAAA,MAAA;EAAA;AAAA,CAAA,EE7CV,cF6CU,CAAA,EE7CI,MAAA,CAAA,WAAA,GF6CJ,IAAA;;;;UGjFI,iBAAA;;EHmEA,OAAA,EAAA,MAAA,GAAa,IAAA;EAAA;gBACf,EAAA,MAAA,GAAA,IAAA;OAOI,EGtEV,aHsEU,EAAA;QAMN,EG3EH,aH2EG;EAAK,cAAA,CAAA,EAAA,OAAA;EA4QF,aAAQ,CAAA,EAAA,OAAA;EAAA;eACtB,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;;WAEA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;;AAEA,iBG9Sc,YAAA,CH8Sd;EAAA,SAAA;EAAA,OAAA;EAAA,cAAA;EAAA,KAAA;EAAA,MAAA;EAAA,cAAA;EAAA,aAAA;EAAA,aAAA;EAAA;AAAA,CAAA,EGpSC,iBHoSD,CAAA,EGpSkB,kBAAA,CAAA,GAAA,CAAA,OAAA,GHoSlB,IAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/editor-ui.tsx","../src/components/bubble-menu.tsx","../src/components/slash-menu.tsx","../src/components/block-toolbar.tsx"],"sourcesContent":[],"mappings":";;;;;;;UAqGiB,aAAA;eACF;;;;AADf;;;gBAQmB,CAAA,EAAA,SAAA,GAAA,KAAA;WAMN,CAAA,EAAA,MAAA;EAAK;AA4QlB;;;UAEE,CAAA,EA9QW,KA8QX,CAAA,aAAA,GAAA,WAAA,GAAA,cAAA,GAAA,YAAA,CAAA;;AAEA,iBAJc,QAAA,CAId;EAAA,UAAA;EAAA,SAAA;EAAA,cAAA;EAAA,SAAA;EAAA;AAAA,CAAA,EAEC,aAFD,CAAA,EAEc,kBAAA,CAAA,GAAA,CAAA,OAFd;;;;UCxXe,eAAA;;UAEP;iCACuB;UACvB;ADsFV;AAA8B,iBCpBd,UAAA,CDoBc;EAAA,SAAA;EAAA,MAAA;EAAA,cAAA;EAAA;AAAA,CAAA,ECf3B,eDe2B,CAAA,ECfZ,MAAA,CAAA,WAAA,GDeY,IAAA;;;;UEzFb,cAAA;;;SAGR;UACC;EFqFO,OAAA,EAAA,GAAA,GAAA,IAAa;;AACf,iBExCC,SAAA,CFwCD;EAAA,SAAA;EAAA,KAAA;EAAA,KAAA;EAAA,MAAA;EAAA;AAAA,CAAA,EElCZ,cFkCY,CAAA,EElCE,MAAA,CAAA,WAAA,GFkCF,IAAA;;;;UGvEE,iBAAA;;;;;EHsEA,KAAA,EGjER,aHiEqB,EAAA;EAAA,MAAA,EGhEpB,aHgEoB;gBACf,CAAA,EAAA,OAAA;eAOI,CAAA,EAAA,OAAA;;EAMD,aAAA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EA4QF;EAAQ,SAAA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;;AAEtB,iBG7Rc,YAAA,CH6Rd;EAAA,SAAA;EAAA,OAAA;EAAA,cAAA;EAAA,KAAA;EAAA,MAAA;EAAA,cAAA;EAAA,aAAA;EAAA,aAAA;EAAA;AAAA,CAAA,EGnRC,iBHmRD,CAAA,EGnRkB,kBAAA,CAAA,GAAA,CAAA,OAAA,GHmRlB,IAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/editor-ui.tsx","../src/components/bubble-menu.tsx","../src/components/slash-menu.tsx","../src/components/block-toolbar.tsx"],"sourcesContent":[],"mappings":";;;;;;;UAqGiB,aAAA;eACF;;;;AADf;;;gBAQmB,CAAA,EAAA,SAAA,GAAA,KAAA;WAMN,CAAA,EAAA,MAAA;EAAK;AA4QlB;;;UAEE,CAAA,EA9QW,KA8QX,CAAA,aAAA,GAAA,WAAA,GAAA,cAAA,GAAA,YAAA,CAAA;;AAEA,iBAJc,QAAA,CAId;EAAA,UAAA;EAAA,SAAA;EAAA,cAAA;EAAA,SAAA;EAAA;AAAA,CAAA,EAEC,aAFD,CAAA,EAEc,kBAAA,CAAA,GAAA,CAAA,OAFd;;;;UCtXe,eAAA;;UAEP;EDsFO,cAAA,EAAa,CAAA,MAAA,EAAA,MCrFG,oBDqFH,EAAA,GAAA,IAAA;EAAA,MAAA,ECpFpB,aDoFoB;;AAQX,iBChCH,UAAA,CDgCG;EAAA,SAAA;EAAA,MAAA;EAAA,cAAA;EAAA;AAAA,CAAA,EC3BhB,eD2BgB,CAAA,EC3BD,MAAA,CAAA,WAAA,GD2BC,IAAA;;;;UE/FF,cAAA;;;EFuFA,KAAA,EEpFR,aFoFqB,EAAA;EAAA,MAAA,EEnFpB,aFmFoB;SACf,EAAA,GAAA,GAAA,IAAA;;AAaF,iBEnDG,SAAA,CFmDH;EAAA,SAAA;EAAA,KAAA;EAAA,KAAA;EAAA,MAAA;EAAA;AAAA,CAAA,EE7CV,cF6CU,CAAA,EE7CI,MAAA,CAAA,WAAA,GF6CJ,IAAA;;;;UGjFI,iBAAA;;EHmEA,OAAA,EAAA,MAAA,GAAa,IAAA;EAAA;gBACf,EAAA,MAAA,GAAA,IAAA;OAOI,EGtEV,aHsEU,EAAA;QAMN,EG3EH,aH2EG;EAAK,cAAA,CAAA,EAAA,OAAA;EA4QF,aAAQ,CAAA,EAAA,OAAA;EAAA;eACtB,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;;WAEA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;;AAEA,iBG9Sc,YAAA,CH8Sd;EAAA,SAAA;EAAA,OAAA;EAAA,cAAA;EAAA,KAAA;EAAA,MAAA;EAAA,cAAA;EAAA,aAAA;EAAA,aAAA;EAAA;AAAA,CAAA,EGpSC,iBHoSD,CAAA,EGpSkB,kBAAA,CAAA,GAAA,CAAA,OAAA,GHoSlB,IAAA"}
package/dist/index.js CHANGED
@@ -31,11 +31,7 @@ function Btn({ active, label, onMouseDown, children }) {
31
31
  type: "button",
32
32
  "aria-label": label,
33
33
  onMouseDown,
34
- className: [
35
- "flex items-center justify-center w-8 h-8 rounded-md",
36
- "transition-colors duration-75",
37
- active ? "bg-white/15 text-white" : "text-zinc-400 hover:bg-white/10 hover:text-zinc-200"
38
- ].join(" "),
34
+ className: `jikjo-bubble-menu__btn${active ? " jikjo-bubble-menu__btn--active" : ""}`,
39
35
  children
40
36
  });
41
37
  }
@@ -91,7 +87,7 @@ function BubbleMenu({ isVisible, format, onToggleFormat, editor }) {
91
87
  duration: .1,
92
88
  ease: "easeOut"
93
89
  },
94
- className: "flex items-center gap-0.5 px-1.5 py-1.5 rounded-lg bg-zinc-800 shadow-xl shadow-black/50",
90
+ className: "jikjo-bubble-menu",
95
91
  children: [
96
92
  /* @__PURE__ */ jsx(Btn, {
97
93
  active: format.bold,
@@ -129,7 +125,7 @@ function BubbleMenu({ isVisible, format, onToggleFormat, editor }) {
129
125
  strokeWidth: 2
130
126
  })
131
127
  }),
132
- /* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-zinc-600/50 mx-0.5 shrink-0" }),
128
+ /* @__PURE__ */ jsx("div", { className: "jikjo-bubble-menu__divider" }),
133
129
  /* @__PURE__ */ jsx(Btn, {
134
130
  active: format.strikethrough,
135
131
  label: "Strikethrough",
@@ -165,22 +161,6 @@ const BTN = 24;
165
161
  const GAP = 4;
166
162
  const GUTTER_LEFT = 6;
167
163
  GUTTER_LEFT + 2 * BTN + GAP;
168
- const btnBase = {
169
- display: "flex",
170
- alignItems: "center",
171
- justifyContent: "center",
172
- borderRadius: 4,
173
- border: "none",
174
- background: "transparent",
175
- color: "#a1a1aa",
176
- cursor: "pointer",
177
- padding: 0,
178
- transition: "background 80ms, color 80ms"
179
- };
180
- const btnHover = {
181
- background: "rgba(63,63,70,0.7)",
182
- color: "#e4e4e7"
183
- };
184
164
  const ICON_MAP$1 = {
185
165
  paragraph: /* @__PURE__ */ jsx(Text, {
186
166
  size: 13,
@@ -445,31 +425,12 @@ function BlockToolbar({ isVisible, nodeKey, focusedNodeKey, items, editor, showD
445
425
  setPanelOpen((p) => !p);
446
426
  }, []);
447
427
  if (!container) return null;
448
- const dragBtnStyle = {
449
- ...btnBase,
450
- width: BTN,
451
- height: BTN,
452
- cursor: "grab",
453
- ...dragHovered ? btnHover : {}
454
- };
455
- const addBtnStyle = {
456
- ...btnBase,
457
- width: BTN,
458
- height: BTN,
459
- ...panelOpen || addHovered ? btnHover : {}
460
- };
461
428
  return /* @__PURE__ */ jsxs(Fragment, { children: [
462
429
  createPortal(/* @__PURE__ */ jsx(AnimatePresence, { children: focusedTop !== null && /* @__PURE__ */ jsx(motion.div, {
430
+ className: "jikjo-block-toolbar__cursor-indicator",
463
431
  style: {
464
- position: "absolute",
465
432
  top: focusedTop,
466
- left: 0,
467
- width: 2,
468
- height: focusedHeight,
469
- background: "#818cf8",
470
- borderRadius: 1,
471
- zIndex: 2,
472
- pointerEvents: "none"
433
+ height: focusedHeight
473
434
  },
474
435
  initial: { opacity: 0 },
475
436
  animate: { opacity: 1 },
@@ -501,7 +462,12 @@ function BlockToolbar({ isVisible, nodeKey, focusedNodeKey, items, editor, showD
501
462
  "data-drag-handle": true,
502
463
  onDragStart: handleDragStart,
503
464
  "aria-label": "Drag to reorder",
504
- style: dragBtnStyle,
465
+ className: `jikjo-block-toolbar__btn${dragHovered ? " jikjo-block-toolbar__btn--hovered" : ""}`,
466
+ style: {
467
+ width: BTN,
468
+ height: BTN,
469
+ cursor: "grab"
470
+ },
505
471
  onMouseEnter: () => setDragHovered(true),
506
472
  onMouseLeave: () => setDragHovered(false),
507
473
  children: /* @__PURE__ */ jsx(GripVertical, {
@@ -513,7 +479,11 @@ function BlockToolbar({ isVisible, nodeKey, focusedNodeKey, items, editor, showD
513
479
  type: "button",
514
480
  "aria-label": "Add block",
515
481
  "aria-expanded": panelOpen,
516
- style: addBtnStyle,
482
+ className: `jikjo-block-toolbar__btn${panelOpen || addHovered ? " jikjo-block-toolbar__btn--hovered" : ""}`,
483
+ style: {
484
+ width: BTN,
485
+ height: BTN
486
+ },
517
487
  onMouseDown: handleAddClick,
518
488
  onMouseEnter: () => setAddHovered(true),
519
489
  onMouseLeave: () => setAddHovered(false),
@@ -524,35 +494,13 @@ function BlockToolbar({ isVisible, nodeKey, focusedNodeKey, items, editor, showD
524
494
  })]
525
495
  }, nodeKey ?? "toolbar") }), container),
526
496
  createPortal(/* @__PURE__ */ jsx(AnimatePresence, { children: dropLine !== null && /* @__PURE__ */ jsxs(motion.div, {
527
- style: {
528
- position: "absolute",
529
- top: dropLine.top,
530
- left: 0,
531
- right: 0,
532
- height: 2,
533
- zIndex: 3,
534
- pointerEvents: "none",
535
- display: "flex",
536
- alignItems: "center",
537
- transform: "translateY(-1px)"
538
- },
497
+ className: "jikjo-block-toolbar__drop-line",
498
+ style: { top: dropLine.top },
539
499
  initial: { opacity: 0 },
540
500
  animate: { opacity: 1 },
541
501
  exit: { opacity: 0 },
542
502
  transition: { duration: .06 },
543
- children: [/* @__PURE__ */ jsx("div", { style: {
544
- width: 6,
545
- height: 6,
546
- borderRadius: "50%",
547
- background: "#818cf8",
548
- flexShrink: 0,
549
- marginLeft: 4
550
- } }), /* @__PURE__ */ jsx("div", { style: {
551
- flex: 1,
552
- height: 2,
553
- background: "#818cf8",
554
- marginRight: 8
555
- } })]
503
+ children: [/* @__PURE__ */ jsx("div", { className: "jikjo-block-toolbar__drop-line-dot" }), /* @__PURE__ */ jsx("div", { className: "jikjo-block-toolbar__drop-line-bar" })]
556
504
  }, "drop-line") }), container),
557
505
  createPortal(/* @__PURE__ */ jsx(AnimatePresence, { children: isVisible && panelOpen && top !== null && /* @__PURE__ */ jsx(motion.div, {
558
506
  ref: panelRef,
@@ -582,11 +530,11 @@ function BlockToolbar({ isVisible, nodeKey, focusedNodeKey, items, editor, showD
582
530
  },
583
531
  role: "dialog",
584
532
  "aria-label": "Insert block",
585
- className: "rounded-lg bg-zinc-800 shadow-xl shadow-black/50 py-1.5",
533
+ className: "jikjo-menu-panel",
586
534
  children: /* @__PURE__ */ jsx("div", {
587
535
  role: "listbox",
588
536
  "aria-label": "Block type",
589
- className: "w-full px-1.5 flex flex-col",
537
+ className: "jikjo-menu-panel__list",
590
538
  children: items.map((item, index) => {
591
539
  const icon = ICON_MAP$1[item.id] ?? item.icon;
592
540
  const isActive = index === activeIndex;
@@ -602,16 +550,12 @@ function BlockToolbar({ isVisible, nodeKey, focusedNodeKey, items, editor, showD
602
550
  });
603
551
  },
604
552
  onMouseEnter: () => setActiveIndex(index),
605
- className: [
606
- "w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors duration-75",
607
- "outline-none",
608
- isActive ? "bg-zinc-700/60 text-zinc-100" : "text-zinc-400 hover:bg-zinc-700/40 hover:text-zinc-200"
609
- ].join(" "),
553
+ className: `jikjo-menu-panel__item${isActive ? " jikjo-menu-panel__item--active" : ""}`,
610
554
  children: [/* @__PURE__ */ jsx("span", {
611
- className: "flex items-center justify-center shrink-0 w-4 text-current",
555
+ className: "jikjo-menu-panel__item-icon",
612
556
  children: icon
613
557
  }), /* @__PURE__ */ jsx("span", {
614
- className: "text-sm font-normal leading-none",
558
+ className: "jikjo-menu-panel__item-label",
615
559
  children: item.label
616
560
  })]
617
561
  }, item.id);
@@ -756,9 +700,9 @@ function SlashMenu({ isVisible, query, items, editor, onClose }) {
756
700
  },
757
701
  role: "dialog",
758
702
  "aria-label": "Insert block",
759
- className: "rounded-lg bg-zinc-800 shadow-xl shadow-black/50 py-1.5 overflow-hidden",
703
+ className: "jikjo-menu-panel",
760
704
  children: filtered.length === 0 ? /* @__PURE__ */ jsxs("p", {
761
- className: "px-4 py-2 text-xs text-zinc-500",
705
+ className: "jikjo-menu-panel__empty",
762
706
  children: [
763
707
  "No results for “",
764
708
  query,
@@ -768,7 +712,7 @@ function SlashMenu({ isVisible, query, items, editor, onClose }) {
768
712
  ref: containerRef,
769
713
  role: "listbox",
770
714
  "aria-label": "Block type",
771
- className: "w-full max-h-72 overflow-y-auto px-1.5 flex flex-col",
715
+ className: "jikjo-menu-panel__list jikjo-menu-panel__list--scrollable",
772
716
  children: filtered.map((item, index) => {
773
717
  const icon = ICON_MAP[item.id] ?? item.icon;
774
718
  const isActive = index === activeIndex;
@@ -785,16 +729,12 @@ function SlashMenu({ isVisible, query, items, editor, onClose }) {
785
729
  });
786
730
  },
787
731
  onMouseEnter: () => setActiveIndex(index),
788
- className: [
789
- "w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors duration-75",
790
- "outline-none focus-visible:outline-none",
791
- isActive ? "bg-zinc-700/80 text-zinc-100" : "text-zinc-400 hover:bg-zinc-700/60 hover:text-zinc-200 focus-visible:bg-zinc-700/80 focus-visible:text-zinc-100"
792
- ].join(" "),
732
+ className: `jikjo-menu-panel__item${isActive ? " jikjo-menu-panel__item--active" : ""}`,
793
733
  children: [/* @__PURE__ */ jsx("span", {
794
- className: "flex items-center justify-center shrink-0 w-4 text-current",
734
+ className: "jikjo-menu-panel__item-icon",
795
735
  children: icon
796
736
  }), /* @__PURE__ */ jsx("span", {
797
- className: "text-sm font-normal leading-none",
737
+ className: "jikjo-menu-panel__item-label",
798
738
  children: item.label
799
739
  })]
800
740
  }, item.id);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["btnBase: CSSProperties","btnHover: CSSProperties","ICON_MAP: Record<string, React.ReactNode>","targetKey: string | null","top","dragBtnStyle: CSSProperties","addBtnStyle: CSSProperties","ICON_MAP","ICON_MAP: Record<string, ReactNode>","tbtnBase: CSSProperties","tbtnHover: CSSProperties","tbtnActive: CSSProperties","style: CSSProperties","defaultExtensions: Extension[]","key: string | null"],"sources":["../src/components/bubble-menu.tsx","../src/components/block-toolbar.tsx","../src/components/slash-menu.tsx","../src/editor-ui.tsx"],"sourcesContent":["\"use client\";\n\nimport type { SelectionFormatState } from \"@jikjo/core\";\nimport type { LexicalEditor } from \"lexical\";\nimport { Bold, Code, Italic, Strikethrough, Underline } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { useEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface BubbleMenuProps {\n isVisible: boolean;\n format: SelectionFormatState;\n onToggleFormat: (format: keyof SelectionFormatState) => void;\n editor: LexicalEditor;\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\ninterface AbsolutePos {\n top: number;\n left: number;\n}\n\n/**\n * selection rect를 컨테이너 기준 absolute 좌표로 변환.\n * 컨테이너가 스크롤되어도 absolute 요소는 함께 움직인다.\n */\nfunction getSelectionAbsPos(container: HTMLElement): AbsolutePos | null {\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n const range = sel.getRangeAt(0);\n if (range.collapsed) return null;\n\n const selRect = range.getBoundingClientRect();\n if (selRect.width === 0) return null;\n\n const cr = container.getBoundingClientRect();\n const MENU_HEIGHT = 36;\n const GAP = 6;\n\n return {\n top: selRect.top - cr.top + container.scrollTop - MENU_HEIGHT - GAP,\n left: selRect.left - cr.left + container.scrollLeft + selRect.width / 2,\n };\n}\n\n// ─── Button ───────────────────────────────────────────────────────────────────\n\nfunction Btn({\n active,\n label,\n onMouseDown,\n children,\n}: {\n active: boolean;\n label: string;\n onMouseDown: (e: React.MouseEvent) => void;\n children: React.ReactNode;\n}) {\n return (\n <button\n type=\"button\"\n aria-label={label}\n onMouseDown={onMouseDown}\n className={[\n \"flex items-center justify-center w-8 h-8 rounded-md\",\n \"transition-colors duration-75\",\n active\n ? \"bg-white/15 text-white\"\n : \"text-zinc-400 hover:bg-white/10 hover:text-zinc-200\",\n ].join(\" \")}\n >\n {children}\n </button>\n );\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport function BubbleMenu({\n isVisible,\n format,\n onToggleFormat,\n editor,\n}: BubbleMenuProps) {\n const [container, setContainer] = useState<HTMLElement | null>(null);\n const [pos, setPos] = useState<AbsolutePos | null>(null);\n\n // editor.getRootElement()가 준비된 후 container를 설정\n useEffect(() => {\n function attachContainer() {\n const rootEl = editor.getRootElement();\n if (!rootEl) return;\n const parent = rootEl.parentElement;\n if (parent) setContainer(parent);\n }\n attachContainer();\n return editor.registerRootListener(attachContainer);\n }, [editor]);\n\n // selection 변화 또는 isVisible 변화 시 좌표 재계산.\n // isVisible=false 될 때만 pos를 null로 초기화 (format 적용 중에는 이전 pos 유지).\n useEffect(() => {\n if (!isVisible || !container) {\n setPos(null);\n return;\n }\n const newPos = getSelectionAbsPos(container);\n // newPos가 null이면 이전 pos 유지 (format 적용 직후 DOM rect가 잠시 없을 때 대비)\n if (newPos !== null) {\n setPos(newPos);\n }\n }, [isVisible, container]);\n\n if (!container) return null;\n\n const menuStyle = pos\n ? { position: \"absolute\" as const, top: pos.top, left: pos.left, x: \"-50%\", zIndex: 50 }\n : { position: \"absolute\" as const, top: -9999, left: -9999, zIndex: 50 };\n\n return createPortal(\n <AnimatePresence>\n {isVisible && (\n <motion.div\n style={menuStyle}\n initial={{ opacity: 0, y: 6 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 6 }}\n transition={{ duration: 0.1, ease: \"easeOut\" }}\n className=\"flex items-center gap-0.5 px-1.5 py-1.5 rounded-lg bg-zinc-800 shadow-xl shadow-black/50\"\n >\n <Btn\n active={format.bold}\n label=\"Bold\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"bold\");\n }}\n >\n <Bold size={13} strokeWidth={2.5} />\n </Btn>\n <Btn\n active={format.italic}\n label=\"Italic\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"italic\");\n }}\n >\n <Italic size={13} strokeWidth={2} />\n </Btn>\n <Btn\n active={format.underline}\n label=\"Underline\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"underline\");\n }}\n >\n <Underline size={13} strokeWidth={2} />\n </Btn>\n\n <div className=\"w-px h-4 bg-zinc-600/50 mx-0.5 shrink-0\" />\n\n <Btn\n active={format.strikethrough}\n label=\"Strikethrough\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"strikethrough\");\n }}\n >\n <Strikethrough size={13} strokeWidth={2} />\n </Btn>\n <Btn\n active={format.code}\n label=\"Code\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"code\");\n }}\n >\n <Code size={13} strokeWidth={2} />\n </Btn>\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n );\n}\n","\"use client\";\n\nimport type { SlashMenuItem } from \"@jikjo/core\";\nimport type { LexicalEditor } from \"lexical\";\nimport {\n $getNearestNodeFromDOMNode,\n $getNodeByKey,\n $isElementNode,\n COMMAND_PRIORITY_HIGH,\n COMMAND_PRIORITY_LOW,\n DRAGOVER_COMMAND,\n DROP_COMMAND,\n} from \"lexical\";\nimport {\n GripVertical,\n Heading1,\n Heading2,\n Heading3,\n List,\n ListOrdered,\n ListTodo,\n Plus,\n Quote,\n Text,\n} from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { useCallback, useEffect, useRef, useState, type CSSProperties } from \"react\";\nimport { createPortal } from \"react-dom\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface BlockToolbarProps {\n isVisible: boolean;\n nodeKey: string | null;\n /** 현재 커서(포커스)가 있는 블록의 nodeKey */\n focusedNodeKey: string | null;\n items: SlashMenuItem[];\n editor: LexicalEditor;\n showDragHandle?: boolean;\n showAddButton?: boolean;\n /** drag 시작 시 드래그 중인 블록의 nodeKey를 부모에 전달 */\n onDragStarted?: (nodeKey: string) => void;\n /** drag & drop 완료 후 이동된 블록의 nodeKey를 부모에 전달 */\n onDropped?: (nodeKey: string) => void;\n}\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst DRAG_DATA_FORMAT = \"application/x-lexical-drag-block\";\nconst BTN = 24;\nconst GAP = 4;\nconst GUTTER_LEFT = 6;\n// 버튼 그룹 전체 너비 (drag + add 기준): GUTTER_LEFT + 2*BTN + GAP\nconst TOOLBAR_WIDTH = GUTTER_LEFT + 2 * BTN + GAP; // 58px\n\n// ─── Inline styles ────────────────────────────────────────────────────────────\n\nconst btnBase: CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: 4,\n border: \"none\",\n background: \"transparent\",\n color: \"#a1a1aa\",\n cursor: \"pointer\",\n padding: 0,\n transition: \"background 80ms, color 80ms\",\n};\n\nconst btnHover: CSSProperties = {\n background: \"rgba(63,63,70,0.7)\",\n color: \"#e4e4e7\",\n};\n\n// ─── Icon map ─────────────────────────────────────────────────────────────────\n\nconst ICON_MAP: Record<string, React.ReactNode> = {\n paragraph: <Text size={13} strokeWidth={1.75} />,\n heading1: <Heading1 size={13} strokeWidth={1.75} />,\n heading2: <Heading2 size={13} strokeWidth={1.75} />,\n heading3: <Heading3 size={13} strokeWidth={1.75} />,\n bulletList: <List size={13} strokeWidth={1.75} />,\n orderedList: <ListOrdered size={13} strokeWidth={1.75} />,\n taskList: <ListTodo size={13} strokeWidth={1.75} />,\n quote: <Quote size={13} strokeWidth={1.75} />,\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/** blockEl의 top을 container 기준 절대 좌표로 계산 (getBoundingClientRect 기반) */\nfunction getOffsetTopToContainer(blockEl: HTMLElement, container: HTMLElement): number {\n const blockRect = blockEl.getBoundingClientRect();\n const containerRect = container.getBoundingClientRect();\n // container의 scrollTop도 반영 (container가 스크롤 가능한 경우)\n return blockRect.top - containerRect.top + container.scrollTop;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport function BlockToolbar({\n isVisible,\n nodeKey,\n focusedNodeKey,\n items,\n editor,\n showDragHandle = true,\n showAddButton = true,\n onDragStarted,\n onDropped,\n}: BlockToolbarProps) {\n const [panelOpen, setPanelOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(0);\n const [container, setContainer] = useState<HTMLElement | null>(null);\n const [top, setTop] = useState<number | null>(null);\n const [dragHovered, setDragHovered] = useState(false);\n const [addHovered, setAddHovered] = useState(false);\n\n // 드래그 가이드 라인: container 기준 absolute top + 위치 (before/after)\n const [dropLine, setDropLine] = useState<{ top: number; position: \"before\" | \"after\" } | null>(null);\n // 커서 인디케이터: focusedNodeKey 블록의 위치\n const [focusedTop, setFocusedTop] = useState<number | null>(null);\n const [focusedHeight, setFocusedHeight] = useState<number>(0);\n\n const panelRef = useRef<HTMLDivElement>(null);\n const addBtnRef = useRef<HTMLButtonElement>(null);\n\n const numBtns = (showDragHandle ? 1 : 0) + (showAddButton ? 1 : 0);\n\n // ── 컨테이너 취득 ──────────────────────────────────────────────────────────\n\n useEffect(() => {\n return editor.registerRootListener((rootElement) => {\n setContainer(rootElement?.parentElement ?? null);\n });\n }, [editor]);\n\n // ── 수직 위치 계산 ─────────────────────────────────────────────────────────\n\n useEffect(() => {\n function calc() {\n if (!isVisible || !nodeKey || !container) { setTop(null); return; }\n const blockEl = editor.getElementByKey(nodeKey);\n if (!blockEl) { setTop(null); return; }\n const offset = getOffsetTopToContainer(blockEl, container);\n setTop(offset + (blockEl.offsetHeight - BTN) / 2);\n }\n calc();\n return editor.registerUpdateListener(() => calc());\n }, [isVisible, nodeKey, editor, container]);\n\n // ── 커서 인디케이터 위치 계산 ──────────────────────────────────────────────\n\n useEffect(() => {\n function calc() {\n if (!focusedNodeKey || !container) { setFocusedTop(null); return; }\n const blockEl = editor.getElementByKey(focusedNodeKey);\n if (!blockEl) { setFocusedTop(null); return; }\n setFocusedTop(getOffsetTopToContainer(blockEl, container));\n setFocusedHeight(blockEl.offsetHeight);\n }\n calc();\n const unregister = editor.registerUpdateListener(() => calc());\n return unregister;\n }, [focusedNodeKey, editor, container]);\n\n // ── 패널 닫기 ──────────────────────────────────────────────────────────────\n\n useEffect(() => { if (!panelOpen) setActiveIndex(0); }, [panelOpen]);\n useEffect(() => { if (!isVisible) setPanelOpen(false); }, [isVisible]);\n\n\n // 스크롤 시 패널 닫기\n useEffect(() => {\n if (!panelOpen) return;\n function onScroll() { setPanelOpen(false); }\n window.addEventListener(\"scroll\", onScroll, { passive: true, capture: true });\n return () => window.removeEventListener(\"scroll\", onScroll, { capture: true });\n }, [panelOpen]);\n\n useEffect(() => {\n if (!panelOpen) return;\n function onPointerDown(e: PointerEvent) {\n if (panelRef.current && !panelRef.current.contains(e.target as Node)) {\n setPanelOpen(false);\n }\n }\n window.addEventListener(\"pointerdown\", onPointerDown);\n return () => window.removeEventListener(\"pointerdown\", onPointerDown);\n }, [panelOpen]);\n\n useEffect(() => {\n if (!panelOpen) return;\n function onKeyDown(e: KeyboardEvent) {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActiveIndex((p) => Math.min(p + 1, items.length - 1));\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActiveIndex((p) => Math.max(p - 1, 0));\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const item = items[activeIndex];\n if (item) { editor.focus(() => { item.onSelect(editor); setPanelOpen(false); }); }\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n setPanelOpen(false);\n }\n }\n window.addEventListener(\"keydown\", onKeyDown, { capture: true });\n return () => window.removeEventListener(\"keydown\", onKeyDown, { capture: true });\n }, [panelOpen, items, activeIndex, editor]);\n\n // ── Drag & Drop (가이드 라인 포함) ─────────────────────────────────────────\n\n useEffect(() => {\n if (!showDragHandle || !container) return;\n\n const unregDragover = editor.registerCommand(\n DRAGOVER_COMMAND,\n (event) => {\n if (!event.dataTransfer?.types.includes(DRAG_DATA_FORMAT)) return false;\n event.preventDefault();\n\n const draggedKey = event.dataTransfer.getData(DRAG_DATA_FORMAT);\n const target = event.target as HTMLElement | null;\n if (!target) return true;\n\n // 드래그 중인 element가 draggedNode의 DOM 안에 있으면 가이드라인 숨김\n if (draggedKey) {\n const draggedDOM = editor.getElementByKey(draggedKey);\n if (draggedDOM?.contains(target)) { setDropLine(null); return true; }\n }\n\n let targetKey: string | null = null;\n editor.read(() => {\n const node = $getNearestNodeFromDOMNode(target);\n if (!node) return;\n // 최상위 블록으로 올라감\n const top = node.isInline() ? node.getTopLevelElement() : node;\n if (top) targetKey = top.getKey();\n });\n if (!targetKey || targetKey === draggedKey) { setDropLine(null); return true; }\n\n const targetDOM = editor.getElementByKey(targetKey);\n if (!targetDOM) { setDropLine(null); return true; }\n\n const { top: domTop, height } = targetDOM.getBoundingClientRect();\n const isBefore = event.clientY < domTop + height / 2;\n const offsetTop = getOffsetTopToContainer(targetDOM, container);\n\n setDropLine({\n top: isBefore ? offsetTop : offsetTop + targetDOM.offsetHeight,\n position: isBefore ? \"before\" : \"after\",\n });\n\n return true;\n },\n COMMAND_PRIORITY_LOW,\n );\n\n const unregDrop = editor.registerCommand(\n DROP_COMMAND,\n (event) => {\n const draggedKey = event.dataTransfer?.getData(DRAG_DATA_FORMAT);\n if (!draggedKey) return false;\n event.preventDefault();\n setDropLine(null);\n\n const target = event.target as HTMLElement | null;\n if (!target) return true;\n\n // 실제로 노드가 이동되었는지 확인하기 위해 현재 에디터 상태에서 사전 검증\n let willMove = false;\n editor.read(() => {\n const draggedNode = $getNodeByKey(draggedKey);\n if (!draggedNode) return;\n const nearestNode = $getNearestNodeFromDOMNode(target);\n if (!nearestNode) return;\n const targetNode = nearestNode.isInline() ? nearestNode.getTopLevelElement() : nearestNode;\n if (!targetNode) return;\n const targetKey = targetNode.getKey();\n if (targetKey === draggedKey) return;\n if ($isElementNode(draggedNode) && draggedNode.getChildren().some(\n (child) => child.getKey() === targetKey,\n )) return;\n willMove = true;\n });\n\n editor.update(() => {\n const draggedNode = $getNodeByKey(draggedKey);\n if (!draggedNode) return;\n\n const nearestNode = $getNearestNodeFromDOMNode(target);\n if (!nearestNode) return;\n\n // 최상위 블록으로 정규화\n const targetNode = nearestNode.isInline()\n ? nearestNode.getTopLevelElement()\n : nearestNode;\n if (!targetNode) return;\n\n const targetKey = targetNode.getKey();\n // 자기 자신 또는 자기 자손으로 drop 방지\n if (targetKey === draggedKey) return;\n if ($isElementNode(draggedNode) && draggedNode.getChildren().some(\n (child) => child.getKey() === targetKey,\n )) return;\n\n const targetDOM = editor.getElementByKey(targetKey);\n if (!targetDOM) return;\n\n const { top: targetTop, height } = targetDOM.getBoundingClientRect();\n if (event.clientY < targetTop + height / 2) {\n targetNode.insertBefore(draggedNode);\n } else {\n targetNode.insertAfter(draggedNode);\n }\n });\n\n // Lexical의 drop 이벤트 처리(DOM selection 읽기)가 완료된 뒤에 onDropped 호출\n // requestAnimationFrame은 Lexical의 microtask 큐 처리 후에 실행됨\n if (willMove && onDropped) {\n requestAnimationFrame(() => onDropped(draggedKey));\n }\n return true;\n },\n COMMAND_PRIORITY_HIGH,\n );\n\n // 드래그가 에디터 밖으로 나가면 가이드 라인 제거\n function onDragEnd() { setDropLine(null); }\n document.addEventListener(\"dragend\", onDragEnd);\n\n return () => {\n unregDragover();\n unregDrop();\n document.removeEventListener(\"dragend\", onDragEnd);\n };\n }, [editor, showDragHandle, container, onDropped]);\n\n const handleDragStart = useCallback((e: React.DragEvent) => {\n if (!nodeKey) return;\n e.dataTransfer.setData(DRAG_DATA_FORMAT, nodeKey);\n e.dataTransfer.effectAllowed = \"move\";\n const blockEl = editor.getElementByKey(nodeKey);\n if (blockEl) e.dataTransfer.setDragImage(blockEl, 0, 0);\n onDragStarted?.(nodeKey);\n }, [nodeKey, editor, onDragStarted]);\n\n // ── + 버튼 클릭 ────────────────────────────────────────────────────────────\n\n const handleAddClick = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n setPanelOpen((p) => !p);\n }, []);\n\n // ── Render ─────────────────────────────────────────────────────────────────\n\n if (!container) return null;\n\n const dragBtnStyle: CSSProperties = {\n ...btnBase,\n width: BTN,\n height: BTN,\n cursor: \"grab\",\n ...(dragHovered ? btnHover : {}),\n };\n\n const addBtnStyle: CSSProperties = {\n ...btnBase,\n width: BTN,\n height: BTN,\n ...(panelOpen || addHovered ? btnHover : {}),\n };\n\n return (\n <>\n {/* 커서 인디케이터 — 현재 포커스 블록 왼쪽에 세로 라인 */}\n {createPortal(\n <AnimatePresence>\n {focusedTop !== null && (\n <motion.div\n key={focusedNodeKey ?? \"cursor-indicator\"}\n style={{\n position: \"absolute\",\n top: focusedTop,\n left: 0,\n width: 2,\n height: focusedHeight,\n background: \"#818cf8\", // indigo-400\n borderRadius: 1,\n zIndex: 2,\n pointerEvents: \"none\",\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.1 }}\n />\n )}\n </AnimatePresence>,\n container!,\n )}\n\n {/* 버튼 그룹 — container 내부, absolute */}\n {createPortal(\n <AnimatePresence>\n {isVisible && top !== null && (\n <motion.div\n key={nodeKey ?? \"toolbar\"}\n data-block-toolbar\n style={{\n position: \"absolute\",\n top,\n left: GUTTER_LEFT,\n display: \"flex\",\n alignItems: \"center\",\n gap: GAP,\n width: numBtns * BTN + Math.max(0, numBtns - 1) * GAP,\n zIndex: 2,\n userSelect: \"none\",\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.08, ease: \"easeOut\" }}\n >\n {showDragHandle && (\n <div\n draggable\n data-drag-handle\n onDragStart={handleDragStart}\n aria-label=\"Drag to reorder\"\n style={dragBtnStyle}\n onMouseEnter={() => setDragHovered(true)}\n onMouseLeave={() => setDragHovered(false)}\n >\n <GripVertical size={14} strokeWidth={2} />\n </div>\n )}\n {showAddButton && (\n <button\n ref={addBtnRef}\n type=\"button\"\n aria-label=\"Add block\"\n aria-expanded={panelOpen}\n style={addBtnStyle}\n onMouseDown={handleAddClick}\n onMouseEnter={() => setAddHovered(true)}\n onMouseLeave={() => setAddHovered(false)}\n >\n <Plus size={13} strokeWidth={2.5} />\n </button>\n )}\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n )}\n\n {/* 드래그 가이드 라인 — container 내부, absolute */}\n {createPortal(\n <AnimatePresence>\n {dropLine !== null && (\n <motion.div\n key=\"drop-line\"\n style={{\n position: \"absolute\",\n top: dropLine.top,\n // 텍스트 시작점(padding-left)에 맞춰 왼쪽 여백\n left: 0,\n right: 0,\n height: 2,\n zIndex: 3,\n pointerEvents: \"none\",\n // 가이드 라인: 파란 선 + 양쪽 원형 핸들\n display: \"flex\",\n alignItems: \"center\",\n // translateY로 선을 블록 경계에 정확히 정렬\n transform: \"translateY(-1px)\",\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.06 }}\n >\n {/* 왼쪽 원 */}\n <div style={{\n width: 6,\n height: 6,\n borderRadius: \"50%\",\n background: \"#818cf8\", // indigo-400\n flexShrink: 0,\n marginLeft: 4,\n }} />\n {/* 가로선 */}\n <div style={{\n flex: 1,\n height: 2,\n background: \"#818cf8\",\n marginRight: 8,\n }} />\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n )}\n\n {/* 패널 — container 내부, absolute */}\n {createPortal(\n <AnimatePresence>\n {isVisible && panelOpen && top !== null && (\n <motion.div\n ref={panelRef}\n data-block-toolbar\n style={{\n position: \"absolute\",\n top: top + BTN + GAP,\n left: GUTTER_LEFT,\n zIndex: 50,\n width: 240,\n }}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: 0.1, ease: \"easeOut\" }}\n role=\"dialog\"\n aria-label=\"Insert block\"\n className=\"rounded-lg bg-zinc-800 shadow-xl shadow-black/50 py-1.5\"\n >\n <div role=\"listbox\" aria-label=\"Block type\" className=\"w-full px-1.5 flex flex-col\">\n {items.map((item, index) => {\n const icon = ICON_MAP[item.id] ?? item.icon;\n const isActive = index === activeIndex;\n return (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n onMouseDown={(e) => {\n e.preventDefault();\n editor.focus(() => { item.onSelect(editor); setPanelOpen(false); });\n }}\n onMouseEnter={() => setActiveIndex(index)}\n className={[\n \"w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors duration-75\",\n \"outline-none\",\n isActive\n ? \"bg-zinc-700/60 text-zinc-100\"\n : \"text-zinc-400 hover:bg-zinc-700/40 hover:text-zinc-200\",\n ].join(\" \")}\n >\n <span className=\"flex items-center justify-center shrink-0 w-4 text-current\">\n {icon}\n </span>\n <span className=\"text-sm font-normal leading-none\">{item.label}</span>\n </button>\n );\n })}\n </div>\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n )}\n </>\n );\n}\n","\"use client\";\n\nimport type { LexicalEditor } from \"lexical\";\nimport type { SlashMenuItem } from \"@jikjo/core\";\nimport { Heading1, Heading2, Heading3, Quote, Text } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport type { ReactNode } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface SlashMenuProps {\n isVisible: boolean;\n query: string;\n items: SlashMenuItem[];\n editor: LexicalEditor;\n onClose: () => void;\n}\n\n// ─── Icon map ─────────────────────────────────────────────────────────────────\n\nconst ICON_MAP: Record<string, ReactNode> = {\n paragraph: <Text size={14} strokeWidth={1.75} />,\n heading1: <Heading1 size={14} strokeWidth={1.75} />,\n heading2: <Heading2 size={14} strokeWidth={1.75} />,\n heading3: <Heading3 size={14} strokeWidth={1.75} />,\n quote: <Quote size={14} strokeWidth={1.75} />,\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\ninterface AbsolutePos {\n top: number;\n left: number;\n}\n\n/**\n * 캐럿(collapsed range)의 bottom 위치를 컨테이너 기준 absolute 좌표로 변환.\n */\nfunction getCaretAbsPos(container: HTMLElement): AbsolutePos | null {\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n\n const range = sel.getRangeAt(0).cloneRange();\n range.collapse(true);\n const caretRect = range.getBoundingClientRect();\n\n // getBoundingClientRect()가 (0,0,0,0)을 반환하는 경우 방어\n if (caretRect.width === 0 && caretRect.height === 0 && caretRect.top === 0) return null;\n\n const cr = container.getBoundingClientRect();\n const GAP = 6;\n\n return {\n top: caretRect.bottom - cr.top + container.scrollTop + GAP,\n left: caretRect.left - cr.left + container.scrollLeft,\n };\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport function SlashMenu({\n isVisible,\n query,\n items,\n editor,\n onClose,\n}: SlashMenuProps) {\n const [activeIndex, setActiveIndex] = useState(0);\n const containerRef = useRef<HTMLDivElement>(null);\n const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);\n const [pos, setPos] = useState<AbsolutePos | null>(null);\n\n // editor.getRootElement()가 준비된 후 portalContainer를 설정\n useEffect(() => {\n function attachContainer() {\n const rootEl = editor.getRootElement();\n if (!rootEl) return;\n const parent = rootEl.parentElement;\n if (parent) setPortalContainer(parent);\n }\n attachContainer();\n return editor.registerRootListener(attachContainer);\n }, [editor]);\n\n const filtered = items.filter(\n (item) =>\n !query ||\n item.label.toLowerCase().includes(query.toLowerCase()) ||\n item.description.toLowerCase().includes(query.toLowerCase()),\n );\n\n // isVisible 또는 query 변경 시 좌표 재계산 (캐럿이 이동/입력될 때마다)\n useEffect(() => {\n if (!isVisible || !portalContainer) {\n setPos(null);\n return;\n }\n // registerUpdateListener가 발동된 직후 호출되므로 DOM이 확정된 시점\n setPos(getCaretAbsPos(portalContainer));\n }, [isVisible, query, portalContainer]);\n\n useEffect(() => {\n setActiveIndex(0);\n }, [query]);\n\n useEffect(() => {\n if (!isVisible) return;\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n setActiveIndex((prev) => Math.min(prev + 1, filtered.length - 1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setActiveIndex((prev) => Math.max(prev - 1, 0));\n } else if (event.key === \"Enter\") {\n event.preventDefault();\n const item = filtered[activeIndex];\n if (!item) return;\n editor.focus(() => {\n item.onSelect(editor);\n onClose();\n });\n } else if (event.key === \"Escape\") {\n onClose();\n }\n }\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [isVisible, filtered, activeIndex, editor, onClose]);\n\n useEffect(() => {\n const el = containerRef.current?.querySelector<HTMLElement>(\n '[data-active=\"true\"]',\n );\n el?.scrollIntoView({ block: \"nearest\" });\n }, [activeIndex]);\n\n if (!portalContainer) return null;\n\n const menuStyle = pos\n ? { position: \"absolute\" as const, top: pos.top, left: pos.left, zIndex: 50, width: 240 }\n : { position: \"absolute\" as const, top: -9999, left: -9999, zIndex: 50, width: 240 };\n\n return createPortal(\n <AnimatePresence>\n {isVisible && (\n <motion.div\n style={menuStyle}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: 0.1, ease: \"easeOut\" }}\n role=\"dialog\"\n aria-label=\"Insert block\"\n className=\"rounded-lg bg-zinc-800 shadow-xl shadow-black/50 py-1.5 overflow-hidden\"\n >\n {filtered.length === 0 ? (\n <p className=\"px-4 py-2 text-xs text-zinc-500\">\n No results for &ldquo;{query}&rdquo;\n </p>\n ) : (\n <div\n ref={containerRef}\n role=\"listbox\"\n aria-label=\"Block type\"\n className=\"w-full max-h-72 overflow-y-auto px-1.5 flex flex-col\"\n >\n {filtered.map((item, index) => {\n const icon = ICON_MAP[item.id] ?? item.icon;\n const isActive = index === activeIndex;\n return (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n data-active={isActive}\n onMouseDown={(e) => {\n e.preventDefault();\n editor.focus(() => {\n item.onSelect(editor);\n onClose();\n });\n }}\n onMouseEnter={() => setActiveIndex(index)}\n className={[\n \"w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors duration-75\",\n \"outline-none focus-visible:outline-none\",\n isActive\n ? \"bg-zinc-700/80 text-zinc-100\"\n : \"text-zinc-400 hover:bg-zinc-700/60 hover:text-zinc-200 focus-visible:bg-zinc-700/80 focus-visible:text-zinc-100\",\n ].join(\" \")}\n >\n <span className=\"flex items-center justify-center shrink-0 w-4 text-current\">\n {icon}\n </span>\n <span className=\"text-sm font-normal leading-none\">\n {item.label}\n </span>\n </button>\n );\n })}\n </div>\n )}\n </motion.div>\n )}\n </AnimatePresence>,\n portalContainer,\n );\n}\n","\"use client\";\n\nimport {\n Editor,\n historyExtension,\n richTextExtension,\n useBlockHoverPlugin,\n useSelectionPlugin,\n useSlashCommandPlugin,\n} from \"@jikjo/core\";\nimport type { Extension, SlashMenuItem } from \"@jikjo/core\";\nimport {\n $createHeadingNode,\n $isHeadingNode,\n type HeadingTagType,\n} from \"@lexical/rich-text\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport {\n $createParagraphNode,\n $getNearestNodeFromDOMNode,\n $getSelection,\n $isRangeSelection,\n} from \"lexical\";\nimport {\n Bold,\n Code,\n Heading1,\n Heading2,\n Heading3,\n Italic,\n Strikethrough,\n Underline,\n} from \"lucide-react\";\nimport { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport type { MouseEvent } from \"react\";\nimport { BubbleMenu } from \"./components/bubble-menu\";\nimport { BlockToolbar } from \"./components/block-toolbar\";\nimport { SlashMenu } from \"./components/slash-menu\";\n\n// ─── Styles ───────────────────────────────────────────────────────────────────\n\nconst tbtnBase: CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: 32,\n height: 32,\n borderRadius: 6,\n border: \"none\",\n background: \"transparent\",\n color: \"#71717a\",\n cursor: \"pointer\",\n padding: 0,\n transition: \"background 100ms, color 100ms\",\n flexShrink: 0,\n};\n\nconst tbtnHover: CSSProperties = {\n background: \"rgba(39,39,42,0.9)\",\n color: \"#e4e4e7\",\n};\n\nconst tbtnActive: CSSProperties = {\n background: \"rgba(63,63,70,0.8)\",\n color: \"#f4f4f5\",\n};\n\n// ─── ToolbarButton ─────────────────────────────────────────────────────────────\n\ninterface ToolbarButtonProps {\n label: string;\n isActive?: boolean;\n onMouseDown: (e: MouseEvent) => void;\n children: ReactNode;\n}\n\nfunction ToolbarButton({ label, isActive, onMouseDown, children }: ToolbarButtonProps) {\n const [hovered, setHovered] = useState(false);\n\n const style: CSSProperties = {\n ...tbtnBase,\n ...(hovered ? tbtnHover : {}),\n ...(isActive ? tbtnActive : {}),\n };\n\n return (\n <button\n type=\"button\"\n aria-label={label}\n onMouseDown={onMouseDown}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n style={style}\n >\n {children}\n </button>\n );\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface EditorUIProps {\n extensions?: Extension[];\n namespace?: string;\n /**\n * undefined → default toolbar\n * false → no toolbar rendered at all\n * ReactNode → custom toolbar content\n */\n toolbarContent?: ReactNode | false;\n className?: string;\n /**\n * 활성화할 UI 기능 목록. 기본값: 모두 활성화.\n * \"blockHandle\" | \"inlineAdd\" | \"slashCommand\" | \"bubbleMenu\"\n */\n features?: Array<\"blockHandle\" | \"inlineAdd\" | \"slashCommand\" | \"bubbleMenu\">;\n}\n\n// ─── Default extensions ───────────────────────────────────────────────────────\n\nconst defaultExtensions: Extension[] = [richTextExtension, historyExtension];\n\n// ─── Inner component ──────────────────────────────────────────────────────────\n// Must live inside LexicalComposer so all hooks have editor context.\n\nconst ALL_FEATURES = [\"blockHandle\", \"inlineAdd\", \"slashCommand\", \"bubbleMenu\"] as const;\n\ninterface EditorInnerProps {\n extensions: Extension[];\n toolbarContent: ReactNode | false | undefined;\n features: Array<\"blockHandle\" | \"inlineAdd\" | \"slashCommand\" | \"bubbleMenu\">;\n}\n\nfunction EditorInner({\n extensions,\n toolbarContent,\n features,\n}: EditorInnerProps) {\n const [editor] = useLexicalComposerContext();\n\n const selection = useSelectionPlugin();\n const slashCommand = useSlashCommandPlugin();\n const blockHover = useBlockHoverPlugin();\n\n // heading 태그는 Lexical selection으로 추적 (toolbar 표시용)\n const [currentHeadingTag, setCurrentHeadingTag] = useState<HeadingTagType | null>(null);\n\n // focusedNodeKey: Lexical selection에서 완전히 독립.\n // - 에디터 click → DOM에서 직접 nodeKey 읽기\n // - dragstart → onDragStarted 콜백으로 nodeKey 전달\n // - drop 성공 → onDropped 콜백으로 이동된 nodeKey 전달\n // - dragend(취소) → null\n // - 에디터 바깥 클릭 → null\n const [focusedNodeKey, setFocusedNodeKey] = useState<string | null>(null);\n\n // drop 성공 플래그 (dragend 시 취소인지 성공인지 구분용)\n const dropSucceededRef = useRef(false);\n\n // ── heading tag 추적 (selection 기반, toolbar 전용) ──────────────────────\n\n useEffect(() => {\n return editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n const sel = $getSelection();\n if (!$isRangeSelection(sel)) {\n setCurrentHeadingTag(null);\n return;\n }\n const topNode = sel.anchor.getNode().getTopLevelElement();\n setCurrentHeadingTag($isHeadingNode(topNode) ? topNode.getTag() : null);\n });\n });\n }, [editor]);\n\n // ── focusedNodeKey: 에디터 click 이벤트에서 DOM → nodeKey 직접 읽기 ──────\n\n useEffect(() => {\n return editor.registerRootListener((rootElement) => {\n if (!rootElement) return;\n const root = rootElement;\n\n function onClick(e: Event) {\n const target = e.target as HTMLElement | null;\n if (!target) return;\n let key: string | null = null;\n editor.read(() => {\n const node = $getNearestNodeFromDOMNode(target);\n if (!node) return;\n const top = node.isInline() ? node.getTopLevelElement() : node;\n if (top) key = top.getKey();\n });\n if (key) setFocusedNodeKey(key);\n }\n\n // 에디터/툴바 바깥 클릭 시 커서 인디케이터 제거\n function onDocClick(e: Event) {\n const target = e.target as HTMLElement | null;\n if (root.contains(target)) return;\n if (target?.closest(\"[data-block-toolbar]\") || target?.closest(\"[data-drag-handle]\")) return;\n setFocusedNodeKey(null);\n }\n\n root.addEventListener(\"click\", onClick);\n document.addEventListener(\"click\", onDocClick, true);\n\n return () => {\n root.removeEventListener(\"click\", onClick);\n document.removeEventListener(\"click\", onDocClick, true);\n };\n });\n }, [editor]);\n\n // ── drag/drop 이벤트에서 focusedNodeKey 관리 ────────────────────────────\n // Firefox: drag handle mousedown → blur → dragstart 순서로 발생.\n // blur 시 Lexical이 $setSelection(null)을 호출하지만,\n // focusedNodeKey는 Lexical selection과 독립이므로 영향받지 않음.\n // drop 성공/취소는 onDropped / dragend 이벤트로 구분.\n\n const handleDragStarted = useCallback((key: string) => {\n dropSucceededRef.current = false;\n setFocusedNodeKey(key);\n // Firefox blur로 에디터 포커스가 빠진 경우 복구\n editor.focus();\n }, [editor]);\n\n const handleDropped = useCallback((key: string) => {\n dropSucceededRef.current = true;\n setFocusedNodeKey(key);\n // drop 후 에디터 포커스 복구\n editor.focus();\n }, [editor]);\n\n useEffect(() => {\n function onDragEnd() {\n if (!dropSucceededRef.current) {\n // drag 취소: 커서 인디케이터 제거\n setFocusedNodeKey(null);\n }\n dropSucceededRef.current = false;\n }\n document.addEventListener(\"dragend\", onDragEnd);\n return () => document.removeEventListener(\"dragend\", onDragEnd);\n }, []);\n\n // Collect slash menu items from all registered extensions\n const slashMenuItems = useMemo<SlashMenuItem[]>(\n () => extensions.flatMap((ext) => ext.slashMenuItems ?? []),\n [extensions],\n );\n\n // Toggle heading: if already at this level, revert to paragraph.\n // newNode.select() preserves selection after replace to avoid Lexical error.\n const toggleHeading = useCallback(\n (tag: HeadingTagType) => {\n editor.update(() => {\n const sel = $getSelection();\n if (!$isRangeSelection(sel)) return;\n const topNode = sel.anchor.getNode().getTopLevelElementOrThrow();\n const newNode = ($isHeadingNode(topNode) && topNode.getTag() === tag)\n ? $createParagraphNode()\n : $createHeadingNode(tag);\n topNode.replace(newNode);\n newNode.select();\n });\n },\n [editor],\n );\n\n return (\n <>\n {/* ── Toolbar ──────────────────────────────────────────────────── */}\n {toolbarContent === false ? null : toolbarContent !== undefined ? (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2, padding: \"8px 16px\", borderBottom: \"1px solid rgba(39,39,42,0.5)\" }}>\n {toolbarContent}\n </div>\n ) : (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2, padding: \"8px 16px\", borderBottom: \"1px solid rgba(39,39,42,0.5)\" }}>\n {/* Format group */}\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2 }}>\n <ToolbarButton\n label=\"Bold\"\n isActive={selection.format.bold}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"bold\"); }}\n >\n <Bold size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Italic\"\n isActive={selection.format.italic}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"italic\"); }}\n >\n <Italic size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Underline\"\n isActive={selection.format.underline}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"underline\"); }}\n >\n <Underline size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Strikethrough\"\n isActive={selection.format.strikethrough}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"strikethrough\"); }}\n >\n <Strikethrough size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Code\"\n isActive={selection.format.code}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"code\"); }}\n >\n <Code size={14} strokeWidth={2.5} />\n </ToolbarButton>\n </div>\n\n <div style={{ width: 1, height: 16, background: \"#27272a\", margin: \"0 4px\" }} />\n\n {/* Heading group */}\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2 }}>\n <ToolbarButton\n label=\"Heading 1\"\n isActive={currentHeadingTag === \"h1\"}\n onMouseDown={(e) => { e.preventDefault(); toggleHeading(\"h1\"); }}\n >\n <Heading1 size={15} strokeWidth={2} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Heading 2\"\n isActive={currentHeadingTag === \"h2\"}\n onMouseDown={(e) => { e.preventDefault(); toggleHeading(\"h2\"); }}\n >\n <Heading2 size={15} strokeWidth={2} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Heading 3\"\n isActive={currentHeadingTag === \"h3\"}\n onMouseDown={(e) => { e.preventDefault(); toggleHeading(\"h3\"); }}\n >\n <Heading3 size={15} strokeWidth={2} />\n </ToolbarButton>\n </div>\n </div>\n )}\n\n {/* ── Floating overlays ────────────────────────────────────────── */}\n {features.includes(\"bubbleMenu\") && (\n <BubbleMenu\n isVisible={selection.isActive}\n format={selection.format}\n onToggleFormat={selection.toggleFormat}\n editor={editor}\n />\n )}\n\n {features.includes(\"slashCommand\") && (\n <SlashMenu\n isVisible={slashCommand.isActive}\n query={slashCommand.query}\n items={slashMenuItems}\n editor={editor}\n onClose={slashCommand.close}\n />\n )}\n\n {/* drag handle + + 버튼 + 커서 인디케이터 */}\n <BlockToolbar\n isVisible={blockHover.isActive && (features.includes(\"blockHandle\") || features.includes(\"inlineAdd\"))}\n nodeKey={blockHover.nodeKey}\n focusedNodeKey={focusedNodeKey}\n items={slashMenuItems}\n editor={editor}\n showDragHandle={features.includes(\"blockHandle\")}\n showAddButton={features.includes(\"inlineAdd\")}\n onDragStarted={handleDragStarted}\n onDropped={handleDropped}\n />\n </>\n );\n}\n\n// ─── Public component ─────────────────────────────────────────────────────────\n\nexport function EditorUI({\n extensions = defaultExtensions,\n namespace = \"jikjo\",\n toolbarContent,\n className,\n features = [...ALL_FEATURES],\n}: EditorUIProps) {\n const hasBlockToolbar = features.includes(\"blockHandle\") || features.includes(\"inlineAdd\");\n return (\n <div\n className={className}\n {...(hasBlockToolbar ? { \"data-has-block-toolbar\": \"\" } : {})}\n // block toolbar가 있을 때만 좌측 74px padding을 주입\n // (GUTTER_LEFT 6 + 2×BTN 48 + GAP 4 = 58px toolbar + 16px text padding)\n style={hasBlockToolbar ? ({\n \"--jikjo-content-pl\": \"74px\",\n \"--jikjo-placeholder-pl\": \"74px\",\n } as React.CSSProperties) : ({\n \"--jikjo-content-pl\": \"16px\",\n \"--jikjo-placeholder-pl\": \"16px\",\n } as React.CSSProperties)}\n >\n <Editor extensions={extensions} namespace={namespace}>\n <EditorInner\n extensions={extensions}\n toolbarContent={toolbarContent}\n features={features}\n />\n </Editor>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["ICON_MAP: Record<string, React.ReactNode>","targetKey: string | null","top","ICON_MAP","ICON_MAP: Record<string, ReactNode>","tbtnBase: CSSProperties","tbtnHover: CSSProperties","tbtnActive: CSSProperties","style: CSSProperties","defaultExtensions: Extension[]","key: string | null"],"sources":["../src/components/bubble-menu.tsx","../src/components/block-toolbar.tsx","../src/components/slash-menu.tsx","../src/editor-ui.tsx"],"sourcesContent":["\"use client\";\n\nimport type { SelectionFormatState } from \"@jikjo/core\";\nimport type { LexicalEditor } from \"lexical\";\nimport { Bold, Code, Italic, Strikethrough, Underline } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { useEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport \"../styles/variables.css\";\nimport \"../styles/bubble-menu.css\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface BubbleMenuProps {\n isVisible: boolean;\n format: SelectionFormatState;\n onToggleFormat: (format: keyof SelectionFormatState) => void;\n editor: LexicalEditor;\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\ninterface AbsolutePos {\n top: number;\n left: number;\n}\n\n/**\n * selection rect를 컨테이너 기준 absolute 좌표로 변환.\n * 컨테이너가 스크롤되어도 absolute 요소는 함께 움직인다.\n */\nfunction getSelectionAbsPos(container: HTMLElement): AbsolutePos | null {\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n const range = sel.getRangeAt(0);\n if (range.collapsed) return null;\n\n const selRect = range.getBoundingClientRect();\n if (selRect.width === 0) return null;\n\n const cr = container.getBoundingClientRect();\n const MENU_HEIGHT = 36;\n const GAP = 6;\n\n return {\n top: selRect.top - cr.top + container.scrollTop - MENU_HEIGHT - GAP,\n left: selRect.left - cr.left + container.scrollLeft + selRect.width / 2,\n };\n}\n\n// ─── Button ───────────────────────────────────────────────────────────────────\n\nfunction Btn({\n active,\n label,\n onMouseDown,\n children,\n}: {\n active: boolean;\n label: string;\n onMouseDown: (e: React.MouseEvent) => void;\n children: React.ReactNode;\n}) {\n return (\n <button\n type=\"button\"\n aria-label={label}\n onMouseDown={onMouseDown}\n className={`jikjo-bubble-menu__btn${active ? \" jikjo-bubble-menu__btn--active\" : \"\"}`}\n >\n {children}\n </button>\n );\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport function BubbleMenu({\n isVisible,\n format,\n onToggleFormat,\n editor,\n}: BubbleMenuProps) {\n const [container, setContainer] = useState<HTMLElement | null>(null);\n const [pos, setPos] = useState<AbsolutePos | null>(null);\n\n // editor.getRootElement()가 준비된 후 container를 설정\n useEffect(() => {\n function attachContainer() {\n const rootEl = editor.getRootElement();\n if (!rootEl) return;\n const parent = rootEl.parentElement;\n if (parent) setContainer(parent);\n }\n attachContainer();\n return editor.registerRootListener(attachContainer);\n }, [editor]);\n\n // selection 변화 또는 isVisible 변화 시 좌표 재계산.\n // isVisible=false 될 때만 pos를 null로 초기화 (format 적용 중에는 이전 pos 유지).\n useEffect(() => {\n if (!isVisible || !container) {\n setPos(null);\n return;\n }\n const newPos = getSelectionAbsPos(container);\n // newPos가 null이면 이전 pos 유지 (format 적용 직후 DOM rect가 잠시 없을 때 대비)\n if (newPos !== null) {\n setPos(newPos);\n }\n }, [isVisible, container]);\n\n if (!container) return null;\n\n const menuStyle = pos\n ? { position: \"absolute\" as const, top: pos.top, left: pos.left, x: \"-50%\", zIndex: 50 }\n : { position: \"absolute\" as const, top: -9999, left: -9999, zIndex: 50 };\n\n return createPortal(\n <AnimatePresence>\n {isVisible && (\n <motion.div\n style={menuStyle}\n initial={{ opacity: 0, y: 6 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 6 }}\n transition={{ duration: 0.1, ease: \"easeOut\" }}\n className=\"jikjo-bubble-menu\"\n >\n <Btn\n active={format.bold}\n label=\"Bold\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"bold\");\n }}\n >\n <Bold size={13} strokeWidth={2.5} />\n </Btn>\n <Btn\n active={format.italic}\n label=\"Italic\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"italic\");\n }}\n >\n <Italic size={13} strokeWidth={2} />\n </Btn>\n <Btn\n active={format.underline}\n label=\"Underline\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"underline\");\n }}\n >\n <Underline size={13} strokeWidth={2} />\n </Btn>\n\n <div className=\"jikjo-bubble-menu__divider\" />\n\n <Btn\n active={format.strikethrough}\n label=\"Strikethrough\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"strikethrough\");\n }}\n >\n <Strikethrough size={13} strokeWidth={2} />\n </Btn>\n <Btn\n active={format.code}\n label=\"Code\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"code\");\n }}\n >\n <Code size={13} strokeWidth={2} />\n </Btn>\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n );\n}\n","\"use client\";\n\nimport type { SlashMenuItem } from \"@jikjo/core\";\nimport type { LexicalEditor } from \"lexical\";\nimport {\n $getNearestNodeFromDOMNode,\n $getNodeByKey,\n $isElementNode,\n COMMAND_PRIORITY_HIGH,\n COMMAND_PRIORITY_LOW,\n DRAGOVER_COMMAND,\n DROP_COMMAND,\n} from \"lexical\";\nimport {\n GripVertical,\n Heading1,\n Heading2,\n Heading3,\n List,\n ListOrdered,\n ListTodo,\n Plus,\n Quote,\n Text,\n} from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport \"../styles/variables.css\";\nimport \"../styles/block-toolbar.css\";\nimport \"../styles/menu-panel.css\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface BlockToolbarProps {\n isVisible: boolean;\n nodeKey: string | null;\n /** 현재 커서(포커스)가 있는 블록의 nodeKey */\n focusedNodeKey: string | null;\n items: SlashMenuItem[];\n editor: LexicalEditor;\n showDragHandle?: boolean;\n showAddButton?: boolean;\n /** drag 시작 시 드래그 중인 블록의 nodeKey를 부모에 전달 */\n onDragStarted?: (nodeKey: string) => void;\n /** drag & drop 완료 후 이동된 블록의 nodeKey를 부모에 전달 */\n onDropped?: (nodeKey: string) => void;\n}\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst DRAG_DATA_FORMAT = \"application/x-lexical-drag-block\";\nconst BTN = 24;\nconst GAP = 4;\nconst GUTTER_LEFT = 6;\n// 버튼 그룹 전체 너비 (drag + add 기준): GUTTER_LEFT + 2*BTN + GAP\nconst TOOLBAR_WIDTH = GUTTER_LEFT + 2 * BTN + GAP; // 58px\n\n// ─── Inline styles ────────────────────────────────────────────────────────────\n\n\n// ─── Icon map ─────────────────────────────────────────────────────────────────\n\nconst ICON_MAP: Record<string, React.ReactNode> = {\n paragraph: <Text size={13} strokeWidth={1.75} />,\n heading1: <Heading1 size={13} strokeWidth={1.75} />,\n heading2: <Heading2 size={13} strokeWidth={1.75} />,\n heading3: <Heading3 size={13} strokeWidth={1.75} />,\n bulletList: <List size={13} strokeWidth={1.75} />,\n orderedList: <ListOrdered size={13} strokeWidth={1.75} />,\n taskList: <ListTodo size={13} strokeWidth={1.75} />,\n quote: <Quote size={13} strokeWidth={1.75} />,\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/** blockEl의 top을 container 기준 절대 좌표로 계산 (getBoundingClientRect 기반) */\nfunction getOffsetTopToContainer(blockEl: HTMLElement, container: HTMLElement): number {\n const blockRect = blockEl.getBoundingClientRect();\n const containerRect = container.getBoundingClientRect();\n // container의 scrollTop도 반영 (container가 스크롤 가능한 경우)\n return blockRect.top - containerRect.top + container.scrollTop;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport function BlockToolbar({\n isVisible,\n nodeKey,\n focusedNodeKey,\n items,\n editor,\n showDragHandle = true,\n showAddButton = true,\n onDragStarted,\n onDropped,\n}: BlockToolbarProps) {\n const [panelOpen, setPanelOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(0);\n const [container, setContainer] = useState<HTMLElement | null>(null);\n const [top, setTop] = useState<number | null>(null);\n const [dragHovered, setDragHovered] = useState(false);\n const [addHovered, setAddHovered] = useState(false);\n\n // 드래그 가이드 라인: container 기준 absolute top + 위치 (before/after)\n const [dropLine, setDropLine] = useState<{ top: number; position: \"before\" | \"after\" } | null>(null);\n // 커서 인디케이터: focusedNodeKey 블록의 위치\n const [focusedTop, setFocusedTop] = useState<number | null>(null);\n const [focusedHeight, setFocusedHeight] = useState<number>(0);\n\n const panelRef = useRef<HTMLDivElement>(null);\n const addBtnRef = useRef<HTMLButtonElement>(null);\n\n const numBtns = (showDragHandle ? 1 : 0) + (showAddButton ? 1 : 0);\n\n // ── 컨테이너 취득 ──────────────────────────────────────────────────────────\n\n useEffect(() => {\n return editor.registerRootListener((rootElement) => {\n setContainer(rootElement?.parentElement ?? null);\n });\n }, [editor]);\n\n // ── 수직 위치 계산 ─────────────────────────────────────────────────────────\n\n useEffect(() => {\n function calc() {\n if (!isVisible || !nodeKey || !container) { setTop(null); return; }\n const blockEl = editor.getElementByKey(nodeKey);\n if (!blockEl) { setTop(null); return; }\n const offset = getOffsetTopToContainer(blockEl, container);\n setTop(offset + (blockEl.offsetHeight - BTN) / 2);\n }\n calc();\n return editor.registerUpdateListener(() => calc());\n }, [isVisible, nodeKey, editor, container]);\n\n // ── 커서 인디케이터 위치 계산 ──────────────────────────────────────────────\n\n useEffect(() => {\n function calc() {\n if (!focusedNodeKey || !container) { setFocusedTop(null); return; }\n const blockEl = editor.getElementByKey(focusedNodeKey);\n if (!blockEl) { setFocusedTop(null); return; }\n setFocusedTop(getOffsetTopToContainer(blockEl, container));\n setFocusedHeight(blockEl.offsetHeight);\n }\n calc();\n const unregister = editor.registerUpdateListener(() => calc());\n return unregister;\n }, [focusedNodeKey, editor, container]);\n\n // ── 패널 닫기 ──────────────────────────────────────────────────────────────\n\n useEffect(() => { if (!panelOpen) setActiveIndex(0); }, [panelOpen]);\n useEffect(() => { if (!isVisible) setPanelOpen(false); }, [isVisible]);\n\n\n // 스크롤 시 패널 닫기\n useEffect(() => {\n if (!panelOpen) return;\n function onScroll() { setPanelOpen(false); }\n window.addEventListener(\"scroll\", onScroll, { passive: true, capture: true });\n return () => window.removeEventListener(\"scroll\", onScroll, { capture: true });\n }, [panelOpen]);\n\n useEffect(() => {\n if (!panelOpen) return;\n function onPointerDown(e: PointerEvent) {\n if (panelRef.current && !panelRef.current.contains(e.target as Node)) {\n setPanelOpen(false);\n }\n }\n window.addEventListener(\"pointerdown\", onPointerDown);\n return () => window.removeEventListener(\"pointerdown\", onPointerDown);\n }, [panelOpen]);\n\n useEffect(() => {\n if (!panelOpen) return;\n function onKeyDown(e: KeyboardEvent) {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActiveIndex((p) => Math.min(p + 1, items.length - 1));\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActiveIndex((p) => Math.max(p - 1, 0));\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const item = items[activeIndex];\n if (item) { editor.focus(() => { item.onSelect(editor); setPanelOpen(false); }); }\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n setPanelOpen(false);\n }\n }\n window.addEventListener(\"keydown\", onKeyDown, { capture: true });\n return () => window.removeEventListener(\"keydown\", onKeyDown, { capture: true });\n }, [panelOpen, items, activeIndex, editor]);\n\n // ── Drag & Drop (가이드 라인 포함) ─────────────────────────────────────────\n\n useEffect(() => {\n if (!showDragHandle || !container) return;\n\n const unregDragover = editor.registerCommand(\n DRAGOVER_COMMAND,\n (event) => {\n if (!event.dataTransfer?.types.includes(DRAG_DATA_FORMAT)) return false;\n event.preventDefault();\n\n const draggedKey = event.dataTransfer.getData(DRAG_DATA_FORMAT);\n const target = event.target as HTMLElement | null;\n if (!target) return true;\n\n // 드래그 중인 element가 draggedNode의 DOM 안에 있으면 가이드라인 숨김\n if (draggedKey) {\n const draggedDOM = editor.getElementByKey(draggedKey);\n if (draggedDOM?.contains(target)) { setDropLine(null); return true; }\n }\n\n let targetKey: string | null = null;\n editor.read(() => {\n const node = $getNearestNodeFromDOMNode(target);\n if (!node) return;\n // 최상위 블록으로 올라감\n const top = node.isInline() ? node.getTopLevelElement() : node;\n if (top) targetKey = top.getKey();\n });\n if (!targetKey || targetKey === draggedKey) { setDropLine(null); return true; }\n\n const targetDOM = editor.getElementByKey(targetKey);\n if (!targetDOM) { setDropLine(null); return true; }\n\n const { top: domTop, height } = targetDOM.getBoundingClientRect();\n const isBefore = event.clientY < domTop + height / 2;\n const offsetTop = getOffsetTopToContainer(targetDOM, container);\n\n setDropLine({\n top: isBefore ? offsetTop : offsetTop + targetDOM.offsetHeight,\n position: isBefore ? \"before\" : \"after\",\n });\n\n return true;\n },\n COMMAND_PRIORITY_LOW,\n );\n\n const unregDrop = editor.registerCommand(\n DROP_COMMAND,\n (event) => {\n const draggedKey = event.dataTransfer?.getData(DRAG_DATA_FORMAT);\n if (!draggedKey) return false;\n event.preventDefault();\n setDropLine(null);\n\n const target = event.target as HTMLElement | null;\n if (!target) return true;\n\n // 실제로 노드가 이동되었는지 확인하기 위해 현재 에디터 상태에서 사전 검증\n let willMove = false;\n editor.read(() => {\n const draggedNode = $getNodeByKey(draggedKey);\n if (!draggedNode) return;\n const nearestNode = $getNearestNodeFromDOMNode(target);\n if (!nearestNode) return;\n const targetNode = nearestNode.isInline() ? nearestNode.getTopLevelElement() : nearestNode;\n if (!targetNode) return;\n const targetKey = targetNode.getKey();\n if (targetKey === draggedKey) return;\n if ($isElementNode(draggedNode) && draggedNode.getChildren().some(\n (child) => child.getKey() === targetKey,\n )) return;\n willMove = true;\n });\n\n editor.update(() => {\n const draggedNode = $getNodeByKey(draggedKey);\n if (!draggedNode) return;\n\n const nearestNode = $getNearestNodeFromDOMNode(target);\n if (!nearestNode) return;\n\n // 최상위 블록으로 정규화\n const targetNode = nearestNode.isInline()\n ? nearestNode.getTopLevelElement()\n : nearestNode;\n if (!targetNode) return;\n\n const targetKey = targetNode.getKey();\n // 자기 자신 또는 자기 자손으로 drop 방지\n if (targetKey === draggedKey) return;\n if ($isElementNode(draggedNode) && draggedNode.getChildren().some(\n (child) => child.getKey() === targetKey,\n )) return;\n\n const targetDOM = editor.getElementByKey(targetKey);\n if (!targetDOM) return;\n\n const { top: targetTop, height } = targetDOM.getBoundingClientRect();\n if (event.clientY < targetTop + height / 2) {\n targetNode.insertBefore(draggedNode);\n } else {\n targetNode.insertAfter(draggedNode);\n }\n });\n\n // Lexical의 drop 이벤트 처리(DOM selection 읽기)가 완료된 뒤에 onDropped 호출\n // requestAnimationFrame은 Lexical의 microtask 큐 처리 후에 실행됨\n if (willMove && onDropped) {\n requestAnimationFrame(() => onDropped(draggedKey));\n }\n return true;\n },\n COMMAND_PRIORITY_HIGH,\n );\n\n // 드래그가 에디터 밖으로 나가면 가이드 라인 제거\n function onDragEnd() { setDropLine(null); }\n document.addEventListener(\"dragend\", onDragEnd);\n\n return () => {\n unregDragover();\n unregDrop();\n document.removeEventListener(\"dragend\", onDragEnd);\n };\n }, [editor, showDragHandle, container, onDropped]);\n\n const handleDragStart = useCallback((e: React.DragEvent) => {\n if (!nodeKey) return;\n e.dataTransfer.setData(DRAG_DATA_FORMAT, nodeKey);\n e.dataTransfer.effectAllowed = \"move\";\n const blockEl = editor.getElementByKey(nodeKey);\n if (blockEl) e.dataTransfer.setDragImage(blockEl, 0, 0);\n onDragStarted?.(nodeKey);\n }, [nodeKey, editor, onDragStarted]);\n\n // ── + 버튼 클릭 ────────────────────────────────────────────────────────────\n\n const handleAddClick = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n setPanelOpen((p) => !p);\n }, []);\n\n // ── Render ─────────────────────────────────────────────────────────────────\n\n if (!container) return null;\n\n\n return (\n <>\n {/* 커서 인디케이터 — 현재 포커스 블록 왼쪽에 세로 라인 */}\n {createPortal(\n <AnimatePresence>\n {focusedTop !== null && (\n <motion.div\n key={focusedNodeKey ?? \"cursor-indicator\"}\n className=\"jikjo-block-toolbar__cursor-indicator\"\n style={{ top: focusedTop, height: focusedHeight }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.1 }}\n />\n )}\n </AnimatePresence>,\n container!,\n )}\n\n {/* 버튼 그룹 — container 내부, absolute */}\n {createPortal(\n <AnimatePresence>\n {isVisible && top !== null && (\n <motion.div\n key={nodeKey ?? \"toolbar\"}\n data-block-toolbar\n style={{\n position: \"absolute\",\n top,\n left: GUTTER_LEFT,\n display: \"flex\",\n alignItems: \"center\",\n gap: GAP,\n width: numBtns * BTN + Math.max(0, numBtns - 1) * GAP,\n zIndex: 2,\n userSelect: \"none\",\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.08, ease: \"easeOut\" }}\n >\n {showDragHandle && (\n <div\n draggable\n data-drag-handle\n onDragStart={handleDragStart}\n aria-label=\"Drag to reorder\"\n className={`jikjo-block-toolbar__btn${dragHovered ? \" jikjo-block-toolbar__btn--hovered\" : \"\"}`}\n style={{ width: BTN, height: BTN, cursor: \"grab\" }}\n onMouseEnter={() => setDragHovered(true)}\n onMouseLeave={() => setDragHovered(false)}\n >\n <GripVertical size={14} strokeWidth={2} />\n </div>\n )}\n {showAddButton && (\n <button\n ref={addBtnRef}\n type=\"button\"\n aria-label=\"Add block\"\n aria-expanded={panelOpen}\n className={`jikjo-block-toolbar__btn${panelOpen || addHovered ? \" jikjo-block-toolbar__btn--hovered\" : \"\"}`}\n style={{ width: BTN, height: BTN }}\n onMouseDown={handleAddClick}\n onMouseEnter={() => setAddHovered(true)}\n onMouseLeave={() => setAddHovered(false)}\n >\n <Plus size={13} strokeWidth={2.5} />\n </button>\n )}\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n )}\n\n {/* 드래그 가이드 라인 — container 내부, absolute */}\n {createPortal(\n <AnimatePresence>\n {dropLine !== null && (\n <motion.div\n key=\"drop-line\"\n className=\"jikjo-block-toolbar__drop-line\"\n style={{ top: dropLine.top }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.06 }}\n >\n <div className=\"jikjo-block-toolbar__drop-line-dot\" />\n <div className=\"jikjo-block-toolbar__drop-line-bar\" />\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n )}\n\n {/* 패널 — container 내부, absolute */}\n {createPortal(\n <AnimatePresence>\n {isVisible && panelOpen && top !== null && (\n <motion.div\n ref={panelRef}\n data-block-toolbar\n style={{\n position: \"absolute\",\n top: top + BTN + GAP,\n left: GUTTER_LEFT,\n zIndex: 50,\n width: 240,\n }}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: 0.1, ease: \"easeOut\" }}\n role=\"dialog\"\n aria-label=\"Insert block\"\n className=\"jikjo-menu-panel\"\n >\n <div role=\"listbox\" aria-label=\"Block type\" className=\"jikjo-menu-panel__list\">\n {items.map((item, index) => {\n const icon = ICON_MAP[item.id] ?? item.icon;\n const isActive = index === activeIndex;\n return (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n onMouseDown={(e) => {\n e.preventDefault();\n editor.focus(() => { item.onSelect(editor); setPanelOpen(false); });\n }}\n onMouseEnter={() => setActiveIndex(index)}\n className={`jikjo-menu-panel__item${isActive ? \" jikjo-menu-panel__item--active\" : \"\"}`}\n >\n <span className=\"jikjo-menu-panel__item-icon\">{icon}</span>\n <span className=\"jikjo-menu-panel__item-label\">{item.label}</span>\n </button>\n );\n })}\n </div>\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n )}\n </>\n );\n}\n","\"use client\";\n\nimport type { LexicalEditor } from \"lexical\";\nimport type { SlashMenuItem } from \"@jikjo/core\";\nimport { Heading1, Heading2, Heading3, Quote, Text } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport type { ReactNode } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport \"../styles/variables.css\";\nimport \"../styles/menu-panel.css\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface SlashMenuProps {\n isVisible: boolean;\n query: string;\n items: SlashMenuItem[];\n editor: LexicalEditor;\n onClose: () => void;\n}\n\n// ─── Icon map ─────────────────────────────────────────────────────────────────\n\nconst ICON_MAP: Record<string, ReactNode> = {\n paragraph: <Text size={14} strokeWidth={1.75} />,\n heading1: <Heading1 size={14} strokeWidth={1.75} />,\n heading2: <Heading2 size={14} strokeWidth={1.75} />,\n heading3: <Heading3 size={14} strokeWidth={1.75} />,\n quote: <Quote size={14} strokeWidth={1.75} />,\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\ninterface AbsolutePos {\n top: number;\n left: number;\n}\n\n/**\n * 캐럿(collapsed range)의 bottom 위치를 컨테이너 기준 absolute 좌표로 변환.\n */\nfunction getCaretAbsPos(container: HTMLElement): AbsolutePos | null {\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n\n const range = sel.getRangeAt(0).cloneRange();\n range.collapse(true);\n const caretRect = range.getBoundingClientRect();\n\n // getBoundingClientRect()가 (0,0,0,0)을 반환하는 경우 방어\n if (caretRect.width === 0 && caretRect.height === 0 && caretRect.top === 0) return null;\n\n const cr = container.getBoundingClientRect();\n const GAP = 6;\n\n return {\n top: caretRect.bottom - cr.top + container.scrollTop + GAP,\n left: caretRect.left - cr.left + container.scrollLeft,\n };\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport function SlashMenu({\n isVisible,\n query,\n items,\n editor,\n onClose,\n}: SlashMenuProps) {\n const [activeIndex, setActiveIndex] = useState(0);\n const containerRef = useRef<HTMLDivElement>(null);\n const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);\n const [pos, setPos] = useState<AbsolutePos | null>(null);\n\n // editor.getRootElement()가 준비된 후 portalContainer를 설정\n useEffect(() => {\n function attachContainer() {\n const rootEl = editor.getRootElement();\n if (!rootEl) return;\n const parent = rootEl.parentElement;\n if (parent) setPortalContainer(parent);\n }\n attachContainer();\n return editor.registerRootListener(attachContainer);\n }, [editor]);\n\n const filtered = items.filter(\n (item) =>\n !query ||\n item.label.toLowerCase().includes(query.toLowerCase()) ||\n item.description.toLowerCase().includes(query.toLowerCase()),\n );\n\n // isVisible 또는 query 변경 시 좌표 재계산 (캐럿이 이동/입력될 때마다)\n useEffect(() => {\n if (!isVisible || !portalContainer) {\n setPos(null);\n return;\n }\n // registerUpdateListener가 발동된 직후 호출되므로 DOM이 확정된 시점\n setPos(getCaretAbsPos(portalContainer));\n }, [isVisible, query, portalContainer]);\n\n useEffect(() => {\n setActiveIndex(0);\n }, [query]);\n\n useEffect(() => {\n if (!isVisible) return;\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n setActiveIndex((prev) => Math.min(prev + 1, filtered.length - 1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setActiveIndex((prev) => Math.max(prev - 1, 0));\n } else if (event.key === \"Enter\") {\n event.preventDefault();\n const item = filtered[activeIndex];\n if (!item) return;\n editor.focus(() => {\n item.onSelect(editor);\n onClose();\n });\n } else if (event.key === \"Escape\") {\n onClose();\n }\n }\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [isVisible, filtered, activeIndex, editor, onClose]);\n\n useEffect(() => {\n const el = containerRef.current?.querySelector<HTMLElement>(\n '[data-active=\"true\"]',\n );\n el?.scrollIntoView({ block: \"nearest\" });\n }, [activeIndex]);\n\n if (!portalContainer) return null;\n\n const menuStyle = pos\n ? { position: \"absolute\" as const, top: pos.top, left: pos.left, zIndex: 50, width: 240 }\n : { position: \"absolute\" as const, top: -9999, left: -9999, zIndex: 50, width: 240 };\n\n return createPortal(\n <AnimatePresence>\n {isVisible && (\n <motion.div\n style={menuStyle}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: 0.1, ease: \"easeOut\" }}\n role=\"dialog\"\n aria-label=\"Insert block\"\n className=\"jikjo-menu-panel\"\n >\n {filtered.length === 0 ? (\n <p className=\"jikjo-menu-panel__empty\">\n No results for &ldquo;{query}&rdquo;\n </p>\n ) : (\n <div\n ref={containerRef}\n role=\"listbox\"\n aria-label=\"Block type\"\n className=\"jikjo-menu-panel__list jikjo-menu-panel__list--scrollable\"\n >\n {filtered.map((item, index) => {\n const icon = ICON_MAP[item.id] ?? item.icon;\n const isActive = index === activeIndex;\n return (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n data-active={isActive}\n onMouseDown={(e) => {\n e.preventDefault();\n editor.focus(() => {\n item.onSelect(editor);\n onClose();\n });\n }}\n onMouseEnter={() => setActiveIndex(index)}\n className={`jikjo-menu-panel__item${isActive ? \" jikjo-menu-panel__item--active\" : \"\"}`}\n >\n <span className=\"jikjo-menu-panel__item-icon\">\n {icon}\n </span>\n <span className=\"jikjo-menu-panel__item-label\">\n {item.label}\n </span>\n </button>\n );\n })}\n </div>\n )}\n </motion.div>\n )}\n </AnimatePresence>,\n portalContainer,\n );\n}\n","\"use client\";\n\nimport {\n Editor,\n historyExtension,\n richTextExtension,\n useBlockHoverPlugin,\n useSelectionPlugin,\n useSlashCommandPlugin,\n} from \"@jikjo/core\";\nimport type { Extension, SlashMenuItem } from \"@jikjo/core\";\nimport {\n $createHeadingNode,\n $isHeadingNode,\n type HeadingTagType,\n} from \"@lexical/rich-text\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport {\n $createParagraphNode,\n $getNearestNodeFromDOMNode,\n $getSelection,\n $isRangeSelection,\n} from \"lexical\";\nimport {\n Bold,\n Code,\n Heading1,\n Heading2,\n Heading3,\n Italic,\n Strikethrough,\n Underline,\n} from \"lucide-react\";\nimport { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport type { MouseEvent } from \"react\";\nimport { BubbleMenu } from \"./components/bubble-menu\";\nimport { BlockToolbar } from \"./components/block-toolbar\";\nimport { SlashMenu } from \"./components/slash-menu\";\n\n// ─── Styles ───────────────────────────────────────────────────────────────────\n\nconst tbtnBase: CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: 32,\n height: 32,\n borderRadius: 6,\n border: \"none\",\n background: \"transparent\",\n color: \"#71717a\",\n cursor: \"pointer\",\n padding: 0,\n transition: \"background 100ms, color 100ms\",\n flexShrink: 0,\n};\n\nconst tbtnHover: CSSProperties = {\n background: \"rgba(39,39,42,0.9)\",\n color: \"#e4e4e7\",\n};\n\nconst tbtnActive: CSSProperties = {\n background: \"rgba(63,63,70,0.8)\",\n color: \"#f4f4f5\",\n};\n\n// ─── ToolbarButton ─────────────────────────────────────────────────────────────\n\ninterface ToolbarButtonProps {\n label: string;\n isActive?: boolean;\n onMouseDown: (e: MouseEvent) => void;\n children: ReactNode;\n}\n\nfunction ToolbarButton({ label, isActive, onMouseDown, children }: ToolbarButtonProps) {\n const [hovered, setHovered] = useState(false);\n\n const style: CSSProperties = {\n ...tbtnBase,\n ...(hovered ? tbtnHover : {}),\n ...(isActive ? tbtnActive : {}),\n };\n\n return (\n <button\n type=\"button\"\n aria-label={label}\n onMouseDown={onMouseDown}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n style={style}\n >\n {children}\n </button>\n );\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface EditorUIProps {\n extensions?: Extension[];\n namespace?: string;\n /**\n * undefined → default toolbar\n * false → no toolbar rendered at all\n * ReactNode → custom toolbar content\n */\n toolbarContent?: ReactNode | false;\n className?: string;\n /**\n * 활성화할 UI 기능 목록. 기본값: 모두 활성화.\n * \"blockHandle\" | \"inlineAdd\" | \"slashCommand\" | \"bubbleMenu\"\n */\n features?: Array<\"blockHandle\" | \"inlineAdd\" | \"slashCommand\" | \"bubbleMenu\">;\n}\n\n// ─── Default extensions ───────────────────────────────────────────────────────\n\nconst defaultExtensions: Extension[] = [richTextExtension, historyExtension];\n\n// ─── Inner component ──────────────────────────────────────────────────────────\n// Must live inside LexicalComposer so all hooks have editor context.\n\nconst ALL_FEATURES = [\"blockHandle\", \"inlineAdd\", \"slashCommand\", \"bubbleMenu\"] as const;\n\ninterface EditorInnerProps {\n extensions: Extension[];\n toolbarContent: ReactNode | false | undefined;\n features: Array<\"blockHandle\" | \"inlineAdd\" | \"slashCommand\" | \"bubbleMenu\">;\n}\n\nfunction EditorInner({\n extensions,\n toolbarContent,\n features,\n}: EditorInnerProps) {\n const [editor] = useLexicalComposerContext();\n\n const selection = useSelectionPlugin();\n const slashCommand = useSlashCommandPlugin();\n const blockHover = useBlockHoverPlugin();\n\n // heading 태그는 Lexical selection으로 추적 (toolbar 표시용)\n const [currentHeadingTag, setCurrentHeadingTag] = useState<HeadingTagType | null>(null);\n\n // focusedNodeKey: Lexical selection에서 완전히 독립.\n // - 에디터 click → DOM에서 직접 nodeKey 읽기\n // - dragstart → onDragStarted 콜백으로 nodeKey 전달\n // - drop 성공 → onDropped 콜백으로 이동된 nodeKey 전달\n // - dragend(취소) → null\n // - 에디터 바깥 클릭 → null\n const [focusedNodeKey, setFocusedNodeKey] = useState<string | null>(null);\n\n // drop 성공 플래그 (dragend 시 취소인지 성공인지 구분용)\n const dropSucceededRef = useRef(false);\n\n // ── heading tag 추적 (selection 기반, toolbar 전용) ──────────────────────\n\n useEffect(() => {\n return editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n const sel = $getSelection();\n if (!$isRangeSelection(sel)) {\n setCurrentHeadingTag(null);\n return;\n }\n const topNode = sel.anchor.getNode().getTopLevelElement();\n setCurrentHeadingTag($isHeadingNode(topNode) ? topNode.getTag() : null);\n });\n });\n }, [editor]);\n\n // ── focusedNodeKey: 에디터 click 이벤트에서 DOM → nodeKey 직접 읽기 ──────\n\n useEffect(() => {\n return editor.registerRootListener((rootElement) => {\n if (!rootElement) return;\n const root = rootElement;\n\n function onClick(e: Event) {\n const target = e.target as HTMLElement | null;\n if (!target) return;\n let key: string | null = null;\n editor.read(() => {\n const node = $getNearestNodeFromDOMNode(target);\n if (!node) return;\n const top = node.isInline() ? node.getTopLevelElement() : node;\n if (top) key = top.getKey();\n });\n if (key) setFocusedNodeKey(key);\n }\n\n // 에디터/툴바 바깥 클릭 시 커서 인디케이터 제거\n function onDocClick(e: Event) {\n const target = e.target as HTMLElement | null;\n if (root.contains(target)) return;\n if (target?.closest(\"[data-block-toolbar]\") || target?.closest(\"[data-drag-handle]\")) return;\n setFocusedNodeKey(null);\n }\n\n root.addEventListener(\"click\", onClick);\n document.addEventListener(\"click\", onDocClick, true);\n\n return () => {\n root.removeEventListener(\"click\", onClick);\n document.removeEventListener(\"click\", onDocClick, true);\n };\n });\n }, [editor]);\n\n // ── drag/drop 이벤트에서 focusedNodeKey 관리 ────────────────────────────\n // Firefox: drag handle mousedown → blur → dragstart 순서로 발생.\n // blur 시 Lexical이 $setSelection(null)을 호출하지만,\n // focusedNodeKey는 Lexical selection과 독립이므로 영향받지 않음.\n // drop 성공/취소는 onDropped / dragend 이벤트로 구분.\n\n const handleDragStarted = useCallback((key: string) => {\n dropSucceededRef.current = false;\n setFocusedNodeKey(key);\n // Firefox blur로 에디터 포커스가 빠진 경우 복구\n editor.focus();\n }, [editor]);\n\n const handleDropped = useCallback((key: string) => {\n dropSucceededRef.current = true;\n setFocusedNodeKey(key);\n // drop 후 에디터 포커스 복구\n editor.focus();\n }, [editor]);\n\n useEffect(() => {\n function onDragEnd() {\n if (!dropSucceededRef.current) {\n // drag 취소: 커서 인디케이터 제거\n setFocusedNodeKey(null);\n }\n dropSucceededRef.current = false;\n }\n document.addEventListener(\"dragend\", onDragEnd);\n return () => document.removeEventListener(\"dragend\", onDragEnd);\n }, []);\n\n // Collect slash menu items from all registered extensions\n const slashMenuItems = useMemo<SlashMenuItem[]>(\n () => extensions.flatMap((ext) => ext.slashMenuItems ?? []),\n [extensions],\n );\n\n // Toggle heading: if already at this level, revert to paragraph.\n // newNode.select() preserves selection after replace to avoid Lexical error.\n const toggleHeading = useCallback(\n (tag: HeadingTagType) => {\n editor.update(() => {\n const sel = $getSelection();\n if (!$isRangeSelection(sel)) return;\n const topNode = sel.anchor.getNode().getTopLevelElementOrThrow();\n const newNode = ($isHeadingNode(topNode) && topNode.getTag() === tag)\n ? $createParagraphNode()\n : $createHeadingNode(tag);\n topNode.replace(newNode);\n newNode.select();\n });\n },\n [editor],\n );\n\n return (\n <>\n {/* ── Toolbar ──────────────────────────────────────────────────── */}\n {toolbarContent === false ? null : toolbarContent !== undefined ? (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2, padding: \"8px 16px\", borderBottom: \"1px solid rgba(39,39,42,0.5)\" }}>\n {toolbarContent}\n </div>\n ) : (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2, padding: \"8px 16px\", borderBottom: \"1px solid rgba(39,39,42,0.5)\" }}>\n {/* Format group */}\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2 }}>\n <ToolbarButton\n label=\"Bold\"\n isActive={selection.format.bold}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"bold\"); }}\n >\n <Bold size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Italic\"\n isActive={selection.format.italic}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"italic\"); }}\n >\n <Italic size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Underline\"\n isActive={selection.format.underline}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"underline\"); }}\n >\n <Underline size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Strikethrough\"\n isActive={selection.format.strikethrough}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"strikethrough\"); }}\n >\n <Strikethrough size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Code\"\n isActive={selection.format.code}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"code\"); }}\n >\n <Code size={14} strokeWidth={2.5} />\n </ToolbarButton>\n </div>\n\n <div style={{ width: 1, height: 16, background: \"#27272a\", margin: \"0 4px\" }} />\n\n {/* Heading group */}\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2 }}>\n <ToolbarButton\n label=\"Heading 1\"\n isActive={currentHeadingTag === \"h1\"}\n onMouseDown={(e) => { e.preventDefault(); toggleHeading(\"h1\"); }}\n >\n <Heading1 size={15} strokeWidth={2} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Heading 2\"\n isActive={currentHeadingTag === \"h2\"}\n onMouseDown={(e) => { e.preventDefault(); toggleHeading(\"h2\"); }}\n >\n <Heading2 size={15} strokeWidth={2} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Heading 3\"\n isActive={currentHeadingTag === \"h3\"}\n onMouseDown={(e) => { e.preventDefault(); toggleHeading(\"h3\"); }}\n >\n <Heading3 size={15} strokeWidth={2} />\n </ToolbarButton>\n </div>\n </div>\n )}\n\n {/* ── Floating overlays ────────────────────────────────────────── */}\n {features.includes(\"bubbleMenu\") && (\n <BubbleMenu\n isVisible={selection.isActive}\n format={selection.format}\n onToggleFormat={selection.toggleFormat}\n editor={editor}\n />\n )}\n\n {features.includes(\"slashCommand\") && (\n <SlashMenu\n isVisible={slashCommand.isActive}\n query={slashCommand.query}\n items={slashMenuItems}\n editor={editor}\n onClose={slashCommand.close}\n />\n )}\n\n {/* drag handle + + 버튼 + 커서 인디케이터 */}\n <BlockToolbar\n isVisible={blockHover.isActive && (features.includes(\"blockHandle\") || features.includes(\"inlineAdd\"))}\n nodeKey={blockHover.nodeKey}\n focusedNodeKey={focusedNodeKey}\n items={slashMenuItems}\n editor={editor}\n showDragHandle={features.includes(\"blockHandle\")}\n showAddButton={features.includes(\"inlineAdd\")}\n onDragStarted={handleDragStarted}\n onDropped={handleDropped}\n />\n </>\n );\n}\n\n// ─── Public component ─────────────────────────────────────────────────────────\n\nexport function EditorUI({\n extensions = defaultExtensions,\n namespace = \"jikjo\",\n toolbarContent,\n className,\n features = [...ALL_FEATURES],\n}: EditorUIProps) {\n const hasBlockToolbar = features.includes(\"blockHandle\") || features.includes(\"inlineAdd\");\n return (\n <div\n className={className}\n {...(hasBlockToolbar ? { \"data-has-block-toolbar\": \"\" } : {})}\n // block toolbar가 있을 때만 좌측 74px padding을 주입\n // (GUTTER_LEFT 6 + 2×BTN 48 + GAP 4 = 58px toolbar + 16px text padding)\n style={hasBlockToolbar ? ({\n \"--jikjo-content-pl\": \"74px\",\n \"--jikjo-placeholder-pl\": \"74px\",\n } as React.CSSProperties) : ({\n \"--jikjo-content-pl\": \"16px\",\n \"--jikjo-placeholder-pl\": \"16px\",\n } as React.CSSProperties)}\n >\n <Editor extensions={extensions} namespace={namespace}>\n <EditorInner\n extensions={extensions}\n toolbarContent={toolbarContent}\n features={features}\n />\n </Editor>\n </div>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@jikjo/ui-kit",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
- "description": "Styled editor UI components for jikjo",
5
+ "description": "Pre-built editor UI components for jikjo with self-contained CSS and theming via CSS custom properties",
6
6
  "keywords": [
7
7
  "editor",
8
8
  "ui",
@@ -25,14 +25,13 @@
25
25
  "default": "./dist/index.cjs"
26
26
  }
27
27
  },
28
- "./styles.css": "./dist/styles.css"
28
+ "./styles.css": "./dist/index.css"
29
29
  },
30
30
  "files": [
31
31
  "dist"
32
32
  ],
33
33
  "dependencies": {
34
34
  "lucide-react": "^0.575.0",
35
- "tailwind-merge": "^3.5.0",
36
35
  "tailwind-variants": "^3.2.2",
37
36
  "@jikjo/core": "0.2.0"
38
37
  },
@@ -49,20 +48,19 @@
49
48
  "@base-ui/react": "^1.2.0",
50
49
  "@lexical/react": "^0.38.2",
51
50
  "@lexical/rich-text": "^0.38.2",
52
- "@tailwindcss/cli": "^4.2.1",
53
51
  "@types/react": "^19.1.8",
54
52
  "@types/react-dom": "^19.1.6",
55
53
  "lexical": "^0.38.2",
56
54
  "motion": "^11.18.2",
57
55
  "react": "^19.2.0",
58
56
  "react-dom": "^19.2.0",
59
- "tailwindcss": "^4.2.0",
60
57
  "tsdown": "^0.12.7",
61
58
  "typescript": "5.9.2",
59
+ "unplugin-lightningcss": "^0.4.5",
62
60
  "@jikjo/typescript-config": "0.0.0"
63
61
  },
64
62
  "scripts": {
65
- "build": "tsdown && node_modules/.bin/tailwindcss -i ./src/styles.css -o ./dist/styles.css --minify",
63
+ "build": "tsdown",
66
64
  "dev": "tsdown --watch",
67
65
  "check-types": "tsc --noEmit"
68
66
  }
package/dist/styles.css DELETED
@@ -1,2 +0,0 @@
1
- /*! tailwindcss v4.2.1 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-normal:400;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.fixed{position:fixed}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-0\.5{margin-inline:calc(var(--spacing) * .5)}.block{display:block}.flex{display:flex}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-8{height:calc(var(--spacing) * 8)}.max-h-72{max-height:calc(var(--spacing) * 72)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-8{width:calc(var(--spacing) * 8)}.w-full{width:100%}.w-px{width:1px}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-grab{cursor:grab}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-3{gap:calc(var(--spacing) * 3)}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.bg-white\/15{background-color:#ffffff26}@supports (color:color-mix(in lab, red, red)){.bg-white\/15{background-color:color-mix(in oklab, var(--color-white) 15%, transparent)}}.bg-zinc-600\/50{background-color:#52525c80}@supports (color:color-mix(in lab, red, red)){.bg-zinc-600\/50{background-color:color-mix(in oklab, var(--color-zinc-600) 50%, transparent)}}.bg-zinc-700\/60{background-color:#3f3f4699}@supports (color:color-mix(in lab, red, red)){.bg-zinc-700\/60{background-color:color-mix(in oklab, var(--color-zinc-700) 60%, transparent)}}.bg-zinc-700\/80{background-color:#3f3f46cc}@supports (color:color-mix(in lab, red, red)){.bg-zinc-700\/80{background-color:color-mix(in oklab, var(--color-zinc-700) 80%, transparent)}}.bg-zinc-800{background-color:var(--color-zinc-800)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.text-left{text-align:left}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.text-current{color:currentColor}.text-white{color:var(--color-white)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.italic{font-style:italic}.underline{text-decoration-line:underline}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-black\/50{--tw-shadow-color:#00000080}@supports (color:color-mix(in lab, red, red)){.shadow-black\/50{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-black) 50%, transparent) var(--tw-shadow-alpha), transparent)}}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-75{--tw-duration:75ms;transition-duration:75ms}.duration-100{--tw-duration:.1s;transition-duration:.1s}.outline-none{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/10:hover{background-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.hover\:bg-zinc-700\/40:hover{background-color:#3f3f4666}@supports (color:color-mix(in lab, red, red)){.hover\:bg-zinc-700\/40:hover{background-color:color-mix(in oklab, var(--color-zinc-700) 40%, transparent)}}.hover\:bg-zinc-700\/60:hover{background-color:#3f3f4699}@supports (color:color-mix(in lab, red, red)){.hover\:bg-zinc-700\/60:hover{background-color:color-mix(in oklab, var(--color-zinc-700) 60%, transparent)}}.hover\:bg-zinc-700\/80:hover{background-color:#3f3f46cc}@supports (color:color-mix(in lab, red, red)){.hover\:bg-zinc-700\/80:hover{background-color:color-mix(in oklab, var(--color-zinc-700) 80%, transparent)}}.hover\:text-zinc-200:hover{color:var(--color-zinc-200)}.hover\:text-zinc-300:hover{color:var(--color-zinc-300)}}.focus-visible\:bg-zinc-700\/80:focus-visible{background-color:#3f3f46cc}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-zinc-700\/80:focus-visible{background-color:color-mix(in oklab, var(--color-zinc-700) 80%, transparent)}}.focus-visible\:text-zinc-100:focus-visible{color:var(--color-zinc-100)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:cursor-grabbing:active{cursor:grabbing}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}