@agent-native/toolkit 0.211.0 → 0.212.0-nightly-20261021010049

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 (143) hide show
  1. package/agent-native.eject.json +1 -0
  2. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  3. package/dist/app/i18n/catalogs/ar-SA.js +23 -0
  4. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  5. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  6. package/dist/app/i18n/catalogs/de-DE.js +23 -0
  7. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  8. package/dist/app/i18n/catalogs/en-US.d.ts +23 -0
  9. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  10. package/dist/app/i18n/catalogs/en-US.js +23 -0
  11. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  12. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  13. package/dist/app/i18n/catalogs/es-ES.js +23 -0
  14. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  15. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  16. package/dist/app/i18n/catalogs/fr-FR.js +23 -0
  17. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  18. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  19. package/dist/app/i18n/catalogs/hi-IN.js +23 -0
  20. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  21. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  22. package/dist/app/i18n/catalogs/ja-JP.js +23 -0
  23. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  24. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  25. package/dist/app/i18n/catalogs/ko-KR.js +23 -0
  26. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  27. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  28. package/dist/app/i18n/catalogs/pt-BR.js +23 -0
  29. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  30. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  31. package/dist/app/i18n/catalogs/zh-CN.js +23 -0
  32. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  33. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  34. package/dist/app/i18n/catalogs/zh-TW.js +23 -0
  35. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  36. package/dist/app/sharing/OwnershipFilter.d.ts +8 -0
  37. package/dist/app/sharing/OwnershipFilter.d.ts.map +1 -0
  38. package/dist/app/sharing/OwnershipFilter.js +20 -0
  39. package/dist/app/sharing/OwnershipFilter.js.map +1 -0
  40. package/dist/app/sharing/ShareButton.d.ts +43 -1
  41. package/dist/app/sharing/ShareButton.d.ts.map +1 -1
  42. package/dist/app/sharing/ShareButton.js +102 -63
  43. package/dist/app/sharing/ShareButton.js.map +1 -1
  44. package/dist/app/sharing/ShareDialog.d.ts +3 -1
  45. package/dist/app/sharing/ShareDialog.d.ts.map +1 -1
  46. package/dist/app/sharing/ShareDialog.js +18 -12
  47. package/dist/app/sharing/ShareDialog.js.map +1 -1
  48. package/dist/app/sharing/ShareVisibilityConfirmDialog.d.ts +20 -0
  49. package/dist/app/sharing/ShareVisibilityConfirmDialog.d.ts.map +1 -0
  50. package/dist/app/sharing/ShareVisibilityConfirmDialog.js +57 -0
  51. package/dist/app/sharing/ShareVisibilityConfirmDialog.js.map +1 -0
  52. package/dist/app/sharing/VisibilityBadge.d.ts +8 -1
  53. package/dist/app/sharing/VisibilityBadge.d.ts.map +1 -1
  54. package/dist/app/sharing/VisibilityBadge.js +16 -1
  55. package/dist/app/sharing/VisibilityBadge.js.map +1 -1
  56. package/dist/app/sharing/index.d.ts +2 -0
  57. package/dist/app/sharing/index.d.ts.map +1 -1
  58. package/dist/app/sharing/index.js +2 -0
  59. package/dist/app/sharing/index.js.map +1 -1
  60. package/dist/chat-history/ChatHistoryList.d.ts +19 -0
  61. package/dist/chat-history/ChatHistoryList.d.ts.map +1 -1
  62. package/dist/chat-history/ChatHistoryList.js +19 -7
  63. package/dist/chat-history/ChatHistoryList.js.map +1 -1
  64. package/dist/chat-history/ChatHistoryRail.d.ts +55 -8
  65. package/dist/chat-history/ChatHistoryRail.d.ts.map +1 -1
  66. package/dist/chat-history/ChatHistoryRail.js +33 -7
  67. package/dist/chat-history/ChatHistoryRail.js.map +1 -1
  68. package/dist/chat-history/index.d.ts +3 -3
  69. package/dist/chat-history/index.d.ts.map +1 -1
  70. package/dist/chat-history/index.js +1 -1
  71. package/dist/chat-history/index.js.map +1 -1
  72. package/dist/chat-history/useChatHistoryRailController.d.ts +44 -0
  73. package/dist/chat-history/useChatHistoryRailController.d.ts.map +1 -1
  74. package/dist/chat-history/useChatHistoryRailController.js +46 -0
  75. package/dist/chat-history/useChatHistoryRailController.js.map +1 -1
  76. package/dist/chat-history.css +29 -0
  77. package/dist/composer/TiptapComposer.d.ts +7 -0
  78. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  79. package/dist/composer/TiptapComposer.js +15 -2
  80. package/dist/composer/TiptapComposer.js.map +1 -1
  81. package/dist/sharing/OwnershipFilter.d.ts +21 -0
  82. package/dist/sharing/OwnershipFilter.d.ts.map +1 -0
  83. package/dist/sharing/OwnershipFilter.js +30 -0
  84. package/dist/sharing/OwnershipFilter.js.map +1 -0
  85. package/dist/sharing/VisibilityBadge.d.ts +12 -2
  86. package/dist/sharing/VisibilityBadge.d.ts.map +1 -1
  87. package/dist/sharing/VisibilityBadge.js +13 -4
  88. package/dist/sharing/VisibilityBadge.js.map +1 -1
  89. package/dist/sharing/index.d.ts +2 -1
  90. package/dist/sharing/index.d.ts.map +1 -1
  91. package/dist/sharing/index.js +2 -1
  92. package/dist/sharing/index.js.map +1 -1
  93. package/dist/ui/alert-dialog.d.ts +2 -0
  94. package/dist/ui/alert-dialog.d.ts.map +1 -1
  95. package/dist/ui/alert-dialog.js +1 -1
  96. package/dist/ui/alert-dialog.js.map +1 -1
  97. package/dist/ui/tooltip.d.ts +6 -1
  98. package/dist/ui/tooltip.d.ts.map +1 -1
  99. package/dist/ui/tooltip.js +13 -1
  100. package/dist/ui/tooltip.js.map +1 -1
  101. package/package.json +4 -4
  102. package/src/app/i18n/catalogs/ar-SA.ts +26 -0
  103. package/src/app/i18n/catalogs/de-DE.ts +25 -0
  104. package/src/app/i18n/catalogs/en-US.ts +25 -0
  105. package/src/app/i18n/catalogs/es-ES.ts +25 -0
  106. package/src/app/i18n/catalogs/fr-FR.ts +25 -0
  107. package/src/app/i18n/catalogs/hi-IN.ts +25 -0
  108. package/src/app/i18n/catalogs/ja-JP.ts +25 -0
  109. package/src/app/i18n/catalogs/ko-KR.ts +25 -0
  110. package/src/app/i18n/catalogs/pt-BR.ts +25 -0
  111. package/src/app/i18n/catalogs/zh-CN.ts +25 -0
  112. package/src/app/i18n/catalogs/zh-TW.ts +25 -0
  113. package/src/app/sharing/OwnershipFilter.tsx +35 -0
  114. package/src/app/sharing/ShareButton.tsx +384 -259
  115. package/src/app/sharing/ShareDialog.spec.tsx +50 -0
  116. package/src/app/sharing/ShareDialog.tsx +51 -15
  117. package/src/app/sharing/ShareVisibilityConfirmDialog.spec.tsx +138 -0
  118. package/src/app/sharing/ShareVisibilityConfirmDialog.tsx +124 -0
  119. package/src/app/sharing/ShareVisibilityKeyboard.spec.tsx +244 -0
  120. package/src/app/sharing/VisibilityBadge.spec.tsx +65 -0
  121. package/src/app/sharing/VisibilityBadge.tsx +31 -3
  122. package/src/app/sharing/index.ts +2 -0
  123. package/src/app/sharing/share-visibility-mutation-lifecycle.spec.tsx +1107 -0
  124. package/src/app/sharing/useShareButtonController.spec.tsx +226 -37
  125. package/src/app/sharing/useShareDialogController.spec.tsx +357 -9
  126. package/src/chat-history/ChatHistoryList.spec.tsx +157 -0
  127. package/src/chat-history/ChatHistoryList.tsx +104 -10
  128. package/src/chat-history/ChatHistoryRail.spec.tsx +50 -1
  129. package/src/chat-history/ChatHistoryRail.tsx +177 -26
  130. package/src/chat-history/ChatHistoryRailSections.spec.tsx +410 -0
  131. package/src/chat-history/index.ts +8 -0
  132. package/src/chat-history/useChatHistoryRailController.ts +94 -0
  133. package/src/chat-history/useChatHistoryRailSectionsController.spec.tsx +133 -0
  134. package/src/chat-history.css +29 -0
  135. package/src/composer/TiptapComposer.spec.ts +19 -0
  136. package/src/composer/TiptapComposer.tsx +26 -5
  137. package/src/sharing/OwnershipFilter.spec.tsx +108 -0
  138. package/src/sharing/OwnershipFilter.tsx +103 -0
  139. package/src/sharing/VisibilityBadge.spec.ts +53 -35
  140. package/src/sharing/VisibilityBadge.tsx +23 -3
  141. package/src/sharing/index.ts +11 -0
  142. package/src/ui/alert-dialog.tsx +7 -2
  143. package/src/ui/tooltip.tsx +22 -1
