@proteos/ui 0.44.0 → 0.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.
Files changed (38) hide show
  1. package/dist/index.cjs +605 -60
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +174 -7
  4. package/dist/index.d.ts +174 -7
  5. package/dist/index.js +601 -64
  6. package/dist/index.js.map +1 -1
  7. package/package.json +4 -4
  8. package/src/components/attribute-type-icon/attribute-type-icon.tsx +5 -0
  9. package/src/components/avatar/avatar.stories.tsx +37 -1
  10. package/src/components/avatar/avatar.test.tsx +42 -0
  11. package/src/components/avatar/avatar.tsx +39 -4
  12. package/src/components/avatar-group/avatar-group.test.tsx +19 -0
  13. package/src/components/avatar-group/avatar-group.tsx +18 -3
  14. package/src/components/badge/badge.stories.tsx +27 -1
  15. package/src/components/badge/badge.test.tsx +26 -0
  16. package/src/components/badge/badge.tsx +48 -4
  17. package/src/components/badge-multi-select/badge-multi-select.stories.tsx +24 -0
  18. package/src/components/badge-multi-select/badge-multi-select.tsx +25 -4
  19. package/src/components/color-input/color-input.stories.tsx +72 -0
  20. package/src/components/color-input/color-input.test.tsx +95 -0
  21. package/src/components/color-input/color-input.tsx +208 -0
  22. package/src/components/color-input/index.ts +2 -0
  23. package/src/components/draft-message-composer/draft-message-composer.stories.tsx +1 -1
  24. package/src/components/icon-picker/icon-picker.stories.tsx +77 -0
  25. package/src/components/icon-picker/icon-picker.test.tsx +88 -0
  26. package/src/components/icon-picker/icon-picker.tsx +336 -0
  27. package/src/components/icon-picker/index.ts +2 -0
  28. package/src/components/record-filter/chip-value.tsx +10 -3
  29. package/src/components/record-filter/filter-value-input.tsx +12 -2
  30. package/src/components/record-filter/operators.ts +6 -6
  31. package/src/components/record-filter/record-filter.stories.tsx +1 -1
  32. package/src/components/record-filter/types.ts +4 -0
  33. package/src/components/table/table.stories.tsx +12 -13
  34. package/src/components/toast/index.ts +9 -0
  35. package/src/components/toast/toast.stories.tsx +58 -0
  36. package/src/components/toast/toast.test.tsx +34 -0
  37. package/src/components/toast/toast.tsx +121 -0
  38. package/src/index.ts +3 -0
