@particle-academy/react-fancy 3.1.0 → 3.2.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
@@ -12156,7 +12156,8 @@ function PromptInput({
12156
12156
  placeholder = "Ask anything. Type / for commands, @ for mentions. \u2318/Ctrl+Enter to send.",
12157
12157
  charsPerToken = 4,
12158
12158
  mentionColor,
12159
- maxHeight = 280
12159
+ maxHeight = 280,
12160
+ aboveInput
12160
12161
  }) {
12161
12162
  const [text, setText] = react.useState("");
12162
12163
  const [attachments, setAttachments] = react.useState([]);
@@ -12292,6 +12293,7 @@ function PromptInput({
12292
12293
  onDrop,
12293
12294
  className: `relative rounded-md border transition ${dragOver ? "border-violet-400 bg-violet-50/50 dark:border-violet-600 dark:bg-violet-950/30" : "border-zinc-200 dark:border-zinc-800"} bg-white dark:bg-zinc-900`,
12294
12295
  children: [
12296
+ aboveInput && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-zinc-200 dark:border-zinc-800", children: aboveInput }),
12295
12297
  attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 border-b border-zinc-200 px-3 py-2 dark:border-zinc-800", children: attachments.map((a) => /* @__PURE__ */ jsxRuntime.jsxs(
12296
12298
  "span",
12297
12299
  {
@@ -12432,6 +12434,491 @@ function fmtSize(b) {
12432
12434
  if (b < 1024 * 1024) return `${(b / 1024).toFixed(0)} KB`;
12433
12435
  return `${(b / 1024 / 1024).toFixed(1)} MB`;
12434
12436
  }
12437
+ function ChatDrawer({
12438
+ tabs,
12439
+ activeTabId,
12440
+ onTabChange,
12441
+ open = true,
12442
+ onToggle,
12443
+ children,
12444
+ minBodyHeight = 140,
12445
+ className
12446
+ }) {
12447
+ const handleToggle = react.useCallback(() => {
12448
+ onToggle?.(!open);
12449
+ }, [onToggle, open]);
12450
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, children: [
12451
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 px-2.5 py-2", children: [
12452
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-1.5", children: tabs.map((tab, i) => {
12453
+ const active = tab.id === activeTabId;
12454
+ const number = tab.number === null ? null : tab.number ?? i + 1;
12455
+ return /* @__PURE__ */ jsxRuntime.jsxs(
12456
+ "button",
12457
+ {
12458
+ type: "button",
12459
+ onClick: () => onTabChange(tab.id),
12460
+ className: [
12461
+ "group inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-[12px] font-medium transition",
12462
+ active ? "border-violet-500 bg-violet-500 text-white" : "border-zinc-200 bg-transparent text-zinc-700 hover:border-zinc-300 hover:bg-zinc-50 dark:border-zinc-700 dark:text-zinc-300 dark:hover:border-zinc-600 dark:hover:bg-zinc-800"
12463
+ ].join(" "),
12464
+ "aria-pressed": active,
12465
+ children: [
12466
+ number !== null && /* @__PURE__ */ jsxRuntime.jsx(
12467
+ "span",
12468
+ {
12469
+ className: [
12470
+ "inline-flex h-4 w-4 items-center justify-center rounded-full text-[10px] font-semibold",
12471
+ active ? "bg-white/20 text-white" : "bg-zinc-200 text-zinc-600 group-hover:bg-zinc-300 dark:bg-zinc-700 dark:text-zinc-300"
12472
+ ].join(" "),
12473
+ children: number
12474
+ }
12475
+ ),
12476
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate max-w-[180px]", children: tab.label })
12477
+ ]
12478
+ },
12479
+ tab.id
12480
+ );
12481
+ }) }),
12482
+ /* @__PURE__ */ jsxRuntime.jsx(
12483
+ "button",
12484
+ {
12485
+ type: "button",
12486
+ onClick: handleToggle,
12487
+ "aria-label": open ? "Collapse drawer" : "Expand drawer",
12488
+ "aria-expanded": open,
12489
+ className: "ml-auto inline-flex h-6 w-6 items-center justify-center rounded-full text-zinc-400 transition hover:bg-zinc-100 hover:text-zinc-700 dark:hover:bg-zinc-800 dark:hover:text-zinc-200",
12490
+ children: /* @__PURE__ */ jsxRuntime.jsx(
12491
+ Icon,
12492
+ {
12493
+ name: open ? "chevron-down" : "chevron-up",
12494
+ className: "h-3.5 w-3.5"
12495
+ }
12496
+ )
12497
+ }
12498
+ )
12499
+ ] }),
12500
+ open && /* @__PURE__ */ jsxRuntime.jsx(
12501
+ "div",
12502
+ {
12503
+ className: "px-2.5 pb-2.5",
12504
+ style: { minHeight: minBodyHeight },
12505
+ children
12506
+ }
12507
+ )
12508
+ ] });
12509
+ }
12510
+ function defaultKeyOf(item) {
12511
+ if (item && typeof item === "object") {
12512
+ const r = item;
12513
+ if (typeof r.name === "string") return r.name;
12514
+ if (typeof r.id === "string") return r.id;
12515
+ }
12516
+ return String(item);
12517
+ }
12518
+ function defaultFilter(item, query) {
12519
+ if (!query) return true;
12520
+ return defaultKeyOf(item).toLowerCase().includes(query.toLowerCase());
12521
+ }
12522
+ function detectTrigger(text, caret, triggerChars) {
12523
+ for (let i = caret - 1; i >= 0; i--) {
12524
+ const ch = text[i];
12525
+ if (ch === void 0) break;
12526
+ if (triggerChars.has(ch)) {
12527
+ if (i === 0 || /\s/.test(text[i - 1])) {
12528
+ return {
12529
+ char: ch,
12530
+ start: i,
12531
+ end: caret,
12532
+ query: text.slice(i + 1, caret)
12533
+ };
12534
+ }
12535
+ return null;
12536
+ }
12537
+ if (/\s/.test(ch)) return null;
12538
+ }
12539
+ return null;
12540
+ }
12541
+ function InputTag({
12542
+ adapter,
12543
+ triggers,
12544
+ maxItems = 8,
12545
+ placement = "bottom-left",
12546
+ className,
12547
+ style,
12548
+ onPick
12549
+ }) {
12550
+ const triggerChars = react.useMemo(() => new Set(Object.keys(triggers)), [triggers]);
12551
+ const [active, setActive] = react.useState(null);
12552
+ const [cursor, setCursor] = react.useState(0);
12553
+ const [anchorRect, setAnchorRect] = react.useState(null);
12554
+ const stateRef = react.useRef({ text: "", caretIndex: 0 });
12555
+ const activeRef = react.useRef(null);
12556
+ const cursorRef = react.useRef(0);
12557
+ activeRef.current = active;
12558
+ cursorRef.current = cursor;
12559
+ const items = react.useMemo(() => {
12560
+ if (!active) return [];
12561
+ const cfg2 = triggers[active.char];
12562
+ if (!cfg2) return [];
12563
+ const filter = cfg2.filter ?? defaultFilter;
12564
+ const out = [];
12565
+ for (const item of cfg2.items) {
12566
+ if (filter(item, active.query)) out.push(item);
12567
+ if (out.length >= maxItems) break;
12568
+ }
12569
+ return out;
12570
+ }, [active, triggers, maxItems]);
12571
+ react.useEffect(() => {
12572
+ return adapter.subscribe((s) => {
12573
+ stateRef.current = s;
12574
+ const next = detectTrigger(s.text, s.caretIndex, triggerChars);
12575
+ setActive((prev) => {
12576
+ if (!prev || !next || prev.char !== next.char || prev.start !== next.start) {
12577
+ setCursor(0);
12578
+ }
12579
+ return next;
12580
+ });
12581
+ if (next) setAnchorRect(adapter.getAnchorRect());
12582
+ });
12583
+ }, [adapter, triggerChars]);
12584
+ react.useLayoutEffect(() => {
12585
+ if (!active) return;
12586
+ const update = () => setAnchorRect(adapter.getAnchorRect());
12587
+ update();
12588
+ window.addEventListener("scroll", update, true);
12589
+ window.addEventListener("resize", update);
12590
+ return () => {
12591
+ window.removeEventListener("scroll", update, true);
12592
+ window.removeEventListener("resize", update);
12593
+ };
12594
+ }, [active, adapter]);
12595
+ react.useEffect(() => {
12596
+ if (cursor >= items.length) setCursor(Math.max(0, items.length - 1));
12597
+ }, [items.length, cursor]);
12598
+ const pickItem = react.useCallback(
12599
+ (item) => {
12600
+ const cur = activeRef.current;
12601
+ if (!cur) return;
12602
+ const cfg2 = triggers[cur.char];
12603
+ if (!cfg2) return;
12604
+ const replacement = cfg2.insert(item, cur.query);
12605
+ adapter.replaceRange(cur.start, cur.end, replacement);
12606
+ onPick?.({ triggerChar: cur.char, query: cur.query, item });
12607
+ setActive(null);
12608
+ },
12609
+ [adapter, triggers, onPick]
12610
+ );
12611
+ react.useEffect(() => {
12612
+ return adapter.onKey((e) => {
12613
+ const cur = activeRef.current;
12614
+ if (!cur) return false;
12615
+ const visible = items;
12616
+ switch (e.key) {
12617
+ case "ArrowDown":
12618
+ if (visible.length === 0) return false;
12619
+ setCursor((c) => (c + 1) % visible.length);
12620
+ return true;
12621
+ case "ArrowUp":
12622
+ if (visible.length === 0) return false;
12623
+ setCursor((c) => (c - 1 + visible.length) % visible.length);
12624
+ return true;
12625
+ case "Enter":
12626
+ case "Tab": {
12627
+ if (visible.length === 0) return false;
12628
+ const idx = Math.min(cursorRef.current, visible.length - 1);
12629
+ pickItem(visible[idx]);
12630
+ return true;
12631
+ }
12632
+ case "Escape":
12633
+ setActive(null);
12634
+ return true;
12635
+ default:
12636
+ return false;
12637
+ }
12638
+ });
12639
+ }, [adapter, items, pickItem]);
12640
+ if (!active || !anchorRect || items.length === 0) return null;
12641
+ const cfg = triggers[active.char];
12642
+ if (!cfg) return null;
12643
+ const popStyle = computePopoverStyle(anchorRect, placement);
12644
+ return /* @__PURE__ */ jsxRuntime.jsxs(
12645
+ "div",
12646
+ {
12647
+ role: "listbox",
12648
+ "aria-label": cfg.label ?? `Pick a ${active.char === "/" ? "command" : "mention"}`,
12649
+ className: [
12650
+ "fixed z-50 w-72 overflow-hidden rounded-md border border-zinc-200 bg-white shadow-lg dark:border-zinc-700 dark:bg-zinc-900",
12651
+ className ?? ""
12652
+ ].filter(Boolean).join(" "),
12653
+ style: { ...popStyle, ...style },
12654
+ children: [
12655
+ (cfg.label || active.query) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-b border-zinc-100 bg-zinc-50 px-2 py-1 text-[10px] font-semibold uppercase tracking-wider text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950", children: [
12656
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: cfg.label ?? labelForChar(active.char) }),
12657
+ active.query && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono text-[10px] normal-case tracking-normal text-zinc-400", children: [
12658
+ active.char,
12659
+ active.query
12660
+ ] })
12661
+ ] }),
12662
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "max-h-60 overflow-y-auto", children: items.map((item, i) => {
12663
+ const renderer = cfg.render ?? ((it) => /* @__PURE__ */ jsxRuntime.jsx("span", { children: defaultKeyOf(it) }));
12664
+ const active2 = i === cursor;
12665
+ const key = (cfg.keyOf ?? defaultKeyOf)(item);
12666
+ return /* @__PURE__ */ jsxRuntime.jsx(
12667
+ "li",
12668
+ {
12669
+ role: "option",
12670
+ "aria-selected": active2,
12671
+ onMouseDown: (e) => {
12672
+ e.preventDefault();
12673
+ pickItem(item);
12674
+ },
12675
+ onMouseEnter: () => setCursor(i),
12676
+ className: [
12677
+ "cursor-pointer px-2 py-1.5 text-[12px]",
12678
+ active2 ? "bg-violet-100 dark:bg-violet-900/30" : "hover:bg-zinc-50 dark:hover:bg-zinc-800"
12679
+ ].join(" "),
12680
+ children: renderer(item, active2)
12681
+ },
12682
+ key
12683
+ );
12684
+ }) })
12685
+ ]
12686
+ }
12687
+ );
12688
+ }
12689
+ function labelForChar(ch) {
12690
+ if (ch === "/") return "Commands";
12691
+ if (ch === "@") return "Mentions";
12692
+ if (ch === "#") return "Tags";
12693
+ if (ch === ":") return "Emoji";
12694
+ return `Trigger ${ch}`;
12695
+ }
12696
+ function computePopoverStyle(anchor, placement) {
12697
+ const offset = 4;
12698
+ switch (placement) {
12699
+ case "bottom-right":
12700
+ return { top: anchor.bottom + offset, right: window.innerWidth - anchor.right };
12701
+ case "top-left":
12702
+ return { bottom: window.innerHeight - anchor.top + offset, left: anchor.left };
12703
+ case "top-right":
12704
+ return { bottom: window.innerHeight - anchor.top + offset, right: window.innerWidth - anchor.right };
12705
+ case "bottom-left":
12706
+ default:
12707
+ return { top: anchor.bottom + offset, left: anchor.left };
12708
+ }
12709
+ }
12710
+
12711
+ // src/components/InputTag/adapters.ts
12712
+ function nativeValueSetter(el) {
12713
+ const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
12714
+ const desc = Object.getOwnPropertyDescriptor(proto, "value");
12715
+ if (!desc?.set) return null;
12716
+ return desc.set.bind(el);
12717
+ }
12718
+ function replaceInValueElement(el, start, end, replacement) {
12719
+ const current = el.value;
12720
+ const next = current.slice(0, start) + replacement + current.slice(end);
12721
+ const setter = nativeValueSetter(el);
12722
+ if (setter) {
12723
+ setter(next);
12724
+ } else {
12725
+ el.value = next;
12726
+ }
12727
+ el.dispatchEvent(new Event("input", { bubbles: true }));
12728
+ const caret = start + replacement.length;
12729
+ el.setSelectionRange(caret, caret);
12730
+ el.focus();
12731
+ }
12732
+ function valueElementAdapter(ref) {
12733
+ return {
12734
+ subscribe(fn) {
12735
+ const handler = () => {
12736
+ const el2 = ref.current;
12737
+ if (!el2) return;
12738
+ fn({
12739
+ text: el2.value,
12740
+ caretIndex: el2.selectionStart ?? el2.value.length
12741
+ });
12742
+ };
12743
+ const el = ref.current;
12744
+ if (el) {
12745
+ el.addEventListener("input", handler);
12746
+ el.addEventListener("click", handler);
12747
+ el.addEventListener("keyup", handler);
12748
+ el.addEventListener("focus", handler);
12749
+ }
12750
+ return () => {
12751
+ const el2 = ref.current;
12752
+ if (el2) {
12753
+ el2.removeEventListener("input", handler);
12754
+ el2.removeEventListener("click", handler);
12755
+ el2.removeEventListener("keyup", handler);
12756
+ el2.removeEventListener("focus", handler);
12757
+ }
12758
+ };
12759
+ },
12760
+ replaceRange(start, end, replacement) {
12761
+ const el = ref.current;
12762
+ if (!el) return;
12763
+ replaceInValueElement(el, start, end, replacement);
12764
+ },
12765
+ getAnchorRect() {
12766
+ const el = ref.current;
12767
+ return el ? el.getBoundingClientRect() : null;
12768
+ },
12769
+ onKey(handler) {
12770
+ const fn = (e) => {
12771
+ if (handler(e)) {
12772
+ e.preventDefault();
12773
+ e.stopPropagation();
12774
+ }
12775
+ };
12776
+ const el = ref.current;
12777
+ if (el) el.addEventListener("keydown", fn);
12778
+ return () => {
12779
+ const el2 = ref.current;
12780
+ if (el2) el2.removeEventListener("keydown", fn);
12781
+ };
12782
+ }
12783
+ };
12784
+ }
12785
+ function textareaAdapter(ref) {
12786
+ return valueElementAdapter(ref);
12787
+ }
12788
+ function inputAdapter(ref) {
12789
+ return valueElementAdapter(ref);
12790
+ }
12791
+ function contentEditableAdapter(ref) {
12792
+ function caretIndex(el) {
12793
+ const sel = window.getSelection();
12794
+ if (!sel || sel.rangeCount === 0) return 0;
12795
+ const range2 = sel.getRangeAt(0).cloneRange();
12796
+ range2.selectNodeContents(el);
12797
+ range2.setEnd(sel.focusNode, sel.focusOffset);
12798
+ return range2.toString().length;
12799
+ }
12800
+ function setCaret(el, index) {
12801
+ const sel = window.getSelection();
12802
+ if (!sel) return;
12803
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
12804
+ let remaining = index;
12805
+ let node = walker.nextNode();
12806
+ while (node) {
12807
+ const len = (node.textContent ?? "").length;
12808
+ if (remaining <= len) {
12809
+ const range3 = document.createRange();
12810
+ range3.setStart(node, remaining);
12811
+ range3.collapse(true);
12812
+ sel.removeAllRanges();
12813
+ sel.addRange(range3);
12814
+ return;
12815
+ }
12816
+ remaining -= len;
12817
+ node = walker.nextNode();
12818
+ }
12819
+ const range2 = document.createRange();
12820
+ range2.selectNodeContents(el);
12821
+ range2.collapse(false);
12822
+ sel.removeAllRanges();
12823
+ sel.addRange(range2);
12824
+ }
12825
+ return {
12826
+ subscribe(fn) {
12827
+ const handler = () => {
12828
+ const el2 = ref.current;
12829
+ if (!el2) return;
12830
+ fn({ text: el2.textContent ?? "", caretIndex: caretIndex(el2) });
12831
+ };
12832
+ const el = ref.current;
12833
+ if (el) {
12834
+ el.addEventListener("input", handler);
12835
+ el.addEventListener("click", handler);
12836
+ el.addEventListener("keyup", handler);
12837
+ el.addEventListener("focus", handler);
12838
+ }
12839
+ return () => {
12840
+ const el2 = ref.current;
12841
+ if (el2) {
12842
+ el2.removeEventListener("input", handler);
12843
+ el2.removeEventListener("click", handler);
12844
+ el2.removeEventListener("keyup", handler);
12845
+ el2.removeEventListener("focus", handler);
12846
+ }
12847
+ };
12848
+ },
12849
+ replaceRange(start, end, replacement) {
12850
+ const el = ref.current;
12851
+ if (!el) return;
12852
+ const current = el.textContent ?? "";
12853
+ const next = current.slice(0, start) + replacement + current.slice(end);
12854
+ el.textContent = next;
12855
+ setCaret(el, start + replacement.length);
12856
+ el.dispatchEvent(new Event("input", { bubbles: true }));
12857
+ el.focus();
12858
+ },
12859
+ getAnchorRect() {
12860
+ const el = ref.current;
12861
+ return el ? el.getBoundingClientRect() : null;
12862
+ },
12863
+ onKey(handler) {
12864
+ const fn = (e) => {
12865
+ if (handler(e)) {
12866
+ e.preventDefault();
12867
+ e.stopPropagation();
12868
+ }
12869
+ };
12870
+ const el = ref.current;
12871
+ if (el) el.addEventListener("keydown", fn);
12872
+ return () => {
12873
+ const el2 = ref.current;
12874
+ if (el2) el2.removeEventListener("keydown", fn);
12875
+ };
12876
+ }
12877
+ };
12878
+ }
12879
+ function controlledAdapter(opts) {
12880
+ let listener = null;
12881
+ let keyHandler = null;
12882
+ const installKey = () => {
12883
+ const el = opts.anchorRef.current;
12884
+ if (!el || !keyHandler) return () => {
12885
+ };
12886
+ const fn = (e) => {
12887
+ if (keyHandler && keyHandler(e)) {
12888
+ e.preventDefault();
12889
+ e.stopPropagation();
12890
+ }
12891
+ };
12892
+ el.addEventListener("keydown", fn);
12893
+ return () => el.removeEventListener("keydown", fn);
12894
+ };
12895
+ return {
12896
+ notify(state) {
12897
+ listener?.(state);
12898
+ },
12899
+ subscribe(fn) {
12900
+ listener = fn;
12901
+ return () => {
12902
+ if (listener === fn) listener = null;
12903
+ };
12904
+ },
12905
+ replaceRange(start, end, replacement) {
12906
+ opts.onReplaceRange(start, end, replacement);
12907
+ },
12908
+ getAnchorRect() {
12909
+ const el = opts.anchorRef.current;
12910
+ return el ? el.getBoundingClientRect() : null;
12911
+ },
12912
+ onKey(handler) {
12913
+ keyHandler = handler;
12914
+ const uninstall = installKey();
12915
+ return () => {
12916
+ if (keyHandler === handler) keyHandler = null;
12917
+ uninstall();
12918
+ };
12919
+ }
12920
+ };
12921
+ }
12435
12922
  function MagicWand({
12436
12923
  value,
12437
12924
  onValueChange,
@@ -12641,6 +13128,7 @@ exports.Callout = Callout;
12641
13128
  exports.Card = Card;
12642
13129
  exports.Carousel = Carousel;
12643
13130
  exports.Chart = Chart;
13131
+ exports.ChatDrawer = ChatDrawer;
12644
13132
  exports.Checkbox = Checkbox;
12645
13133
  exports.CheckboxGroup = CheckboxGroup;
12646
13134
  exports.ColorPicker = ColorPicker;
@@ -12661,6 +13149,7 @@ exports.FileUpload = FileUpload;
12661
13149
  exports.Heading = Heading;
12662
13150
  exports.Icon = Icon;
12663
13151
  exports.Input = Input;
13152
+ exports.InputTag = InputTag;
12664
13153
  exports.Kanban = Kanban;
12665
13154
  exports.MagicWand = MagicWand;
12666
13155
  exports.Menu = Menu2;
@@ -12698,8 +13187,11 @@ exports.TreeNav = TreeNav;
12698
13187
  exports.applyTone = applyTone;
12699
13188
  exports.cn = cn;
12700
13189
  exports.configureIcons = configureIcons;
13190
+ exports.contentEditableAdapter = contentEditableAdapter;
13191
+ exports.controlledAdapter = controlledAdapter;
12701
13192
  exports.find = find;
12702
13193
  exports.hasSkinTones = hasSkinTones;
13194
+ exports.inputAdapter = inputAdapter;
12703
13195
  exports.registerExtension = registerExtension;
12704
13196
  exports.registerExtensions = registerExtensions;
12705
13197
  exports.registerIconSet = registerIconSet;
@@ -12709,6 +13201,7 @@ exports.sanitizeHref = sanitizeHref;
12709
13201
  exports.sanitizeHtml = sanitizeHtml;
12710
13202
  exports.search = search;
12711
13203
  exports.skinTones = skinTones;
13204
+ exports.textareaAdapter = textareaAdapter;
12712
13205
  exports.useAccordion = useAccordion;
12713
13206
  exports.useAccordionPanel = useAccordionPanel;
12714
13207
  exports.useAccordionSection = useAccordionSection;