@@ -55,11 +55,13 @@ import {
55
55
  } from "react";
56
56
  import type {
57
57
  KeyboardEvent as ReactKeyboardEvent,
58
+ Ref,
58
59
  UIEvent as ReactUIEvent,
59
60
  } from "react";
60
61
 
61
62
  import { AccessRequestsSection } from "./AccessRequestsSection.js";
62
63
  import { AgentShareSection } from "./AgentShareSection.js";
64
+ import { ShareVisibilityConfirmDialog } from "./ShareVisibilityConfirmDialog.js";
63
65
 
64
66
  export interface ShareButtonProps {
65
67
  resourceType: string;
@@ -125,6 +127,11 @@ export interface ShareButtonProps {
125
127
  visibilityCopy?: Partial<
126
128
  Record<Visibility, { label?: string; description?: string }>
127
129
  >;
130
+ /**
131
+ * What is specific to this resource, already translated, added to the
132
+ * confirmation shown before it becomes visible to the organization.
133
+ */
134
+ visibilityConfirmDetail?: ReactNode;
128
135
  roleCopy?: Partial<Record<Role, { label?: string; description?: string }>>;
129
136
  allowedRoles?: readonly Role[];
130
137
  peopleAccessLabel?: ReactNode;
@@ -450,44 +457,17 @@ function SharePanel(
450
457
  const t = useT();
451
458
  const { controller } = props;
452
459
  const {
453
- inviteEmail,
454
- setInviteEmail: onInviteEmailChange,
455
460
  activeShareTab,
456
461
  handleShareTabChange,
457
462
  data,
458
- policy,
459
463
  visibility,
460
464
  canManage,
461
- role,
462
- setRole,
463
- notifyPeople,
464
- setNotifyPeople,
465
- shareMessage,
466
- setShareMessage,
467
- messageOpen,
468
- setMessageOpen,
469
465
  shareError,
470
- setShareError,
471
- suggestionsOpen,
472
- setSuggestionsOpen,
473
- inFlight,
474
- memberSearch,
475
- memberSuggestions,
476
- groupSuggestions,
477
- selectGroup,
478
- knownMembers,
479
- shares,
480
- handleVisibility,
481
466
  handleHideInSearch,
482
- handleAdd,
483
- handleChangeRole,
484
- handleRemove,
485
467
  } = controller;
486
- const hasInviteEmail = inviteEmail.trim().length > 0;
487
468
 
488
469
  const loadFailed = controller.sharesQuery.isError && data === undefined;
489
470
  const isLoading = !loadFailed && data === undefined;
490
- const meta = visibilityMeta(visibility, t, props.visibilityCopy);
491
471
  const peopleAccessLabel =
492
472
  props.peopleAccessLabel ??
493
473
  t("agentChat.share.peopleWithAccess", {
@@ -599,36 +579,20 @@ function SharePanel(
599
579
  {generalAccessLabel}
600
580
  </div>
601
581
  ) : null}
602
- <div className="flex items-center gap-2">
603
- <span
604
- aria-hidden
605
- className="inline-flex size-6 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground"
606
- >
607
- <meta.Icon size={12} strokeWidth={1.75} />
608
- </span>
609
- <div className="min-w-0 flex-1">
610
- <VisibilitySelect
611
- value={visibility}
612
- onChange={handleVisibility}
613
- disabled={!canManage}
614
- sentenceTrigger={Boolean(props.agentTabContent)}
615
- visibilityCopy={props.visibilityCopy}
616
- allowPublic={policy.allowPublic}
582
+ <ShareGeneralAccessRow
583
+ controller={controller}
584
+ visibilityCopy={props.visibilityCopy}
585
+ sentenceTrigger={Boolean(props.agentTabContent)}
586
+ confirmDetail={props.visibilityConfirmDetail}
587
+ >
588
+ {visibility === "org" && props.hideInSearchControl ? (
589
+ <AdvancedAccessPopover
590
+ control={props.hideInSearchControl}
591
+ canManage={canManage}
592
+ onToggle={handleHideInSearch}
617
593
  />
618
- <div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground">
619
- {!props.agentTabContent ? (
620
- <span className="sr-only">{meta.description}</span>
621
- ) : null}
622
- {visibility === "org" && props.hideInSearchControl ? (
623
- <AdvancedAccessPopover
624
- control={props.hideInSearchControl}
625
- canManage={canManage}
626
- onToggle={handleHideInSearch}
627
- />
628
- ) : null}
629
- </div>
630
- </div>
631
- </div>
594
+ ) : null}
595
+ </ShareGeneralAccessRow>
632
596
  </div>
633
597
 
634
598
  <div className={cn("mb-3 space-y-3", props.agentTabContent && "order-1")}>
@@ -638,134 +602,12 @@ function SharePanel(
638
602
  <div className={SHARE_SECTION_LABEL}>{peopleAccessLabel}</div>
639
603
  </>
640
604
  ) : null}
641
- {canManage ? (
642
- <div className="space-y-2">
643
- <div className="flex items-stretch gap-1.5">
644
- <MemberAutocomplete
645
- value={inviteEmail}
646
- open={suggestionsOpen && !props.basicSharingOnly}
647
- onOpenChange={(open) => {
648
- if (!props.basicSharingOnly) setSuggestionsOpen(open);
649
- }}
650
- onValueChange={(next) => {
651
- onInviteEmailChange(next);
652
- if (shareError) setShareError(null);
653
- }}
654
- onSelectSuggestion={(suggestion) => {
655
- if (suggestion.principalType === "group") {
656
- selectGroup(suggestion);
657
- } else {
658
- onInviteEmailChange(suggestion.email);
659
- }
660
- setSuggestionsOpen(false);
661
- if (shareError) setShareError(null);
662
- }}
663
- onSubmit={handleAdd}
664
- placeholder={
665
- policy.requireOrgMemberForUserShares
666
- ? t("agentChat.share.addPeopleOrganization", {
667
- defaultValue: "Add people from your organization",
668
- })
669
- : t("agentChat.share.addPeopleEmail", {
670
- defaultValue: policy.supportsGroupShares
671
- ? "Add people or groups"
672
- : "Add people by email",
673
- })
674
- }
675
- suggestions={[
676
- ...memberSuggestions.map((member) => ({
677
- ...member,
678
- principalType: "user" as const,
679
- })),
680
- ...(policy.supportsGroupShares
681
- ? groupSuggestions.map((group) => ({
682
- ...group,
683
- principalType: "group" as const,
684
- }))
685
- : []),
686
- ]}
687
- search={memberSearch}
688
- trailing={
689
- <RoleSelect
690
- value={role}
691
- onChange={setRole}
692
- roleCopy={props.roleCopy}
693
- allowedRoles={allowedRolesFor(props)}
694
- />
695
- }
696
- />
697
- <button
698
- type="button"
699
- onClick={handleAdd}
700
- disabled={!hasInviteEmail}
701
- aria-label={t("agentChat.share.add", { defaultValue: "Add" })}
702
- title={t("agentChat.share.add", { defaultValue: "Add" })}
703
- className={BUTTON_PRIMARY_ICON}
704
- >
705
- <IconSend2 />
706
- </button>
707
- </div>
708
- {shareError ? (
709
- <div
710
- role="alert"
711
- className="rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive"
712
- >
713
- {shareError}
714
- </div>
715
- ) : null}
716
- {hasInviteEmail ? (
717
- <div className="flex flex-wrap items-center gap-x-3 gap-y-2 text-xs text-muted-foreground">
718
- <label className="inline-flex items-center gap-2">
719
- <input
720
- type="checkbox"
721
- checked={notifyPeople}
722
- onChange={(e) => setNotifyPeople(e.target.checked)}
723
- className="h-4 w-4 rounded border-input accent-primary"
724
- />
725
- {t("agentChat.share.notifyPeople", {
726
- defaultValue: "Notify people",
727
- })}
728
- </label>
729
- {notifyPeople && !props.basicSharingOnly ? (
730
- <button
731
- type="button"
732
- aria-expanded={messageOpen}
733
- onClick={() => setMessageOpen(!messageOpen)}
734
- className="rounded-sm px-1 py-0.5 font-medium text-foreground underline decoration-border underline-offset-2 transition-colors hover:bg-accent hover:text-accent-foreground"
735
- >
736
- {messageOpen
737
- ? t("agentChat.share.hideMessage", {
738
- defaultValue: "Hide message",
739
- })
740
- : t("agentChat.share.addMessage", {
741
- defaultValue: "Add a message",
742
- })}
743
- </button>
744
- ) : null}
745
- </div>
746
- ) : null}
747
- {hasInviteEmail &&
748
- notifyPeople &&
749
- messageOpen &&
750
- !props.basicSharingOnly ? (
751
- <div className="rounded-md border border-border/70 bg-muted/20 p-2.5">
752
- <textarea
753
- aria-label={t("agentChat.share.message", {
754
- defaultValue: "Message",
755
- })}
756
- placeholder={t("agentChat.share.messagePlaceholder", {
757
- defaultValue: "Add a short note (optional)",
758
- })}
759
- value={shareMessage}
760
- onChange={(event) => setShareMessage(event.target.value)}
761
- maxLength={500}
762
- rows={3}
763
- className="w-full resize-y rounded-md border border-input bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background"
764
- />
765
- </div>
766
- ) : null}
767
- </div>
768
- ) : null}
605
+ <SharePeopleInvite
606
+ controller={controller}
607
+ basicSharingOnly={props.basicSharingOnly}
608
+ roleCopy={props.roleCopy}
609
+ allowedRoles={allowedRolesFor(props)}
610
+ />
769
611
 
770
612
  {props.agentTabContent ? (
771
613
  <>
@@ -773,81 +615,11 @@ function SharePanel(
773
615
  <div className={SHARE_SECTION_LABEL}>{peopleAccessLabel}</div>
774
616
  </>
775
617
  ) : null}
776
- <ul className="flex list-none flex-col gap-1 p-0 m-0">
777
- {data?.ownerEmail ? (
778
- <li className="flex min-h-8 items-center gap-2 px-1.5 py-1 text-sm">
779
- <Avatar
780
- email={data.ownerEmail}
781
- image={memberImage(data.ownerEmail, knownMembers)}
782
- label={displayName(data.ownerEmail, knownMembers, t)}
783
- />
784
- <span className="flex-1 min-w-0 truncate">
785
- {displayName(data.ownerEmail, knownMembers, t)}
786
- </span>
787
- <span className="text-xs text-muted-foreground">
788
- {t("agentChat.share.owner", { defaultValue: "Owner" })}
789
- </span>
790
- </li>
791
- ) : null}
792
- {shares.map((s) => (
793
- <li
794
- key={keyOf(s)}
795
- className={cn(
796
- "flex min-h-8 items-center gap-2 px-1.5 py-1 text-sm",
797
- inFlight.has(keyOf(s)) && "opacity-60",
798
- )}
799
- >
800
- <Avatar
801
- email={s.principalType === "user" ? s.principalId : undefined}
802
- image={
803
- s.principalType === "user"
804
- ? memberImage(s.principalId, knownMembers)
805
- : undefined
806
- }
807
- label={principalLabel(s, knownMembers, t)}
808
- org={s.principalType === "org"}
809
- group={s.principalType === "group"}
810
- />
811
- <span className="flex-1 min-w-0 truncate">
812
- {principalLabel(s, knownMembers, t)}
813
- </span>
814
- {canManage ? (
815
- <RoleSelect
816
- value={s.role}
817
- onChange={(r) => handleChangeRole(s, r)}
818
- disabled={inFlight.has(keyOf(s))}
819
- plain
820
- roleCopy={props.roleCopy}
821
- allowedRoles={allowedRolesFor(props)}
822
- />
823
- ) : (
824
- <span className="text-xs text-muted-foreground">
825
- {roleMeta(s.role, t, props.roleCopy).label}
826
- </span>
827
- )}
828
- {canManage ? (
829
- <button
830
- type="button"
831
- aria-label={t("agentChat.share.remove", {
832
- defaultValue: "Remove",
833
- })}
834
- onClick={() => handleRemove(s)}
835
- disabled={inFlight.has(keyOf(s))}
836
- className={BUTTON_GHOST_ICON}
837
- >
838
- <IconTrash size={14} />
839
- </button>
840
- ) : null}
841
- </li>
842
- ))}
843
- {!shares.length && !data?.ownerEmail ? (
844
- <li className="px-1.5 py-1 text-sm text-muted-foreground">
845
- {t("agentChat.share.noAccess", {
846
- defaultValue: "No one has access yet.",
847
- })}
848
- </li>
849
- ) : null}
850
- </ul>
618
+ <SharePeopleList
619
+ controller={controller}
620
+ roleCopy={props.roleCopy}
621
+ allowedRoles={allowedRolesFor(props)}
622
+ />
851
623
  </div>
852
624
 
853
625
  {shareError && !canManage ? (
@@ -969,6 +741,282 @@ function SharePanel(
969
741
  );
970
742
  }
971
743
 
744
+ export interface SharePeopleControlProps {
745
+ controller: ShareButtonController;
746
+ roleCopy?: ShareButtonProps["roleCopy"];
747
+ allowedRoles?: readonly ShareButtonRole[];
748
+ }
749
+
750
+ /**
751
+ * The add-people field (member and group suggestions, role, notify and
752
+ * message) of the share panel. Renders nothing unless the user can manage
753
+ * access. Apps that compose their own sharing surface reuse it with
754
+ * `useShareButtonController`, next to `ShareGeneralAccessRow` and
755
+ * `SharePeopleList`.
756
+ */
757
+ export function SharePeopleInvite({
758
+ controller,
759
+ basicSharingOnly,
760
+ roleCopy,
761
+ allowedRoles,
762
+ }: SharePeopleControlProps & { basicSharingOnly?: boolean }) {
763
+ const t = useT();
764
+ const {
765
+ inviteEmail,
766
+ setInviteEmail: onInviteEmailChange,
767
+ policy,
768
+ canManage,
769
+ role,
770
+ setRole,
771
+ notifyPeople,
772
+ setNotifyPeople,
773
+ shareMessage,
774
+ setShareMessage,
775
+ messageOpen,
776
+ setMessageOpen,
777
+ shareError,
778
+ setShareError,
779
+ suggestionsOpen,
780
+ setSuggestionsOpen,
781
+ memberSearch,
782
+ memberSuggestions,
783
+ groupSuggestions,
784
+ selectGroup,
785
+ handleAdd,
786
+ } = controller;
787
+ const hasInviteEmail = inviteEmail.trim().length > 0;
788
+ return (
789
+ <>
790
+ {canManage ? (
791
+ <div className="space-y-2">
792
+ <div className="flex items-stretch gap-1.5">
793
+ <MemberAutocomplete
794
+ value={inviteEmail}
795
+ open={suggestionsOpen && !basicSharingOnly}
796
+ onOpenChange={(open) => {
797
+ if (!basicSharingOnly) setSuggestionsOpen(open);
798
+ }}
799
+ onValueChange={(next) => {
800
+ onInviteEmailChange(next);
801
+ if (shareError) setShareError(null);
802
+ }}
803
+ onSelectSuggestion={(suggestion) => {
804
+ if (suggestion.principalType === "group") {
805
+ selectGroup(suggestion);
806
+ } else {
807
+ onInviteEmailChange(suggestion.email);
808
+ }
809
+ setSuggestionsOpen(false);
810
+ if (shareError) setShareError(null);
811
+ }}
812
+ onSubmit={handleAdd}
813
+ placeholder={
814
+ policy.requireOrgMemberForUserShares
815
+ ? t("agentChat.share.addPeopleOrganization", {
816
+ defaultValue: "Add people from your organization",
817
+ })
818
+ : t("agentChat.share.addPeopleEmail", {
819
+ defaultValue: policy.supportsGroupShares
820
+ ? "Add people or groups"
821
+ : "Add people by email",
822
+ })
823
+ }
824
+ suggestions={[
825
+ ...memberSuggestions.map((member) => ({
826
+ ...member,
827
+ principalType: "user" as const,
828
+ })),
829
+ ...(policy.supportsGroupShares
830
+ ? groupSuggestions.map((group) => ({
831
+ ...group,
832
+ principalType: "group" as const,
833
+ }))
834
+ : []),
835
+ ]}
836
+ search={memberSearch}
837
+ trailing={
838
+ <RoleSelect
839
+ value={role}
840
+ onChange={setRole}
841
+ roleCopy={roleCopy}
842
+ allowedRoles={allowedRoles}
843
+ />
844
+ }
845
+ />
846
+ <button
847
+ type="button"
848
+ onClick={handleAdd}
849
+ disabled={!hasInviteEmail}
850
+ aria-label={t("agentChat.share.add", { defaultValue: "Add" })}
851
+ title={t("agentChat.share.add", { defaultValue: "Add" })}
852
+ className={BUTTON_PRIMARY_ICON}
853
+ >
854
+ <IconSend2 />
855
+ </button>
856
+ </div>
857
+ {shareError ? (
858
+ <div
859
+ role="alert"
860
+ className="rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive"
861
+ >
862
+ {shareError}
863
+ </div>
864
+ ) : null}
865
+ {hasInviteEmail ? (
866
+ <div className="flex flex-wrap items-center gap-x-3 gap-y-2 text-xs text-muted-foreground">
867
+ <label className="inline-flex items-center gap-2">
868
+ <input
869
+ type="checkbox"
870
+ checked={notifyPeople}
871
+ onChange={(e) => setNotifyPeople(e.target.checked)}
872
+ className="h-4 w-4 rounded border-input accent-primary"
873
+ />
874
+ {t("agentChat.share.notifyPeople", {
875
+ defaultValue: "Notify people",
876
+ })}
877
+ </label>
878
+ {notifyPeople && !basicSharingOnly ? (
879
+ <button
880
+ type="button"
881
+ aria-expanded={messageOpen}
882
+ onClick={() => setMessageOpen(!messageOpen)}
883
+ className="rounded-sm px-1 py-0.5 font-medium text-foreground underline decoration-border underline-offset-2 transition-colors hover:bg-accent hover:text-accent-foreground"
884
+ >
885
+ {messageOpen
886
+ ? t("agentChat.share.hideMessage", {
887
+ defaultValue: "Hide message",
888
+ })
889
+ : t("agentChat.share.addMessage", {
890
+ defaultValue: "Add a message",
891
+ })}
892
+ </button>
893
+ ) : null}
894
+ </div>
895
+ ) : null}
896
+ {hasInviteEmail &&
897
+ notifyPeople &&
898
+ messageOpen &&
899
+ !basicSharingOnly ? (
900
+ <div className="rounded-md border border-border/70 bg-muted/20 p-2.5">
901
+ <textarea
902
+ aria-label={t("agentChat.share.message", {
903
+ defaultValue: "Message",
904
+ })}
905
+ placeholder={t("agentChat.share.messagePlaceholder", {
906
+ defaultValue: "Add a short note (optional)",
907
+ })}
908
+ value={shareMessage}
909
+ onChange={(event) => setShareMessage(event.target.value)}
910
+ maxLength={500}
911
+ rows={3}
912
+ className="w-full resize-y rounded-md border border-input bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background"
913
+ />
914
+ </div>
915
+ ) : null}
916
+ </div>
917
+ ) : null}
918
+ </>
919
+ );
920
+ }
921
+
922
+ /**
923
+ * The owner and the people who have access, with role pickers and remove
924
+ * buttons for managers.
925
+ */
926
+ export function SharePeopleList({
927
+ controller,
928
+ roleCopy,
929
+ allowedRoles,
930
+ }: SharePeopleControlProps) {
931
+ const t = useT();
932
+ const {
933
+ data,
934
+ canManage,
935
+ inFlight,
936
+ knownMembers,
937
+ shares,
938
+ handleChangeRole,
939
+ handleRemove,
940
+ } = controller;
941
+ return (
942
+ <ul className="flex list-none flex-col gap-1 p-0 m-0">
943
+ {data?.ownerEmail ? (
944
+ <li className="flex min-h-8 items-center gap-2 px-1.5 py-1 text-sm">
945
+ <Avatar
946
+ email={data.ownerEmail}
947
+ image={memberImage(data.ownerEmail, knownMembers)}
948
+ label={displayName(data.ownerEmail, knownMembers, t)}
949
+ />
950
+ <span className="flex-1 min-w-0 truncate">
951
+ {displayName(data.ownerEmail, knownMembers, t)}
952
+ </span>
953
+ <span className="text-xs text-muted-foreground">
954
+ {t("agentChat.share.owner", { defaultValue: "Owner" })}
955
+ </span>
956
+ </li>
957
+ ) : null}
958
+ {shares.map((s) => (
959
+ <li
960
+ key={keyOf(s)}
961
+ className={cn(
962
+ "flex min-h-8 items-center gap-2 px-1.5 py-1 text-sm",
963
+ inFlight.has(keyOf(s)) && "opacity-60",
964
+ )}
965
+ >
966
+ <Avatar
967
+ email={s.principalType === "user" ? s.principalId : undefined}
968
+ image={
969
+ s.principalType === "user"
970
+ ? memberImage(s.principalId, knownMembers)
971
+ : undefined
972
+ }
973
+ label={principalLabel(s, knownMembers, t)}
974
+ org={s.principalType === "org"}
975
+ group={s.principalType === "group"}
976
+ />
977
+ <span className="flex-1 min-w-0 truncate">
978
+ {principalLabel(s, knownMembers, t)}
979
+ </span>
980
+ {canManage ? (
981
+ <RoleSelect
982
+ value={s.role}
983
+ onChange={(r) => handleChangeRole(s, r)}
984
+ disabled={inFlight.has(keyOf(s))}
985
+ plain
986
+ roleCopy={roleCopy}
987
+ allowedRoles={allowedRoles}
988
+ />
989
+ ) : (
990
+ <span className="text-xs text-muted-foreground">
991
+ {roleMeta(s.role, t, roleCopy).label}
992
+ </span>
993
+ )}
994
+ {canManage ? (
995
+ <button
996
+ type="button"
997
+ aria-label={t("agentChat.share.remove", {
998
+ defaultValue: "Remove",
999
+ })}
1000
+ onClick={() => handleRemove(s)}
1001
+ disabled={inFlight.has(keyOf(s))}
1002
+ className={BUTTON_GHOST_ICON}
1003
+ >
1004
+ <IconTrash size={14} />
1005
+ </button>
1006
+ ) : null}
1007
+ </li>
1008
+ ))}
1009
+ {!shares.length && !data?.ownerEmail ? (
1010
+ <li className="px-1.5 py-1 text-sm text-muted-foreground">
1011
+ {t("agentChat.share.noAccess", {
1012
+ defaultValue: "No one has access yet.",
1013
+ })}
1014
+ </li>
1015
+ ) : null}
1016
+ </ul>
1017
+ );
1018
+ }
1019
+
972
1020
  function AdvancedAccessPopover({
973
1021
  control,
974
1022
  canManage,
@@ -1495,6 +1543,76 @@ function RoleSelect(props: {
1495
1543
  );
1496
1544
  }
1497
1545
 
1546
+ export interface ShareGeneralAccessRowProps {
1547
+ controller: Pick<
1548
+ ShareButtonController,
1549
+ | "visibility"
1550
+ | "canManage"
1551
+ | "policy"
1552
+ | "handleVisibility"
1553
+ | "visibilityTransition"
1554
+ >;
1555
+ visibilityCopy?: ShareButtonProps["visibilityCopy"];
1556
+ /** Show the description in the trigger instead of the label. */
1557
+ sentenceTrigger?: boolean;
1558
+ /** Resource-specific text added to the organization confirmation. */
1559
+ confirmDetail?: ReactNode;
1560
+ /** Shown beside the access control, such as an advanced-access popover. */
1561
+ children?: ReactNode;
1562
+ }
1563
+
1564
+ /**
1565
+ * The "general access" control of the share panel: the visibility picker and
1566
+ * the confirmation shown before a resource opens to the organization. Apps
1567
+ * that compose their own sharing surface (Clips) reuse it with
1568
+ * `useShareButtonController` so the confirmation stays the same everywhere.
1569
+ */
1570
+ export function ShareGeneralAccessRow({
1571
+ controller,
1572
+ visibilityCopy,
1573
+ sentenceTrigger,
1574
+ confirmDetail,
1575
+ children,
1576
+ }: ShareGeneralAccessRowProps) {
1577
+ const t = useT();
1578
+ const triggerRef = useRef<HTMLButtonElement>(null);
1579
+ const meta = visibilityMeta(controller.visibility, t, visibilityCopy);
1580
+ return (
1581
+ <div className="flex items-center gap-2">
1582
+ <span
1583
+ aria-hidden
1584
+ className="inline-flex size-6 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground"
1585
+ >
1586
+ <meta.Icon size={12} strokeWidth={1.75} />
1587
+ </span>
1588
+ <div className="min-w-0 flex-1">
1589
+ <VisibilitySelect
1590
+ value={controller.visibility}
1591
+ onChange={controller.handleVisibility}
1592
+ disabled={
1593
+ !controller.canManage || controller.visibilityTransition.submitting
1594
+ }
1595
+ sentenceTrigger={sentenceTrigger}
1596
+ visibilityCopy={visibilityCopy}
1597
+ allowPublic={controller.policy.allowPublic}
1598
+ triggerRef={triggerRef}
1599
+ />
1600
+ <div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground">
1601
+ {!sentenceTrigger ? (
1602
+ <span className="sr-only">{meta.description}</span>
1603
+ ) : null}
1604
+ {children}
1605
+ </div>
1606
+ </div>
1607
+ <ShareVisibilityConfirmDialog
1608
+ transition={controller.visibilityTransition}
1609
+ detail={confirmDetail}
1610
+ returnFocusRef={triggerRef}
1611
+ />
1612
+ </div>
1613
+ );
1614
+ }
1615
+
1498
1616
  function VisibilitySelect(props: {
1499
1617
  value: Visibility;
1500
1618
  onChange: (v: Visibility) => void;
@@ -1503,6 +1621,7 @@ function VisibilitySelect(props: {
1503
1621
  visibilityCopy?: ShareButtonProps["visibilityCopy"];
1504
1622
  allowPrivate?: boolean;
1505
1623
  allowPublic?: boolean;
1624
+ triggerRef?: Ref<HTMLButtonElement>;
1506
1625
  }) {
1507
1626
  const t = useT();
1508
1627
  const allowPrivate = props.allowPrivate !== false;
@@ -1521,6 +1640,7 @@ function VisibilitySelect(props: {
1521
1640
  disabled={props.disabled}
1522
1641
  >
1523
1642
  <Select.Trigger
1643
+ ref={props.triggerRef}
1524
1644
  className={cn(
1525
1645
  BUTTON_BASE,
1526
1646
  "h-7 px-1 -ms-1 bg-transparent font-normal text-foreground hover:bg-accent hover:text-accent-foreground",
@@ -1543,6 +1663,11 @@ function VisibilitySelect(props: {
1543
1663
  className={selectContentClass}
1544
1664
  position="popper"
1545
1665
  sideOffset={4}
1666
+ // Runs after the item has selected. Enter may open a confirmation
1667
+ // that takes focus; its keypress must not land on the button there.
1668
+ onKeyDown={(event) => {
1669
+ if (event.key === "Enter") event.preventDefault();
1670
+ }}
1546
1671
  >
1547
1672
  <Select.Viewport>
1548
1673
  <SelectItems