@pactor-app/ui 1.43.0 → 1.46.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/ui/index.js CHANGED
@@ -25,10 +25,6 @@ import {
25
25
  BreadcrumbList,
26
26
  BreadcrumbPage,
27
27
  BreadcrumbSeparator,
28
- Bubble,
29
- BubbleContent,
30
- BubbleGroup,
31
- BubbleReactions,
32
28
  ButtonGroup,
33
29
  Calendar,
34
30
  Carousel,
@@ -161,7 +157,7 @@ import {
161
157
  toggleVariants,
162
158
  typographyVariants,
163
159
  useCarousel
164
- } from "../chunk-T3TGKYAB.js";
160
+ } from "../chunk-XA7J5SGA.js";
165
161
  import {
166
162
  Dialog,
167
163
  DialogClose,
@@ -247,10 +243,48 @@ import {
247
243
  cn
248
244
  } from "../chunk-WKJUCGV4.js";
249
245
 
246
+ // src/ui/action-button.tsx
247
+ import { jsx, jsxs } from "react/jsx-runtime";
248
+ function ActionButton({
249
+ className,
250
+ variant,
251
+ size,
252
+ icon,
253
+ label,
254
+ tooltip,
255
+ type = "button",
256
+ ...props
257
+ }) {
258
+ const resolvedSize = size ?? (label ? "sm" : "icon-sm");
259
+ if (process.env.NODE_ENV !== "production" && !label && !props["aria-label"]) {
260
+ console.warn("[ActionButton] icon-only \u7528\u6CD5\u987B\u4F20 aria-label \u4EE5\u4FDD\u8BC1\u53EF\u8BBF\u95EE\u6027\u3002");
261
+ }
262
+ const button = /* @__PURE__ */ jsxs(
263
+ Button,
264
+ {
265
+ "data-slot": "action-button",
266
+ variant,
267
+ size: resolvedSize,
268
+ type,
269
+ className: cn(className),
270
+ ...props,
271
+ children: [
272
+ icon,
273
+ label ? /* @__PURE__ */ jsx("span", { children: label }) : null
274
+ ]
275
+ }
276
+ );
277
+ if (!tooltip) return button;
278
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
279
+ /* @__PURE__ */ jsx(TooltipTrigger, { render: button }),
280
+ /* @__PURE__ */ jsx(TooltipContent, { children: tooltip })
281
+ ] });
282
+ }
283
+
250
284
  // src/ui/attachment.tsx
251
285
  import { cva } from "class-variance-authority";
252
286
  import * as React from "react";