package/dist/index.cjs CHANGED
@@ -356,6 +356,7 @@ var AttributeType = {
356
356
  Object: "object",
357
357
  Relation: "relation",
358
358
  User: "user",
359
+ Principal: "principal",
359
360
  Currency: "currency",
360
361
  KnowledgeText: "knowledge-text",
361
362
  File: "file"
@@ -372,6 +373,7 @@ var ATTRIBUTE_TYPE_ICONS = {
372
373
  [AttributeType.Object]: lucideReact.Braces,
373
374
  [AttributeType.Relation]: lucideReact.Link2,
374
375
  [AttributeType.User]: lucideReact.User,
376
+ [AttributeType.Principal]: lucideReact.UsersRound,
375
377
  [AttributeType.Currency]: lucideReact.Banknote,
376
378
  [AttributeType.KnowledgeText]: lucideReact.BookText,
377
379
  [AttributeType.File]: lucideReact.Paperclip
@@ -398,7 +400,7 @@ function AttributeTypeIcon({
398
400
  }
399
401
  var avatarStyles = tailwindVariants.tv({
400
402
  base: [
401
- "inline-grid place-items-center shrink-0 select-none",
403
+ "inline-grid place-items-center shrink-0 select-none overflow-hidden",
402
404
  "rounded-full border",
403
405
  "font-mono font-medium tracking-[-0.005em]"
404
406
  ],
@@ -406,7 +408,8 @@ var avatarStyles = tailwindVariants.tv({
406
408
  size: {
407
409
  sm: "size-[22px] text-[10px]",
408
410
  md: "size-7 text-[12px]",
409
- lg: "size-9 text-[13px]"
411
+ lg: "size-9 text-[13px]",
412
+ xl: "size-[120px] text-[34px]"
410
413
  },
411
414
  tone: {
412
415
  paper: "bg-surface-3 text-ink border-rule",
@@ -418,8 +421,27 @@ var avatarStyles = tailwindVariants.tv({
418
421
  },
419
422
  defaultVariants: { size: "md", tone: "paper" }
420
423
  });
421
- function Avatar({ size, tone, initials, className, children, ...props }) {
422
- return /* @__PURE__ */ jsxRuntime.jsx("span", { ...props, className: chunkMWAXK66H_cjs.cn(avatarStyles({ size, tone }), className), children: children ?? initials });
424
+ function Avatar({
425
+ size,
426
+ tone,
427
+ initials,
428
+ src,
429
+ alt = "",
430
+ className,
431
+ children,
432
+ ...props
433
+ }) {
434
+ const [failedSrc, setFailedSrc] = react.useState(null);
435
+ const showsImage = Boolean(src) && failedSrc !== src;
436
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { ...props, className: chunkMWAXK66H_cjs.cn(avatarStyles({ size, tone }), className), children: showsImage ? /* @__PURE__ */ jsxRuntime.jsx(
437
+ "img",
438
+ {
439
+ src: src ?? void 0,
440
+ alt,
441
+ className: "size-full object-cover",
442
+ onError: () => setFailedSrc(src ?? null)
443
+ }
444
+ ) : children ?? initials });
423
445
  }
424
446
  var avatarGroupStyles = tailwindVariants.tv({
425
447
  base: "flex items-center",
@@ -432,53 +454,29 @@ var avatarGroupStyles = tailwindVariants.tv({
432
454
  },
433
455
  defaultVariants: { size: "md" }
434
456
  });
435
- function AvatarGroup({ avatars, max = 3, size = "md", className, ...props }) {
457
+ function AvatarGroup({
458
+ avatars,
459
+ max = 3,
460
+ size = "md",
461
+ className,
462
+ ...props
463
+ }) {
436
464
  const shown = avatars.slice(0, max);
437
465
  const overflow = avatars.length - shown.length;
438
466
  return /* @__PURE__ */ jsxRuntime.jsxs("span", { ...props, className: chunkMWAXK66H_cjs.cn(avatarGroupStyles({ size }), className), children: [
439
- shown.map((avatar, index) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full ring-[1.5px] ring-surface", children: /* @__PURE__ */ jsxRuntime.jsx(Avatar, { size, tone: avatar.tone ?? "paper", initials: avatar.initials }) }, avatar.key ?? index)),
467
+ shown.map((avatar, index) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full ring-[1.5px] ring-surface", children: /* @__PURE__ */ jsxRuntime.jsx(
468
+ Avatar,
469
+ {
470
+ size,
471
+ tone: avatar.tone ?? "paper",
472
+ initials: avatar.initials,
473
+ src: avatar.src ?? null,
474
+ alt: avatar.alt ?? ""
475
+ }
476
+ ) }, avatar.key ?? index)),
440
477
  overflow > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full ring-[1.5px] ring-surface", children: /* @__PURE__ */ jsxRuntime.jsx(Avatar, { size, tone: "paper", initials: `+${overflow}` }) })
441
478
  ] });
442
479
  }
443
- var badgeStyles = tailwindVariants.tv({
444
- base: [
445
- "inline-flex items-center gap-1.5",
446
- "font-mono text-[11.5px] tracking-[-0.005em]",
447
- "px-2.5 py-0.5 rounded-pill border",
448
- // Leading dot
449
- 'before:content-[""] before:block before:size-1.5 before:rounded-full'
450
- ],
451
- variants: {
452
- variant: {
453
- default: "border-rule bg-surface text-ink-3 before:bg-ink-4",
454
- neutral: "border-rule bg-surface-3 text-ink-2 before:bg-ink-3",
455
- active: [
456
- "text-accent-ink before:bg-accent",
457
- "border-[color-mix(in_oklab,var(--color-accent)_35%,var(--color-rule))]",
458
- "bg-[color-mix(in_oklab,var(--color-accent)_14%,var(--color-surface))]"
459
- ],
460
- success: [
461
- "text-success before:bg-success",
462
- "border-[color-mix(in_oklab,var(--color-success)_35%,var(--color-rule))]",
463
- "bg-[color-mix(in_oklab,var(--color-success)_12%,var(--color-surface))]"
464
- ],
465
- warning: [
466
- "text-warning before:bg-warning",
467
- "border-[color-mix(in_oklab,var(--color-warning)_35%,var(--color-rule))]",
468
- "bg-[color-mix(in_oklab,var(--color-warning)_14%,var(--color-surface))]"
469
- ],
470
- danger: [
471
- "text-danger before:bg-danger",
472
- "border-[color-mix(in_oklab,var(--color-danger)_40%,var(--color-rule))]",
473
- "bg-[color-mix(in_oklab,var(--color-danger)_12%,var(--color-surface))]"
474
- ]
475
- }
476
- },
477
- defaultVariants: { variant: "default" }
478
- });
479
- function Badge({ variant, className, ...props }) {
480
- return /* @__PURE__ */ jsxRuntime.jsx("span", { ...props, className: chunkMWAXK66H_cjs.cn(badgeStyles({ variant }), className) });
481
- }
482
480
  var fieldControlStyles = tailwindVariants.tv({
483
481
  slots: {
484
482
  box: [
@@ -571,6 +569,318 @@ function FieldCaret() {
571
569
  }
572
570
  ) });
573
571
  }
572
+ var ICON_NAMES = Object.keys(lucideReact.icons);
573
+ var BY_NORMALIZED = new Map(
574
+ ICON_NAMES.map((name) => [normalize(name), name])
575
+ );
576
+ function normalize(name) {
577
+ return name.toLowerCase().replace(/[^a-z0-9]/g, "");
578
+ }
579
+ function resolveLucideIcon(name) {
580
+ const canonical = resolveLucideIconName(name);
581
+ return canonical ? lucideReact.icons[canonical] : null;
582
+ }
583
+ function resolveLucideIconName(name) {
584
+ if (!name) return null;
585
+ return BY_NORMALIZED.get(normalize(name)) ?? null;
586
+ }
587
+ var GRID_PAGE = 120;
588
+ var COMPACT_SIZE = {
589
+ sm: "size-8",
590
+ md: "size-[42px]"
591
+ };
592
+ function IconPicker({
593
+ label,
594
+ description,
595
+ errorMessage,
596
+ placeholder = "Choose an icon\u2026",
597
+ value,
598
+ onChange,
599
+ isDisabled = false,
600
+ isInvalid = false,
601
+ allowClear = true,
602
+ size = "md",
603
+ appearance,
604
+ compact = false,
605
+ "aria-label": ariaLabel,
606
+ className
607
+ }) {
608
+ const [isOpen, setIsOpen] = react.useState(false);
609
+ const [query, setQuery] = react.useState("");
610
+ const [limit, setLimit] = react.useState(GRID_PAGE);
611
+ const triggerRef = react.useRef(null);
612
+ const styles3 = fieldControlStyles({ size, invalid: isInvalid, appearance });
613
+ const selectedName = resolveLucideIconName(value);
614
+ const SelectedIcon = selectedName ? lucideReact.icons[selectedName] : null;
615
+ const matches2 = react.useMemo(() => {
616
+ const q = normalize(query);
617
+ return q ? ICON_NAMES.filter((name) => normalize(name).includes(q)) : ICON_NAMES;
618
+ }, [query]);
619
+ const visible = matches2.slice(0, limit);
620
+ const hiddenCount = matches2.length - visible.length;
621
+ const open = (next) => {
622
+ setIsOpen(next);
623
+ if (!next) {
624
+ setQuery("");
625
+ setLimit(GRID_PAGE);
626
+ }
627
+ };
628
+ const search = (next) => {
629
+ setQuery(next);
630
+ setLimit(GRID_PAGE);
631
+ };
632
+ const onGridScroll = (element) => {
633
+ if (hiddenCount === 0) return;
634
+ const remaining = element.scrollHeight - element.scrollTop - element.clientHeight;
635
+ if (remaining < 120) setLimit((current) => current + GRID_PAGE);
636
+ };
637
+ const pick = (name) => {
638
+ onChange(name);
639
+ open(false);
640
+ triggerRef.current?.focus();
641
+ };
642
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chunkMWAXK66H_cjs.cn("grid grid-cols-1 gap-1.5", className), children: [
643
+ label ? /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.Label, { className: fieldLabelStyles, children: label }) : null,
644
+ compact ? /* @__PURE__ */ jsxRuntime.jsx(
645
+ "button",
646
+ {
647
+ ref: triggerRef,
648
+ type: "button",
649
+ "aria-haspopup": "dialog",
650
+ "aria-expanded": isOpen,
651
+ "aria-label": ariaLabel ?? (typeof label === "string" ? label : "Icon"),
652
+ title: selectedName ?? placeholder,
653
+ disabled: isDisabled,
654
+ onClick: () => open(!isOpen),
655
+ "data-invalid": isInvalid || void 0,
656
+ className: chunkMWAXK66H_cjs.cn(
657
+ styles3.box(),
658
+ "flex shrink-0 items-center justify-center cursor-pointer",
659
+ appearance === "cell" ? "size-[29px]" : COMPACT_SIZE[size],
660
+ "disabled:cursor-not-allowed disabled:opacity-45"
661
+ ),
662
+ children: SelectedIcon ? /* @__PURE__ */ jsxRuntime.jsx(SelectedIcon, { "aria-hidden": true, className: "size-4 text-ink-2" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Shapes, { "aria-hidden": true, className: "size-4 text-ink-4" })
663
+ }
664
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
665
+ "button",
666
+ {
667
+ ref: triggerRef,
668
+ type: "button",
669
+ "aria-haspopup": "dialog",
670
+ "aria-expanded": isOpen,
671
+ "aria-label": ariaLabel ?? (typeof label === "string" ? label : "Icon"),
672
+ disabled: isDisabled,
673
+ onClick: () => open(!isOpen),
674
+ "data-invalid": isInvalid || void 0,
675
+ className: chunkMWAXK66H_cjs.cn(
676
+ // box carries no padding by contract — content is what gives a
677
+ // single-element control its interior (padding, font, text-size).
678
+ styles3.box(),
679
+ styles3.content(),
680
+ "flex w-full items-center gap-2 text-left cursor-pointer",
681
+ "disabled:cursor-not-allowed disabled:opacity-45"
682
+ ),
683
+ children: [
684
+ SelectedIcon ? /* @__PURE__ */ jsxRuntime.jsx(SelectedIcon, { "aria-hidden": true, className: "size-4 shrink-0 text-ink-2" }) : null,
685
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: chunkMWAXK66H_cjs.cn("min-w-0 flex-1 truncate", selectedName ? "text-ink" : "text-ink-4"), children: selectedName ?? placeholder }),
686
+ /* @__PURE__ */ jsxRuntime.jsx(FieldCaret, {})
687
+ ]
688
+ }
689
+ ),
690
+ description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: fieldHintStyles, children: description }) : null,
691
+ errorMessage && appearance !== "cell" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: fieldErrorStyles, children: errorMessage }) : null,
692
+ /* @__PURE__ */ jsxRuntime.jsxs(
693
+ reactAriaComponents.Popover,
694
+ {
695
+ isOpen,
696
+ onOpenChange: open,
697
+ triggerRef,
698
+ offset: 6,
699
+ placement: "bottom start",
700
+ className: chunkMWAXK66H_cjs.cn(
701
+ "w-[316px] rounded-md border border-rule bg-surface p-2",
702
+ "shadow-[var(--shadow-raised)]",
703
+ "entering:animate-in entering:fade-in-0 entering:slide-in-from-top-1",
704
+ "exiting:animate-out exiting:fade-out-0 exiting:slide-out-to-top-1"
705
+ ),
706
+ children: [
707
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 pb-2", children: [
708
+ /* @__PURE__ */ jsxRuntime.jsxs(
709
+ "div",
710
+ {
711
+ className: chunkMWAXK66H_cjs.cn(
712
+ fieldControlStyles({ size: "sm" }).box(),
713
+ "flex min-w-0 flex-1 items-center gap-1.5 px-2.5 py-1.5",
714
+ "focus-within:border-accent focus-within:shadow-[var(--shadow-focus)]"
715
+ ),
716
+ children: [
717
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { "aria-hidden": true, className: "size-3.5 shrink-0 text-ink-4" }),
718
+ /* @__PURE__ */ jsxRuntime.jsx(
719
+ reactAriaComponents.Input,
720
+ {
721
+ autoFocus: true,
722
+ "aria-label": "Search icons",
723
+ value: query,
724
+ onChange: (event) => search(event.target.value),
725
+ placeholder: "Search icons\u2026",
726
+ className: chunkMWAXK66H_cjs.cn(
727
+ "min-w-0 flex-1 bg-transparent font-sans outline-none",
728
+ "text-ink placeholder:text-ink-4",
729
+ fieldTextStyles.sm
730
+ )
731
+ }
732
+ )
733
+ ]
734
+ }
735
+ ),
736
+ allowClear && selectedName ? /* @__PURE__ */ jsxRuntime.jsxs(
737
+ "button",
738
+ {
739
+ type: "button",
740
+ onClick: () => pick(null),
741
+ className: chunkMWAXK66H_cjs.cn(
742
+ "flex shrink-0 items-center gap-1 rounded-[6px] px-2 py-1.5 cursor-pointer",
743
+ "font-sans text-[12px] text-ink-3 transition-colors",
744
+ "hover:bg-surface-2 hover:text-ink",
745
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-accent)]"
746
+ ),
747
+ children: [
748
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { "aria-hidden": true, className: "size-3" }),
749
+ "Clear"
750
+ ]
751
+ }
752
+ ) : null
753
+ ] }),
754
+ /* @__PURE__ */ jsxRuntime.jsx(
755
+ "div",
756
+ {
757
+ className: "max-h-64 overflow-y-auto",
758
+ onScroll: (event) => onGridScroll(event.currentTarget),
759
+ children: /* @__PURE__ */ jsxRuntime.jsx(
760
+ reactAriaComponents.ListBox,
761
+ {
762
+ "aria-label": "Icons",
763
+ layout: "grid",
764
+ selectionMode: "single",
765
+ selectedKeys: selectedName ? /* @__PURE__ */ new Set([selectedName]) : /* @__PURE__ */ new Set(),
766
+ onSelectionChange: (keys) => {
767
+ if (keys === "all") return;
768
+ const next = [...keys][0];
769
+ if (next !== void 0) pick(next);
770
+ },
771
+ className: "grid grid-cols-8 gap-0.5 outline-none",
772
+ renderEmptyState: () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: chunkMWAXK66H_cjs.cn("col-span-8 px-2 py-3 text-center", fieldHintStyles), children: "No icons match" }),
773
+ children: visible.map((name) => {
774
+ const Icon = lucideReact.icons[name];
775
+ return /* @__PURE__ */ jsxRuntime.jsx(
776
+ reactAriaComponents.ListBoxItem,
777
+ {
778
+ id: name,
779
+ textValue: name,
780
+ "aria-label": name,
781
+ className: chunkMWAXK66H_cjs.cn(
782
+ "flex size-[34px] items-center justify-center rounded-[6px]",
783
+ "cursor-pointer outline-none",
784
+ "data-[hovered]:bg-surface-2 data-[focused]:bg-surface-2",
785
+ "data-[focus-visible]:ring-2 data-[focus-visible]:ring-[var(--color-accent)]",
786
+ "data-[selected]:bg-[color-mix(in_oklab,var(--color-accent)_12%,var(--color-surface))]",
787
+ "data-[selected]:text-accent text-ink-2"
788
+ ),
789
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { "aria-hidden": true, className: "size-4" })
790
+ },
791
+ name
792
+ );
793
+ })
794
+ }
795
+ )
796
+ }
797
+ ),
798
+ hiddenCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: chunkMWAXK66H_cjs.cn("px-1 pt-2", fieldHintStyles), children: [
799
+ hiddenCount.toLocaleString(),
800
+ " more \u2014 keep scrolling, or search."
801
+ ] }) : null
802
+ ]
803
+ }
804
+ )
805
+ ] });
806
+ }
807
+ var badgeStyles = tailwindVariants.tv({
808
+ base: [
809
+ "inline-flex items-center gap-1.5",
810
+ "font-mono text-[11.5px] tracking-[-0.005em]",
811
+ "px-2.5 py-0.5 rounded-pill border",
812
+ // Leading dot
813
+ 'before:content-[""] before:block before:size-1.5 before:rounded-full'
814
+ ],
815
+ variants: {
816
+ variant: {
817
+ default: "border-rule bg-surface text-ink-3 before:bg-ink-4",
818
+ neutral: "border-rule bg-surface-3 text-ink-2 before:bg-ink-3",
819
+ active: [
820
+ "text-accent-ink before:bg-accent",
821
+ "border-[color-mix(in_oklab,var(--color-accent)_35%,var(--color-rule))]",
822
+ "bg-[color-mix(in_oklab,var(--color-accent)_14%,var(--color-surface))]"
823
+ ],
824
+ success: [
825
+ "text-success before:bg-success",
826
+ "border-[color-mix(in_oklab,var(--color-success)_35%,var(--color-rule))]",
827
+ "bg-[color-mix(in_oklab,var(--color-success)_12%,var(--color-surface))]"
828
+ ],
829
+ warning: [
830
+ "text-warning before:bg-warning",
831
+ "border-[color-mix(in_oklab,var(--color-warning)_35%,var(--color-rule))]",
832
+ "bg-[color-mix(in_oklab,var(--color-warning)_14%,var(--color-surface))]"
833
+ ],
834
+ danger: [
835
+ "text-danger before:bg-danger",
836
+ "border-[color-mix(in_oklab,var(--color-danger)_40%,var(--color-rule))]",
837
+ "bg-[color-mix(in_oklab,var(--color-danger)_12%,var(--color-surface))]"
838
+ ],
839
+ // Driven by the `--badge-color` custom property (set from the `color`
840
+ // prop). Chrome only — text stays ink so contrast never depends on the
841
+ // picked color.
842
+ custom: [
843
+ "text-ink-2 before:bg-[var(--badge-color)]",
844
+ "border-[color-mix(in_oklab,var(--badge-color)_35%,var(--color-rule))]",
845
+ "bg-[color-mix(in_oklab,var(--badge-color)_12%,var(--color-surface))]"
846
+ ]
847
+ }
848
+ },
849
+ defaultVariants: { variant: "default" }
850
+ });
851
+ var ICON_TONE = {
852
+ default: "text-ink-4",
853
+ neutral: "text-ink-3",
854
+ active: "text-accent",
855
+ success: "text-success",
856
+ warning: "text-warning",
857
+ danger: "text-danger",
858
+ custom: "text-[var(--badge-color)]"
859
+ };
860
+ function Badge({ variant, color, icon, className, style, children, ...props }) {
861
+ const Icon = icon ? resolveLucideIcon(icon) : null;
862
+ const register2 = color ? "custom" : variant ?? "default";
863
+ const mergedStyle = color ? { ...style, "--badge-color": color } : style;
864
+ return /* @__PURE__ */ jsxRuntime.jsxs(
865
+ "span",
866
+ {
867
+ ...props,
868
+ style: mergedStyle,
869
+ className: chunkMWAXK66H_cjs.cn(badgeStyles({ variant: register2 }), Icon ? "before:hidden" : null, className),
870
+ children: [
871
+ Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { "aria-hidden": true, className: chunkMWAXK66H_cjs.cn("size-3 shrink-0", ICON_TONE[register2]) }) : null,
872
+ children
873
+ ]
874
+ }
875
+ );
876
+ }
877
+ function badgeChrome(option) {
878
+ return {
879
+ ...option.variant ? { variant: option.variant } : {},
880
+ ...option.color ? { color: option.color } : {},
881
+ ...option.icon ? { icon: option.icon } : {}
882
+ };
883
+ }
574
884
  function BadgeMultiSelect({
575
885
  label,
576
886
  description,
@@ -643,7 +953,7 @@ function BadgeMultiSelect({
643
953
  ) : selectedOptions.map((o) => /* @__PURE__ */ jsxRuntime.jsx(
644
954
  RemovableBadge,
645
955
  {
646
- variant: o.variant,
956
+ ...badgeChrome(o),
647
957
  onRemove: () => remove(o.id),
648
958
  isDisabled,
649
959
  children: o.label
@@ -701,7 +1011,7 @@ function BadgeMultiSelect({
701
1011
  "data-[selected]:bg-surface-3",
702
1012
  "data-[disabled]:opacity-45 data-[disabled]:cursor-not-allowed"
703
1013
  ),
704
- children: /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: option.variant, children: option.label })
1014
+ children: /* @__PURE__ */ jsxRuntime.jsx(Badge, { ...badgeChrome(option), children: option.label })
705
1015
  },
706
1016
  option.id
707
1017
  ))
@@ -714,10 +1024,12 @@ function BadgeMultiSelect({
714
1024
  function RemovableBadge({
715
1025
  children,
716
1026
  variant,
1027
+ color,
1028
+ icon,
717
1029
  onRemove,
718
1030
  isDisabled
719
1031
  }) {
720
- return /* @__PURE__ */ jsxRuntime.jsxs(Badge, { variant, children: [
1032
+ return /* @__PURE__ */ jsxRuntime.jsxs(Badge, { ...badgeChrome({ variant, color, icon }), children: [
721
1033
  /* @__PURE__ */ jsxRuntime.jsx("span", { children }),
722
1034
  /* @__PURE__ */ jsxRuntime.jsx(
723
1035
  "button",
@@ -1027,6 +1339,146 @@ function CheckboxGroup({
1027
1339
  }
1028
1340
  );
1029
1341
  }
1342
+ var HEX6 = /^#?([0-9a-f]{6})$/i;
1343
+ var HEX3 = /^#?([0-9a-f]{3})$/i;
1344
+ function normalizeHexColor(text) {
1345
+ const raw = (text ?? "").trim();
1346
+ if (raw === "") return null;
1347
+ const six = HEX6.exec(raw);
1348
+ if (six?.[1]) return `#${six[1].toLowerCase()}`;
1349
+ const three = HEX3.exec(raw);
1350
+ if (three?.[1]) {
1351
+ const [r, g, b] = three[1].toLowerCase();
1352
+ return `#${r}${r}${g}${g}${b}${b}`;
1353
+ }
1354
+ return null;
1355
+ }
1356
+ function ColorInput({
1357
+ label,
1358
+ description,
1359
+ errorMessage,
1360
+ placeholder = "#5b8def",
1361
+ value,
1362
+ onChange,
1363
+ isDisabled = false,
1364
+ isInvalid = false,
1365
+ size = "md",
1366
+ appearance,
1367
+ "aria-label": ariaLabel,
1368
+ className
1369
+ }) {
1370
+ const committed = normalizeHexColor(value);
1371
+ const [draft, setDraft] = react.useState(committed ?? "");
1372
+ const nativeRef = react.useRef(null);
1373
+ react.useEffect(() => {
1374
+ setDraft((current) => normalizeHexColor(current) === committed ? current : committed ?? "");
1375
+ }, [committed]);
1376
+ const isDraftInvalid = draft.trim() !== "" && normalizeHexColor(draft) === null;
1377
+ const invalid = isInvalid || isDraftInvalid;
1378
+ const styles3 = fieldControlStyles({ size, invalid, appearance });
1379
+ const focusWithin = appearance === "cell" ? "focus-within:border-accent focus-within:shadow-none" : "focus-within:border-accent focus-within:shadow-[var(--shadow-focus)]";
1380
+ const edit = (next) => {
1381
+ setDraft(next);
1382
+ if (next.trim() === "") {
1383
+ if (committed !== null) onChange(null);
1384
+ return;
1385
+ }
1386
+ if (!HEX6.test(next.trim())) return;
1387
+ const hex = normalizeHexColor(next);
1388
+ if (hex !== null && hex !== committed) onChange(hex);
1389
+ };
1390
+ const settle = () => {
1391
+ const hex = normalizeHexColor(draft);
1392
+ if (hex === null) return;
1393
+ setDraft(hex);
1394
+ if (hex !== committed) onChange(hex);
1395
+ };
1396
+ const pick = (next) => {
1397
+ const hex = normalizeHexColor(next);
1398
+ if (hex === null) return;
1399
+ setDraft(hex);
1400
+ if (hex !== committed) onChange(hex);
1401
+ };
1402
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1403
+ reactAriaComponents.TextField,
1404
+ {
1405
+ value: draft,
1406
+ onChange: edit,
1407
+ isDisabled,
1408
+ isInvalid: invalid,
1409
+ onBlur: settle,
1410
+ ...ariaLabel ? { "aria-label": ariaLabel } : {},
1411
+ className: chunkMWAXK66H_cjs.cn("grid grid-cols-1 gap-1.5", className),
1412
+ children: [
1413
+ label ? /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.Label, { className: fieldLabelStyles, children: label }) : null,
1414
+ /* @__PURE__ */ jsxRuntime.jsxs(
1415
+ "div",
1416
+ {
1417
+ "data-disabled": isDisabled || void 0,
1418
+ className: chunkMWAXK66H_cjs.cn(
1419
+ styles3.box(),
1420
+ "flex items-center gap-2",
1421
+ size === "sm" ? "pl-2" : "pl-2.5",
1422
+ focusWithin
1423
+ ),
1424
+ children: [
1425
+ /* @__PURE__ */ jsxRuntime.jsxs(
1426
+ "button",
1427
+ {
1428
+ type: "button",
1429
+ "aria-label": "Pick color",
1430
+ disabled: isDisabled,
1431
+ onClick: () => nativeRef.current?.click(),
1432
+ className: chunkMWAXK66H_cjs.cn(
1433
+ "relative flex shrink-0 items-center justify-center rounded-sm border border-rule",
1434
+ "cursor-pointer outline-none",
1435
+ "focus-visible:ring-2 focus-visible:ring-[var(--color-accent)]",
1436
+ "disabled:cursor-not-allowed",
1437
+ size === "sm" ? "size-4" : "size-5",
1438
+ committed ? null : "text-ink-4"
1439
+ ),
1440
+ style: committed ? { backgroundColor: committed } : void 0,
1441
+ children: [
1442
+ committed ? null : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pipette, { "aria-hidden": true, className: "size-2.5" }),
1443
+ /* @__PURE__ */ jsxRuntime.jsx(
1444
+ "input",
1445
+ {
1446
+ ref: nativeRef,
1447
+ type: "color",
1448
+ tabIndex: -1,
1449
+ "aria-hidden": true,
1450
+ disabled: isDisabled,
1451
+ value: committed ?? "#000000",
1452
+ onChange: (event) => pick(event.target.value),
1453
+ className: "pointer-events-none absolute inset-0 size-full opacity-0"
1454
+ }
1455
+ )
1456
+ ]
1457
+ }
1458
+ ),
1459
+ /* @__PURE__ */ jsxRuntime.jsx(
1460
+ reactAriaComponents.Input,
1461
+ {
1462
+ placeholder,
1463
+ spellCheck: false,
1464
+ autoComplete: "off",
1465
+ className: chunkMWAXK66H_cjs.cn(
1466
+ styles3.content(),
1467
+ "min-w-0 flex-1 bg-transparent font-mono outline-none",
1468
+ "pl-0",
1469
+ fieldTextStyles[size]
1470
+ )
1471
+ }
1472
+ )
1473
+ ]
1474
+ }
1475
+ ),
1476
+ description ? /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.Text, { slot: "description", className: fieldHintStyles, children: description }) : null,
1477
+ errorMessage && appearance !== "cell" ? /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.FieldError, { className: fieldErrorStyles, children: errorMessage }) : null
1478
+ ]
1479
+ }
1480
+ );
1481
+ }
1030
1482
  function Combobox({
1031
1483
  label,
1032
1484
  description,
@@ -4877,25 +5329,25 @@ var OPERATORS = [
4877
5329
  id: "eq",
4878
5330
  label: "is",
4879
5331
  arity: 1,
4880
- types: ["string", "number", "integer", "currency", "datetime", "enum", "boolean", "relation", "user"]
5332
+ types: ["string", "number", "integer", "currency", "datetime", "enum", "boolean", "relation", "user", "principal"]
4881
5333
  },
4882
5334
  {
4883
5335
  id: "ne",
4884
5336
  label: "is not",
4885
5337
  arity: 1,
4886
- types: ["string", "number", "integer", "currency", "datetime", "enum", "boolean", "relation", "user"]
5338
+ types: ["string", "number", "integer", "currency", "datetime", "enum", "boolean", "relation", "user", "principal"]
4887
5339
  },
4888
5340
  {
4889
5341
  id: "in",
4890
5342
  label: "is any of",
4891
5343
  arity: "n",
4892
- types: ["string", "number", "integer", "currency", "enum", "relation", "user"]
5344
+ types: ["string", "number", "integer", "currency", "enum", "relation", "user", "principal"]
4893
5345
  },
4894
5346
  {
4895
5347
  id: "not_in",
4896
5348
  label: "is none of",
4897
5349
  arity: "n",
4898
- types: ["string", "number", "integer", "currency", "enum", "relation", "user"]
5350
+ types: ["string", "number", "integer", "currency", "enum", "relation", "user", "principal"]
4899
5351
  },
4900
5352
  { id: "contains", label: "contains", arity: 1, types: ["string", "array"] },
4901
5353
  { id: "starts_with", label: "starts with", arity: 1, types: ["string"] },
@@ -4908,13 +5360,13 @@ var OPERATORS = [
4908
5360
  id: "empty",
4909
5361
  label: "is empty",
4910
5362
  arity: 0,
4911
- types: ["string", "number", "integer", "currency", "datetime", "enum", "boolean", "array", "relation", "user", "file"]
5363
+ types: ["string", "number", "integer", "currency", "datetime", "enum", "boolean", "array", "relation", "user", "principal", "file"]
4912
5364
  },
4913
5365
  {
4914
5366
  id: "not_empty",
4915
5367
  label: "is not empty",
4916
5368
  arity: 0,
4917
- types: ["string", "number", "integer", "currency", "datetime", "enum", "boolean", "array", "relation", "user", "file"]
5369
+ types: ["string", "number", "integer", "currency", "datetime", "enum", "boolean", "array", "relation", "user", "principal", "file"]
4918
5370
  }
4919
5371
  ];
4920
5372
  var OP_LABEL_OVERRIDES = {
@@ -5002,9 +5454,16 @@ function ChipItem({
5002
5454
  if (custom !== void 0) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: custom });
5003
5455
  }
5004
5456
  if (field.type === "enum") {
5005
- const variant = field.options?.find((o) => o.value === value)?.variant;
5006
- const label = field.options?.find((o) => o.value === value)?.label ?? value;
5007
- return /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant, children: label });
5457
+ const option = field.options?.find((o) => o.value === value);
5458
+ return /* @__PURE__ */ jsxRuntime.jsx(
5459
+ Badge,
5460
+ {
5461
+ variant: option?.variant,
5462
+ ...option?.color ? { color: option.color } : {},
5463
+ ...option?.icon ? { icon: option.icon } : {},
5464
+ children: option?.label ?? value
5465
+ }
5466
+ );
5008
5467
  }
5009
5468
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className: chunkMWAXK66H_cjs.cn("font-mono text-[12px] text-ink max-w-[10rem] truncate"), children: formatValue(field, operator, value) });
5010
5469
  }
@@ -5385,8 +5844,16 @@ function EnumSingleValue({
5385
5844
  selectedKey: value === "" ? null : value,
5386
5845
  onSelectionChange: (next) => onChange(next == null ? "" : String(next)),
5387
5846
  renderItem: (o) => {
5388
- const variant = field.options?.find((x) => x.value === o.id)?.variant;
5389
- return /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant, children: o.label });
5847
+ const option = field.options?.find((x) => x.value === o.id);
5848
+ return /* @__PURE__ */ jsxRuntime.jsx(
5849
+ Badge,
5850
+ {
5851
+ variant: option?.variant,
5852
+ ...option?.color ? { color: option.color } : {},
5853
+ ...option?.icon ? { icon: option.icon } : {},
5854
+ children: o.label
5855
+ }
5856
+ );
5390
5857
  },
5391
5858
  ...isDisabled !== void 0 ? { isDisabled } : {},
5392
5859
  ...autoFocus ? { autoFocus: true } : {},
@@ -5405,7 +5872,9 @@ function EnumMultiValue({
5405
5872
  const options = (field.options ?? []).map((o) => ({
5406
5873
  id: o.value,
5407
5874
  label: o.label,
5408
- ...o.variant ? { variant: o.variant } : {}
5875
+ ...o.variant ? { variant: o.variant } : {},
5876
+ ...o.color ? { color: o.color } : {},
5877
+ ...o.icon ? { icon: o.icon } : {}
5409
5878
  }));
5410
5879
  return /* @__PURE__ */ jsxRuntime.jsx(
5411
5880
  BadgeMultiSelect,
@@ -7039,6 +7508,74 @@ function TimeField({
7039
7508
  }
7040
7509
  );
7041
7510
  }
7511
+ function createToastQueue(options = {}) {
7512
+ return new reactAriaComponents.UNSTABLE_ToastQueue({ maxVisibleToasts: options.maxVisibleToasts ?? 3 });
7513
+ }
7514
+ var toastStyles = tailwindVariants.tv({
7515
+ slots: {
7516
+ root: [
7517
+ "flex items-start gap-3 w-[360px] max-w-[calc(100vw-32px)]",
7518
+ "rounded-lg border border-rule bg-surface shadow-float",
7519
+ "px-3.5 py-3 outline-none",
7520
+ "focus-visible:shadow-focus",
7521
+ "entering:animate-in entering:fade-in-0 entering:slide-in-from-bottom-2 entering:duration-200",
7522
+ "exiting:animate-out exiting:fade-out-0 exiting:duration-150"
7523
+ ],
7524
+ icon: "size-4 shrink-0 mt-0.5",
7525
+ title: "font-sans text-[13px] font-medium leading-[1.4] text-ink",
7526
+ description: "font-sans text-[12.5px] leading-[1.5] text-ink-3 break-words"
7527
+ },
7528
+ variants: {
7529
+ variant: {
7530
+ info: { icon: "text-ink-3" },
7531
+ success: { icon: "text-success" },
7532
+ danger: { icon: "text-danger" }
7533
+ }
7534
+ },
7535
+ defaultVariants: { variant: "info" }
7536
+ });
7537
+ var VARIANT_ICON = {
7538
+ info: lucideReact.Info,
7539
+ success: lucideReact.CircleCheck,
7540
+ danger: lucideReact.CircleAlert
7541
+ };
7542
+ function ToastRegion({ queue, className }) {
7543
+ return /* @__PURE__ */ jsxRuntime.jsx(
7544
+ reactAriaComponents.UNSTABLE_ToastRegion,
7545
+ {
7546
+ queue,
7547
+ className: chunkMWAXK66H_cjs.cn(
7548
+ "fixed bottom-4 right-4 z-[1300] flex flex-col-reverse gap-2 outline-none",
7549
+ className
7550
+ ),
7551
+ children: ({ toast }) => {
7552
+ const variant = toast.content.variant ?? "info";
7553
+ const Icon = VARIANT_ICON[variant];
7554
+ const { root, icon, title, description } = toastStyles({ variant });
7555
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactAriaComponents.UNSTABLE_Toast, { toast, className: root(), children: [
7556
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: icon(), "aria-hidden": true }),
7557
+ /* @__PURE__ */ jsxRuntime.jsxs(reactAriaComponents.UNSTABLE_ToastContent, { className: "flex flex-col gap-0.5 flex-1 min-w-0", children: [
7558
+ /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.Text, { slot: "title", className: title(), children: toast.content.title }),
7559
+ toast.content.description ? /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.Text, { slot: "description", className: description(), children: toast.content.description }) : null
7560
+ ] }),
7561
+ /* @__PURE__ */ jsxRuntime.jsx(
7562
+ reactAriaComponents.Button,
7563
+ {
7564
+ slot: "close",
7565
+ "aria-label": "Dismiss",
7566
+ className: chunkMWAXK66H_cjs.cn(
7567
+ "shrink-0 inline-flex items-center justify-center size-6 rounded-md",
7568
+ "text-ink-4 hover:text-ink hover:bg-surface-2 cursor-pointer outline-none",
7569
+ "focus-visible:shadow-focus"
7570
+ ),
7571
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-3.5" })
7572
+ }
7573
+ )
7574
+ ] });
7575
+ }
7576
+ }
7577
+ );
7578
+ }
7042
7579
  function TooltipTrigger({
7043
7580
  delay = 500,
7044
7581
  closeDelay = 0,
@@ -7110,6 +7647,7 @@ exports.ChannelIcon = ChannelIcon;
7110
7647
  exports.Checkbox = Checkbox;
7111
7648
  exports.CheckboxGroup = CheckboxGroup;
7112
7649
  exports.ChipValue = ChipValue;
7650
+ exports.ColorInput = ColorInput;
7113
7651
  exports.Combobox = Combobox;
7114
7652
  exports.Command = Command;
7115
7653
  exports.CommandHint = CommandHint;
@@ -7134,6 +7672,7 @@ exports.FileViewer = FileViewer;
7134
7672
  exports.FileViewerFallbackCard = FileViewerFallbackCard;
7135
7673
  exports.FilterRow = FilterRow;
7136
7674
  exports.FilterValueInput = FilterValueInput;
7675
+ exports.IconPicker = IconPicker;
7137
7676
  exports.ImageAttachment = ImageAttachment;
7138
7677
  exports.ImageLightbox = ImageLightbox;
7139
7678
  exports.Input = Input;
@@ -7193,6 +7732,7 @@ exports.Tabs = Tabs;
7193
7732
  exports.TagInput = TagInput;
7194
7733
  exports.Textarea = Textarea;
7195
7734
  exports.TimeField = TimeField;
7735
+ exports.ToastRegion = ToastRegion;
7196
7736
  exports.Tooltip = Tooltip;
7197
7737
  exports.TooltipTrigger = TooltipTrigger;
7198
7738
  exports.TruncateCell = TruncateCell;
@@ -7210,6 +7750,7 @@ exports.cardStyles = cardStyles2;
7210
7750
  exports.channelIcon = channelIcon;
7211
7751
  exports.countLeaves = countLeaves;
7212
7752
  exports.createDraftMessageClient = createDraftMessageClient;
7753
+ exports.createToastQueue = createToastQueue;
7213
7754
  exports.defaultValueFor = defaultValueFor;
7214
7755
  exports.draftMessageCardStyles = draftMessageCardStyles;
7215
7756
  exports.draftMessageComposerStyles = draftMessageComposerStyles;
@@ -7233,6 +7774,7 @@ exports.isPreviewableImageName = isPreviewableImageName;
7233
7774
  exports.makeActionsColumn = makeActionsColumn;
7234
7775
  exports.messageComposerStyles = messageComposerStyles;
7235
7776
  exports.navItemStyles = navItemStyles;
7777
+ exports.normalizeHexColor = normalizeHexColor;
7236
7778
  exports.operatorById = operatorById;
7237
7779
  exports.operatorsForType = operatorsForType;
7238
7780
  exports.parseCombo = parseCombo;
@@ -7244,6 +7786,8 @@ exports.registerDraftMessageActions = registerDraftMessageActions;
7244
7786
  exports.removeLeaf = removeLeaf;
7245
7787
  exports.removeSubgroup = removeSubgroup;
7246
7788
  exports.replaceLeaf = replaceLeaf;
7789
+ exports.resolveLucideIcon = resolveLucideIcon;
7790
+ exports.resolveLucideIconName = resolveLucideIconName;
7247
7791
  exports.segmentedControlStyles = segmentedControlStyles;
7248
7792
  exports.setJoiner = setJoiner;
7249
7793
  exports.subscribeDraftMessageOutcome = subscribeDraftMessageOutcome;
@@ -7251,6 +7795,7 @@ exports.tableCellStyles = tableCellStyles;
7251
7795
  exports.tableHeaderCellStyles = tableHeaderCellStyles;
7252
7796
  exports.tabsStyles = tabsStyles;
7253
7797
  exports.terminalKindFor = terminalKindFor;
7798
+ exports.toastStyles = toastStyles;
7254
7799
  exports.updateLeaf = updateLeaf;
7255
7800
  exports.updateSubgroup = updateSubgroup;
7256
7801
  exports.useAttributeTypeIcon = useAttributeTypeIcon;