253
- import { jsx } from "react/jsx-runtime";
287
+ import { jsx as jsx2 } from "react/jsx-runtime";
254
288
  var attachmentVariants = cva(
255
289
  "group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
256
290
  {
@@ -274,7 +308,7 @@ function Attachment({
274
308
  orientation = "horizontal",
275
309
  ...props
276
310
  }) {
277
- return /* @__PURE__ */ jsx(
311
+ return /* @__PURE__ */ jsx2(
278
312
  "div",
279
313
  {
280
314
  "data-slot": "attachment",
@@ -305,7 +339,7 @@ function AttachmentMedia({
305
339
  variant = "icon",
306
340
  ...props
307
341
  }) {
308
- return /* @__PURE__ */ jsx(
342
+ return /* @__PURE__ */ jsx2(
309
343
  "div",
310
344
  {
311
345
  "data-slot": "attachment-media",
@@ -319,7 +353,7 @@ function AttachmentContent({
319
353
  className,
320
354
  ...props
321
355
  }) {
322
- return /* @__PURE__ */ jsx(
356
+ return /* @__PURE__ */ jsx2(
323
357
  "div",
324
358
  {
325
359
  "data-slot": "attachment-content",
@@ -335,7 +369,7 @@ function AttachmentTitle({
335
369
  className,
336
370
  ...props
337
371
  }) {
338
- return /* @__PURE__ */ jsx(
372
+ return /* @__PURE__ */ jsx2(
339
373
  "span",
340
374
  {
341
375
  "data-slot": "attachment-title",
@@ -351,7 +385,7 @@ function AttachmentDescription({
351
385
  className,
352
386
  ...props
353
387
  }) {
354
- return /* @__PURE__ */ jsx(
388
+ return /* @__PURE__ */ jsx2(
355
389
  "span",
356
390
  {
357
391
  "data-slot": "attachment-description",
@@ -368,7 +402,7 @@ function AttachmentActions({
368
402
  className,
369
403
  ...props
370
404
  }) {
371
- return /* @__PURE__ */ jsx(
405
+ return /* @__PURE__ */ jsx2(
372
406
  "div",
373
407
  {
374
408
  "data-slot": "attachment-actions",
@@ -386,7 +420,7 @@ function AttachmentAction({
386
420
  size = "icon-xs",
387
421
  ...props
388
422
  }) {
389
- return /* @__PURE__ */ jsx(
423
+ return /* @__PURE__ */ jsx2(
390
424
  Button,
391
425
  {
392
426
  "data-slot": "attachment-action",
@@ -415,7 +449,7 @@ function AttachmentTrigger({
415
449
  ...props
416
450
  });
417
451
  }
418
- return /* @__PURE__ */ jsx(
452
+ return /* @__PURE__ */ jsx2(
419
453
  "button",
420
454
  {
421
455
  "data-slot": "attachment-trigger",
@@ -427,7 +461,7 @@ function AttachmentTrigger({
427
461
  );
428
462
  }
429
463
  function AttachmentGroup({ className, ...props }) {
430
- return /* @__PURE__ */ jsx(
464
+ return /* @__PURE__ */ jsx2(
431
465
  "div",
432
466
  {
433
467
  "data-slot": "attachment-group",
@@ -440,10 +474,584 @@ function AttachmentGroup({ className, ...props }) {
440
474
  );
441
475
  }
442
476
 
477
+ // src/ui/bubble.tsx
478
+ import * as React2 from "react";
479
+ import { cva as cva2 } from "class-variance-authority";
480
+ import { jsx as jsx3 } from "react/jsx-runtime";
481
+ function BubbleGroup({ className, ...props }) {
482
+ return /* @__PURE__ */ jsx3(
483
+ "div",
484
+ {
485
+ "data-slot": "bubble-group",
486
+ className: cn("flex min-w-0 flex-col gap-2", className),
487
+ ...props
488
+ }
489
+ );
490
+ }
491
+ var bubbleVariants = cva2(
492
+ "group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
493
+ {
494
+ variants: {
495
+ variant: {
496
+ default: "*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80",
497
+ secondary: "*:data-[slot=bubble-content]:bg-secondary *:data-[slot=bubble-content]:text-secondary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]",
498
+ muted: "*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]",
499
+ tinted: "*:data-[slot=bubble-content]:bg-(--bubble-background) *:data-[slot=bubble-content]:text-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-(--bubble-hover-background)",
500
+ outline: "*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-card [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30",
501
+ ghost: "border-none *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50",
502
+ destructive: "*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30"
503
+ }
504
+ },
505
+ defaultVariants: {
506
+ variant: "default"
507
+ }
508
+ }
509
+ );
510
+ function Bubble({
511
+ variant = "default",
512
+ align = "start",
513
+ className,
514
+ ...props
515
+ }) {
516
+ return /* @__PURE__ */ jsx3(
517
+ "div",
518
+ {
519
+ "data-slot": "bubble",
520
+ "data-variant": variant,
521
+ "data-align": align,
522
+ className: cn(bubbleVariants({ variant }), className),
523
+ ...props
524
+ }
525
+ );
526
+ }
527
+ function BubbleContent({
528
+ asChild = false,
529
+ className,
530
+ children,
531
+ ...props
532
+ }) {
533
+ const cls = cn(
534
+ "w-fit max-w-full min-w-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50",
535
+ className
536
+ );
537
+ if (asChild && React2.isValidElement(children)) {
538
+ return React2.cloneElement(children, {
539
+ "data-slot": "bubble-content",
540
+ className: cn(cls, children.props.className),
541
+ ...props
542
+ });
543
+ }
544
+ return /* @__PURE__ */ jsx3(
545
+ "div",
546
+ {
547
+ "data-slot": "bubble-content",
548
+ className: cls,
549
+ ...props,
550
+ children
551
+ }
552
+ );
553
+ }
554
+ var bubbleReactionsVariants = cva2(
555
+ "absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0",
556
+ {
557
+ variants: {
558
+ side: {
559
+ top: "top-0 -translate-y-3/4",
560
+ bottom: "bottom-0 translate-y-3/4"
561
+ },
562
+ align: {
563
+ start: "left-3",
564
+ end: "right-3"
565
+ }
566
+ },
567
+ defaultVariants: {
568
+ side: "bottom",
569
+ align: "end"
570
+ }
571
+ }
572
+ );
573
+ function BubbleReactions({
574
+ side = "bottom",
575
+ align = "end",
576
+ className,
577
+ ...props
578
+ }) {
579
+ return /* @__PURE__ */ jsx3(
580
+ "div",
581
+ {
582
+ "data-slot": "bubble-reactions",
583
+ "data-align": align,
584
+ "data-side": side,
585
+ className: cn(bubbleReactionsVariants({ side, align }), className),
586
+ ...props
587
+ }
588
+ );
589
+ }
590
+
591
+ // src/ui/confirm-morph.tsx
592
+ import { CheckIcon, RotateCcwIcon } from "lucide-react";
593
+ import * as React3 from "react";
594
+ import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
595
+ function ConfirmMorph({
596
+ className,
597
+ variant,
598
+ size,
599
+ label,
600
+ confirmLabel = "\u786E\u8BA4",
601
+ pendingLabel,
602
+ successLabel = "\u5DF2\u5B8C\u6210",
603
+ undoLabel = "\u64A4\u9500",
604
+ confirmVariant = "destructive",
605
+ confirmTimeout = 3e3,
606
+ resetDelay = 1500,
607
+ onConfirm,
608
+ onUndo,
609
+ onStateChange,
610
+ disabled,
611
+ type = "button",
612
+ ...props
613
+ }) {
614
+ const [state, setState] = React3.useState("idle");
615
+ const timerRef = React3.useRef(null);
616
+ const generationRef = React3.useRef(0);
617
+ const transition = React3.useCallback(
618
+ (next) => {
619
+ setState(next);
620
+ onStateChange?.(next);
621
+ },
622
+ [onStateChange]
623
+ );
624
+ const clearTimer = React3.useCallback(() => {
625
+ if (timerRef.current) {
626
+ clearTimeout(timerRef.current);
627
+ timerRef.current = null;
628
+ }
629
+ }, []);
630
+ React3.useEffect(() => clearTimer, [clearTimer]);
631
+ const handleClick = () => {
632
+ if (disabled) return;
633
+ if (state === "idle") {
634
+ transition("confirming");
635
+ clearTimer();
636
+ timerRef.current = setTimeout(() => {
637
+ generationRef.current += 1;
638
+ transition("idle");
639
+ }, confirmTimeout);
640
+ return;
641
+ }
642
+ if (state === "confirming") {
643
+ clearTimer();
644
+ const generation = ++generationRef.current;
645
+ let result;
646
+ try {
647
+ result = onConfirm?.();
648
+ } catch {
649
+ transition("error");
650
+ return;
651
+ }
652
+ if (result && typeof result.then === "function") {
653
+ transition("pending");
654
+ result.then(
655
+ () => {
656
+ if (generationRef.current !== generation) return;
657
+ transition("success");
658
+ timerRef.current = setTimeout(
659
+ () => transition("idle"),
660
+ resetDelay
661
+ );
662
+ },
663
+ () => {
664
+ if (generationRef.current !== generation) return;
665
+ transition("error");
666
+ }
667
+ );
668
+ } else {
669
+ transition("success");
670
+ timerRef.current = setTimeout(() => transition("idle"), resetDelay);
671
+ }
672
+ return;
673
+ }
674
+ if (state === "error") {
675
+ generationRef.current += 1;
676
+ onUndo?.();
677
+ transition("idle");
678
+ }
679
+ };
680
+ const interactive = state === "idle" || state === "confirming" || state === "error";
681
+ const currentVariant = state === "confirming" ? confirmVariant : variant;
682
+ const segments = [
683
+ { key: "idle", content: label },
684
+ { key: "confirming", content: confirmLabel },
685
+ {
686
+ key: "pending",
687
+ content: /* @__PURE__ */ jsxs2(Fragment, { children: [
688
+ /* @__PURE__ */ jsx4(Spinner, { className: "motion-reduce:animate-none" }),
689
+ pendingLabel ? /* @__PURE__ */ jsx4("span", { children: pendingLabel }) : null
690
+ ] })
691
+ },
692
+ {
693
+ key: "success",
694
+ content: /* @__PURE__ */ jsxs2(Fragment, { children: [
695
+ /* @__PURE__ */ jsx4(CheckIcon, {}),
696
+ /* @__PURE__ */ jsx4("span", { children: successLabel })
697
+ ] })
698
+ },
699
+ {
700
+ key: "error",
701
+ content: /* @__PURE__ */ jsxs2(Fragment, { children: [
702
+ /* @__PURE__ */ jsx4(RotateCcwIcon, {}),
703
+ /* @__PURE__ */ jsx4("span", { children: undoLabel })
704
+ ] })
705
+ }
706
+ ];
707
+ return /* @__PURE__ */ jsx4(
708
+ Button,
709
+ {
710
+ "data-slot": "confirm-morph",
711
+ "data-state": state,
712
+ variant: currentVariant,
713
+ size,
714
+ type,
715
+ disabled,
716
+ "aria-busy": state === "pending" || void 0,
717
+ className: cn(
718
+ "transition-colors motion-reduce:transition-none",
719
+ !interactive && "pointer-events-none",
720
+ className
721
+ ),
722
+ onClick: handleClick,
723
+ ...props,
724
+ children: /* @__PURE__ */ jsx4("span", { className: "grid", children: segments.map((segment) => {
725
+ const active = state === segment.key;
726
+ return /* @__PURE__ */ jsx4(
727
+ "span",
728
+ {
729
+ "data-segment": segment.key,
730
+ "aria-hidden": active ? void 0 : true,
731
+ className: cn(
732
+ "col-start-1 row-start-1 inline-flex items-center justify-center gap-2 whitespace-nowrap transition-opacity duration-150 motion-reduce:transition-none",
733
+ active ? "opacity-100" : "pointer-events-none opacity-0"
734
+ ),
735
+ children: segment.content
736
+ },
737
+ segment.key
738
+ );
739
+ }) })
740
+ }
741
+ );
742
+ }
743
+
744
+ // src/ui/copy-button.tsx
745
+ import * as React4 from "react";
746
+
747
+ // src/ui/icon-morph.tsx
748
+ import {
749
+ Check,
750
+ CircleQuestionMark,
751
+ Copy,
752
+ Menu,
753
+ Minus,
754
+ Pause,
755
+ Play,
756
+ Plus,
757
+ X
758
+ } from "lucide-react";
759
+ import { jsx as jsx5 } from "react/jsx-runtime";
760
+ var MORPH_PAIRS = {
761
+ "menu-close": { a: Menu, b: X },
762
+ "play-pause": { a: Play, b: Pause },
763
+ "copy-check": { a: Copy, b: Check },
764
+ "plus-minus": { a: Plus, b: Minus }
765
+ };
766
+ function IconMorph({ morph, state, className, style }) {
767
+ const pair = MORPH_PAIRS[morph];
768
+ if (!pair) {
769
+ if (process.env.NODE_ENV !== "production") {
770
+ console.warn(
771
+ `[IconMorph] \u672A\u77E5\u9884\u8BBE\u56FE\u6807\u5BF9 "${morph}"\uFF0C\u5DF2\u6E32\u67D3\u5360\u4F4D\u56FE\u6807\u3002\u53EF\u7528\u503C\uFF1A${Object.keys(MORPH_PAIRS).join(" / ")}`
772
+ );
773
+ }
774
+ return /* @__PURE__ */ jsx5(
775
+ "span",
776
+ {
777
+ "data-slot": "icon-morph",
778
+ "data-unknown": morph,
779
+ className: cn("relative inline-flex size-4 shrink-0", className),
780
+ style,
781
+ "aria-hidden": "true",
782
+ children: /* @__PURE__ */ jsx5(CircleQuestionMark, { className: "size-full" })
783
+ }
784
+ );
785
+ }
786
+ const layers = [
787
+ { key: "a", Icon: pair.a },
788
+ { key: "b", Icon: pair.b }
789
+ ];
790
+ return /* @__PURE__ */ jsx5(
791
+ "span",
792
+ {
793
+ "data-slot": "icon-morph",
794
+ "data-state": state,
795
+ className: cn("relative inline-flex size-4 shrink-0", className),
796
+ style,
797
+ "aria-hidden": "true",
798
+ children: layers.map(({ key, Icon }) => {
799
+ const active = state === key;
800
+ return /* @__PURE__ */ jsx5(
801
+ Icon,
802
+ {
803
+ "data-state": active ? "active" : "inactive",
804
+ className: cn(
805
+ "absolute inset-0 size-full transition-all duration-200 ease-out motion-reduce:transition-none",
806
+ active ? "rotate-0 scale-100 opacity-100" : "scale-50 opacity-0",
807
+ !active && (key === "a" ? "-rotate-90" : "rotate-90")
808
+ )
809
+ },
810
+ key
811
+ );
812
+ })
813
+ }
814
+ );
815
+ }
816
+
817
+ // src/ui/copy-button.tsx
818
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
819
+ function CopyButton({
820
+ className,
821
+ variant = "outline",
822
+ size = "sm",
823
+ value,
824
+ label,
825
+ copiedLabel,
826
+ resetDelay = 1500,
827
+ onCopy,
828
+ onError,
829
+ onClick,
830
+ disabled,
831
+ type = "button",
832
+ ...props
833
+ }) {
834
+ const [copied, setCopied] = React4.useState(false);
835
+ const timerRef = React4.useRef(null);
836
+ React4.useEffect(
837
+ () => () => {
838
+ if (timerRef.current) clearTimeout(timerRef.current);
839
+ },
840
+ []
841
+ );
842
+ const handleClick = async (event) => {
843
+ onClick?.(event);
844
+ if (event.defaultPrevented || disabled) return;
845
+ try {
846
+ if (!navigator.clipboard?.writeText) {
847
+ throw new Error("navigator.clipboard \u4E0D\u53EF\u7528\uFF08\u975E\u5B89\u5168\u4E0A\u4E0B\u6587\uFF09");
848
+ }
849
+ await navigator.clipboard.writeText(value);
850
+ if (timerRef.current) clearTimeout(timerRef.current);
851
+ setCopied(true);
852
+ onCopy?.(value);
853
+ timerRef.current = setTimeout(() => setCopied(false), resetDelay);
854
+ } catch (error) {
855
+ onError?.(error);
856
+ }
857
+ };
858
+ return /* @__PURE__ */ jsxs3(
859
+ Button,
860
+ {
861
+ "data-slot": "copy-button",
862
+ "data-state": copied ? "copied" : "idle",
863
+ variant,
864
+ size,
865
+ type,
866
+ disabled,
867
+ className: cn(className),
868
+ onClick: handleClick,
869
+ ...props,
870
+ children: [
871
+ /* @__PURE__ */ jsx6(IconMorph, { morph: "copy-check", state: copied ? "b" : "a" }),
872
+ copied && copiedLabel ? /* @__PURE__ */ jsx6("span", { children: copiedLabel }) : label ? /* @__PURE__ */ jsx6("span", { children: label }) : null
873
+ ]
874
+ }
875
+ );
876
+ }
877
+
878
+ // src/ui/expanding-button-group.tsx
879
+ import * as React5 from "react";
880
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
881
+ function ExpandingButtonGroup({
882
+ options,
883
+ onSelect,
884
+ className,
885
+ style
886
+ }) {
887
+ const [expanded, setExpanded] = React5.useState(null);
888
+ const [confirming, setConfirming] = React5.useState(null);
889
+ const handleClick = (option) => {
890
+ if (option.confirm) {
891
+ if (confirming === option.value) {
892
+ setConfirming(null);
893
+ onSelect?.(option.value);
894
+ } else {
895
+ setConfirming(option.value);
896
+ }
897
+ return;
898
+ }
899
+ onSelect?.(option.value);
900
+ };
901
+ return /* @__PURE__ */ jsx7(
902
+ "div",
903
+ {
904
+ role: "group",
905
+ "data-slot": "expanding-button-group",
906
+ className: cn("inline-flex w-fit items-center gap-1", className),
907
+ style,
908
+ children: options.map((option) => {
909
+ const isConfirming = confirming === option.value;
910
+ const isExpanded = expanded === option.value || isConfirming;
911
+ return /* @__PURE__ */ jsxs4(
912
+ "button",
913
+ {
914
+ type: "button",
915
+ "data-slot": "expanding-button-group-item",
916
+ "data-value": option.value,
917
+ "data-expanded": isExpanded ? "" : void 0,
918
+ "data-confirming": isConfirming ? "" : void 0,
919
+ disabled: option.disabled,
920
+ className: cn(
921
+ "inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-md px-2 text-sm font-medium whitespace-nowrap outline-none transition-colors duration-150 hover:bg-accent hover:text-accent-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4",
922
+ isConfirming && "bg-destructive text-destructive-foreground hover:bg-destructive/90"
923
+ ),
924
+ onMouseEnter: () => setExpanded(option.value),
925
+ onMouseLeave: () => {
926
+ setExpanded(null);
927
+ setConfirming(
928
+ (prev) => prev === option.value ? null : prev
929
+ );
930
+ },
931
+ onFocus: () => setExpanded(option.value),
932
+ onBlur: () => {
933
+ setExpanded(null);
934
+ setConfirming(
935
+ (prev) => prev === option.value ? null : prev
936
+ );
937
+ },
938
+ onClick: () => handleClick(option),
939
+ children: [
940
+ option.icon,
941
+ /* @__PURE__ */ jsx7(
942
+ "span",
943
+ {
944
+ className: "grid transition-[grid-template-columns] duration-200 ease-out motion-reduce:transition-none",
945
+ style: { gridTemplateColumns: isExpanded ? "1fr" : "0fr" },
946
+ children: /* @__PURE__ */ jsx7("span", { className: "overflow-hidden", children: isConfirming ? option.confirmLabel ?? "\u786E\u8BA4" : option.label })
947
+ }
948
+ )
949
+ ]
950
+ },
951
+ option.value
952
+ );
953
+ })
954
+ }
955
+ );
956
+ }
957
+
958
+ // src/ui/floating-button-group.tsx
959
+ import * as React6 from "react";
960
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
961
+ function FloatingButtonGroup({
962
+ options,
963
+ value,
964
+ onSelect,
965
+ className,
966
+ style
967
+ }) {
968
+ const containerRef = React6.useRef(null);
969
+ const buttonRefs = React6.useRef(/* @__PURE__ */ new Map());
970
+ const [hovered, setHovered] = React6.useState(null);
971
+ const [box, setBox] = React6.useState(null);
972
+ const [visible, setVisible] = React6.useState(false);
973
+ const target = value ?? hovered;
974
+ React6.useLayoutEffect(() => {
975
+ if (!target) {
976
+ setVisible(false);
977
+ return;
978
+ }
979
+ const el = buttonRefs.current.get(target);
980
+ if (!el || el.offsetWidth === 0) {
981
+ setVisible(false);
982
+ return;
983
+ }
984
+ setBox({ x: el.offsetLeft, width: el.offsetWidth });
985
+ setVisible(true);
986
+ }, [target]);
987
+ return /* @__PURE__ */ jsxs5(
988
+ "div",
989
+ {
990
+ ref: containerRef,
991
+ role: "group",
992
+ "data-slot": "floating-button-group",
993
+ className: cn(
994
+ "relative inline-flex w-fit items-center gap-1 rounded-full bg-muted p-1",
995
+ className
996
+ ),
997
+ style,
998
+ onMouseLeave: () => setHovered(null),
999
+ children: [
1000
+ /* @__PURE__ */ jsx8(
1001
+ "span",
1002
+ {
1003
+ "data-slot": "floating-button-group-highlight",
1004
+ "aria-hidden": "true",
1005
+ className: cn(
1006
+ "absolute inset-y-1 left-0 rounded-full bg-card shadow-sm transition-[transform,width,opacity] duration-200 ease-out motion-reduce:transition-none",
1007
+ visible ? "opacity-100" : "opacity-0"
1008
+ ),
1009
+ style: {
1010
+ width: box?.width ?? 0,
1011
+ transform: `translateX(${box?.x ?? 0}px)`
1012
+ }
1013
+ }
1014
+ ),
1015
+ options.map((option) => /* @__PURE__ */ jsxs5(
1016
+ "button",
1017
+ {
1018
+ type: "button",
1019
+ ref: (el) => {
1020
+ if (el) buttonRefs.current.set(option.value, el);
1021
+ else buttonRefs.current.delete(option.value);
1022
+ },
1023
+ "data-slot": "floating-button-group-item",
1024
+ "data-value": option.value,
1025
+ "data-active": value === option.value ? "" : void 0,
1026
+ disabled: option.disabled,
1027
+ className: cn(
1028
+ "relative z-10 inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-full px-3 text-sm font-medium whitespace-nowrap outline-none transition-[transform,color] duration-150 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 active:scale-95 disabled:pointer-events-none disabled:opacity-50 motion-reduce:transition-none motion-reduce:active:transform-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4",
1029
+ target === option.value ? "text-foreground" : "text-muted-foreground"
1030
+ ),
1031
+ onMouseEnter: () => setHovered(option.value),
1032
+ onFocus: (event) => {
1033
+ if (event.currentTarget.matches(":focus-visible")) {
1034
+ setHovered(option.value);
1035
+ }
1036
+ },
1037
+ onBlur: () => setHovered(null),
1038
+ onClick: () => onSelect?.(option.value),
1039
+ children: [
1040
+ option.icon,
1041
+ option.label ? /* @__PURE__ */ jsx8("span", { children: option.label }) : null
1042
+ ]
1043
+ },
1044
+ option.value
1045
+ ))
1046
+ ]
1047
+ }
1048
+ );
1049
+ }
1050
+
443
1051
  // src/ui/message.tsx
444
- import { jsx as jsx2 } from "react/jsx-runtime";
1052
+ import { jsx as jsx9 } from "react/jsx-runtime";
445
1053
  function MessageGroup({ className, ...props }) {
446
- return /* @__PURE__ */ jsx2(
1054
+ return /* @__PURE__ */ jsx9(
447
1055
  "div",
448
1056
  {
449
1057
  "data-slot": "message-group",
@@ -457,7 +1065,7 @@ function Message({
457
1065
  align = "start",
458
1066
  ...props
459
1067
  }) {
460
- return /* @__PURE__ */ jsx2(
1068
+ return /* @__PURE__ */ jsx9(
461
1069
  "div",
462
1070
  {
463
1071
  "data-slot": "message",
@@ -471,7 +1079,7 @@ function Message({
471
1079
  );
472
1080
  }
473
1081
  function MessageAvatar({ className, ...props }) {
474
- return /* @__PURE__ */ jsx2(
1082
+ return /* @__PURE__ */ jsx9(
475
1083
  "div",
476
1084
  {
477
1085
  "data-slot": "message-avatar",
@@ -484,7 +1092,7 @@ function MessageAvatar({ className, ...props }) {
484
1092
  );
485
1093
  }
486
1094
  function MessageContent({ className, ...props }) {
487
- return /* @__PURE__ */ jsx2(
1095
+ return /* @__PURE__ */ jsx9(
488
1096
  "div",
489
1097
  {
490
1098
  "data-slot": "message-content",
@@ -497,7 +1105,7 @@ function MessageContent({ className, ...props }) {
497
1105
  );
498
1106
  }
499
1107
  function MessageHeader({ className, ...props }) {
500
- return /* @__PURE__ */ jsx2(
1108
+ return /* @__PURE__ */ jsx9(
501
1109
  "div",
502
1110
  {
503
1111
  "data-slot": "message-header",
@@ -510,7 +1118,7 @@ function MessageHeader({ className, ...props }) {
510
1118
  );
511
1119
  }
512
1120
  function MessageFooter({ className, ...props }) {
513
- return /* @__PURE__ */ jsx2(
1121
+ return /* @__PURE__ */ jsx9(
514
1122
  "div",
515
1123
  {
516
1124
  "data-slot": "message-footer",
@@ -522,11 +1130,73 @@ function MessageFooter({ className, ...props }) {
522
1130
  }
523
1131
  );
524
1132
  }
1133
+
1134
+ // src/ui/split-button.tsx
1135
+ import { ChevronDownIcon } from "lucide-react";
1136
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
1137
+ function SplitButton({
1138
+ className,
1139
+ style,
1140
+ variant,
1141
+ size,
1142
+ disabled,
1143
+ children,
1144
+ options,
1145
+ onSelect,
1146
+ triggerLabel = "\u66F4\u591A\u9009\u9879",
1147
+ type = "button",
1148
+ ...props
1149
+ }) {
1150
+ return /* @__PURE__ */ jsxs6(ButtonGroup, { "data-slot": "split-button", className: cn(className), style, children: [
1151
+ /* @__PURE__ */ jsx10(
1152
+ Button,
1153
+ {
1154
+ variant,
1155
+ size,
1156
+ disabled,
1157
+ type,
1158
+ ...props,
1159
+ children
1160
+ }
1161
+ ),
1162
+ /* @__PURE__ */ jsxs6(DropdownMenu, { children: [
1163
+ /* @__PURE__ */ jsx10(
1164
+ DropdownMenuTrigger,
1165
+ {
1166
+ render: /* @__PURE__ */ jsx10(
1167
+ Button,
1168
+ {
1169
+ variant,
1170
+ size,
1171
+ disabled,
1172
+ "aria-label": triggerLabel,
1173
+ className: "px-2",
1174
+ children: /* @__PURE__ */ jsx10(ChevronDownIcon, {})
1175
+ }
1176
+ )
1177
+ }
1178
+ ),
1179
+ /* @__PURE__ */ jsx10(DropdownMenuContent, { align: "end", children: options.map((option) => /* @__PURE__ */ jsxs6(
1180
+ DropdownMenuItem,
1181
+ {
1182
+ disabled: option.disabled,
1183
+ onClick: () => onSelect?.(option.value),
1184
+ children: [
1185
+ option.icon,
1186
+ option.label
1187
+ ]
1188
+ },
1189
+ option.value
1190
+ )) })
1191
+ ] })
1192
+ ] });
1193
+ }
525
1194
  export {
526
1195
  Accordion,
527
1196
  AccordionContent,
528
1197
  AccordionItem,
529
1198
  AccordionTrigger,
1199
+ ActionButton,
530
1200
  Alert,
531
1201
  AlertDescription,
532
1202
  AlertDialog,
@@ -594,11 +1264,13 @@ export {
594
1264
  CommandInput,
595
1265
  CommandItem,
596
1266
  CommandList,
1267
+ ConfirmMorph,
597
1268
  ContextMenu,
598
1269
  ContextMenuContent,
599
1270
  ContextMenuItem,
600
1271
  ContextMenuSeparator,
601
1272
  ContextMenuTrigger,
1273
+ CopyButton,
602
1274
  Dialog,
603
1275
  DialogClose,
604
1276
  DialogContent,
@@ -632,13 +1304,16 @@ export {
632
1304
  EmptyDescription,
633
1305
  EmptyHeader,
634
1306
  EmptyTitle,
1307
+ ExpandingButtonGroup,
635
1308
  Field,
636
1309
  FieldDescription,
637
1310
  FieldError,
638
1311
  FieldLabel,
1312
+ FloatingButtonGroup,
639
1313
  HoverCard,
640
1314
  HoverCardContent,
641
1315
  HoverCardTrigger,
1316
+ IconMorph,
642
1317
  Input,
643
1318
  InputGroup,
644
1319
  InputGroupAddon,
@@ -655,6 +1330,7 @@ export {
655
1330
  ItemTitle,
656
1331
  Kbd,
657
1332
  Label,
1333
+ MORPH_PAIRS,
658
1334
  Menubar,
659
1335
  MenubarContent,
660
1336
  MenubarItem,
@@ -745,6 +1421,7 @@ export {
745
1421
  SliderThumb,
746
1422
  SliderTrack,
747
1423
  Spinner,
1424
+ SplitButton,
748
1425
  Switch,
749
1426
  Table,
750
1427
  TableBody,