@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.
- package/agent-native.eject.json +1 -0
- package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.js +23 -0
- package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.js +23 -0
- package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
- package/dist/app/i18n/catalogs/en-US.d.ts +23 -0
- package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/en-US.js +23 -0
- package/dist/app/i18n/catalogs/en-US.js.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.js +23 -0
- package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.js +23 -0
- package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.js +23 -0
- package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.js +23 -0
- package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.js +23 -0
- package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.js +23 -0
- package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.js +23 -0
- package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.js +23 -0
- package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
- package/dist/app/sharing/OwnershipFilter.d.ts +8 -0
- package/dist/app/sharing/OwnershipFilter.d.ts.map +1 -0
- package/dist/app/sharing/OwnershipFilter.js +20 -0
- package/dist/app/sharing/OwnershipFilter.js.map +1 -0
- package/dist/app/sharing/ShareButton.d.ts +43 -1
- package/dist/app/sharing/ShareButton.d.ts.map +1 -1
- package/dist/app/sharing/ShareButton.js +102 -63
- package/dist/app/sharing/ShareButton.js.map +1 -1
- package/dist/app/sharing/ShareDialog.d.ts +3 -1
- package/dist/app/sharing/ShareDialog.d.ts.map +1 -1
- package/dist/app/sharing/ShareDialog.js +18 -12
- package/dist/app/sharing/ShareDialog.js.map +1 -1
- package/dist/app/sharing/ShareVisibilityConfirmDialog.d.ts +20 -0
- package/dist/app/sharing/ShareVisibilityConfirmDialog.d.ts.map +1 -0
- package/dist/app/sharing/ShareVisibilityConfirmDialog.js +57 -0
- package/dist/app/sharing/ShareVisibilityConfirmDialog.js.map +1 -0
- package/dist/app/sharing/VisibilityBadge.d.ts +8 -1
- package/dist/app/sharing/VisibilityBadge.d.ts.map +1 -1
- package/dist/app/sharing/VisibilityBadge.js +16 -1
- package/dist/app/sharing/VisibilityBadge.js.map +1 -1
- package/dist/app/sharing/index.d.ts +2 -0
- package/dist/app/sharing/index.d.ts.map +1 -1
- package/dist/app/sharing/index.js +2 -0
- package/dist/app/sharing/index.js.map +1 -1
- package/dist/chat-history/ChatHistoryList.d.ts +19 -0
- package/dist/chat-history/ChatHistoryList.d.ts.map +1 -1
- package/dist/chat-history/ChatHistoryList.js +19 -7
- package/dist/chat-history/ChatHistoryList.js.map +1 -1
- package/dist/chat-history/ChatHistoryRail.d.ts +55 -8
- package/dist/chat-history/ChatHistoryRail.d.ts.map +1 -1
- package/dist/chat-history/ChatHistoryRail.js +33 -7
- package/dist/chat-history/ChatHistoryRail.js.map +1 -1
- package/dist/chat-history/index.d.ts +3 -3
- package/dist/chat-history/index.d.ts.map +1 -1
- package/dist/chat-history/index.js +1 -1
- package/dist/chat-history/index.js.map +1 -1
- package/dist/chat-history/useChatHistoryRailController.d.ts +44 -0
- package/dist/chat-history/useChatHistoryRailController.d.ts.map +1 -1
- package/dist/chat-history/useChatHistoryRailController.js +46 -0
- package/dist/chat-history/useChatHistoryRailController.js.map +1 -1
- package/dist/chat-history.css +29 -0
- package/dist/composer/TiptapComposer.d.ts +7 -0
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +15 -2
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/dist/sharing/OwnershipFilter.d.ts +21 -0
- package/dist/sharing/OwnershipFilter.d.ts.map +1 -0
- package/dist/sharing/OwnershipFilter.js +30 -0
- package/dist/sharing/OwnershipFilter.js.map +1 -0
- package/dist/sharing/VisibilityBadge.d.ts +12 -2
- package/dist/sharing/VisibilityBadge.d.ts.map +1 -1
- package/dist/sharing/VisibilityBadge.js +13 -4
- package/dist/sharing/VisibilityBadge.js.map +1 -1
- package/dist/sharing/index.d.ts +2 -1
- package/dist/sharing/index.d.ts.map +1 -1
- package/dist/sharing/index.js +2 -1
- package/dist/sharing/index.js.map +1 -1
- package/dist/ui/alert-dialog.d.ts +2 -0
- package/dist/ui/alert-dialog.d.ts.map +1 -1
- package/dist/ui/alert-dialog.js +1 -1
- package/dist/ui/alert-dialog.js.map +1 -1
- package/dist/ui/tooltip.d.ts +6 -1
- package/dist/ui/tooltip.d.ts.map +1 -1
- package/dist/ui/tooltip.js +13 -1
- package/dist/ui/tooltip.js.map +1 -1
- package/package.json +4 -4
- package/src/app/i18n/catalogs/ar-SA.ts +26 -0
- package/src/app/i18n/catalogs/de-DE.ts +25 -0
- package/src/app/i18n/catalogs/en-US.ts +25 -0
- package/src/app/i18n/catalogs/es-ES.ts +25 -0
- package/src/app/i18n/catalogs/fr-FR.ts +25 -0
- package/src/app/i18n/catalogs/hi-IN.ts +25 -0
- package/src/app/i18n/catalogs/ja-JP.ts +25 -0
- package/src/app/i18n/catalogs/ko-KR.ts +25 -0
- package/src/app/i18n/catalogs/pt-BR.ts +25 -0
- package/src/app/i18n/catalogs/zh-CN.ts +25 -0
- package/src/app/i18n/catalogs/zh-TW.ts +25 -0
- package/src/app/sharing/OwnershipFilter.tsx +35 -0
- package/src/app/sharing/ShareButton.tsx +384 -259
- package/src/app/sharing/ShareDialog.spec.tsx +50 -0
- package/src/app/sharing/ShareDialog.tsx +51 -15
- package/src/app/sharing/ShareVisibilityConfirmDialog.spec.tsx +138 -0
- package/src/app/sharing/ShareVisibilityConfirmDialog.tsx +124 -0
- package/src/app/sharing/ShareVisibilityKeyboard.spec.tsx +244 -0
- package/src/app/sharing/VisibilityBadge.spec.tsx +65 -0
- package/src/app/sharing/VisibilityBadge.tsx +31 -3
- package/src/app/sharing/index.ts +2 -0
- package/src/app/sharing/share-visibility-mutation-lifecycle.spec.tsx +1107 -0
- package/src/app/sharing/useShareButtonController.spec.tsx +226 -37
- package/src/app/sharing/useShareDialogController.spec.tsx +357 -9
- package/src/chat-history/ChatHistoryList.spec.tsx +157 -0
- package/src/chat-history/ChatHistoryList.tsx +104 -10
- package/src/chat-history/ChatHistoryRail.spec.tsx +50 -1
- package/src/chat-history/ChatHistoryRail.tsx +177 -26
- package/src/chat-history/ChatHistoryRailSections.spec.tsx +410 -0
- package/src/chat-history/index.ts +8 -0
- package/src/chat-history/useChatHistoryRailController.ts +94 -0
- package/src/chat-history/useChatHistoryRailSectionsController.spec.tsx +133 -0
- package/src/chat-history.css +29 -0
- package/src/composer/TiptapComposer.spec.ts +19 -0
- package/src/composer/TiptapComposer.tsx +26 -5
- package/src/sharing/OwnershipFilter.spec.tsx +108 -0
- package/src/sharing/OwnershipFilter.tsx +103 -0
- package/src/sharing/VisibilityBadge.spec.ts +53 -35
- package/src/sharing/VisibilityBadge.tsx +23 -3
- package/src/sharing/index.ts +11 -0
- package/src/ui/alert-dialog.tsx +7 -2
- package/src/ui/tooltip.tsx +22 -1
|
@@ -226,6 +226,56 @@ describe("ShareDialog primitive normalization", () => {
|
|
|
226
226
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
227
227
|
});
|
|
228
228
|
|
|
229
|
+
it("keeps the Enter that picks Organization from pressing Cancel on the confirmation", async () => {
|
|
230
|
+
await renderDialog();
|
|
231
|
+
const trigger = document.querySelector<HTMLButtonElement>(
|
|
232
|
+
'[aria-label="share.generalAccess"]',
|
|
233
|
+
)!;
|
|
234
|
+
trigger.focus();
|
|
235
|
+
act(() => {
|
|
236
|
+
trigger.dispatchEvent(
|
|
237
|
+
new KeyboardEvent("keydown", {
|
|
238
|
+
key: "ArrowDown",
|
|
239
|
+
bubbles: true,
|
|
240
|
+
cancelable: true,
|
|
241
|
+
}),
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
const organization = Array.from(
|
|
245
|
+
document.querySelectorAll<HTMLElement>('[role="option"]'),
|
|
246
|
+
).find((option) => option.textContent === "share.organization")!;
|
|
247
|
+
organization.focus();
|
|
248
|
+
|
|
249
|
+
const down = new KeyboardEvent("keydown", {
|
|
250
|
+
key: "Enter",
|
|
251
|
+
bubbles: true,
|
|
252
|
+
cancelable: true,
|
|
253
|
+
});
|
|
254
|
+
act(() => {
|
|
255
|
+
organization.dispatchEvent(down);
|
|
256
|
+
});
|
|
257
|
+
// The browser's keypress goes to whatever holds focus by then, and its
|
|
258
|
+
// default action presses a focused button unless it was prevented.
|
|
259
|
+
const target = document.activeElement ?? document.body;
|
|
260
|
+
if (!down.defaultPrevented) {
|
|
261
|
+
const keypress = new KeyboardEvent("keypress", {
|
|
262
|
+
key: "Enter",
|
|
263
|
+
bubbles: true,
|
|
264
|
+
cancelable: true,
|
|
265
|
+
});
|
|
266
|
+
act(() => {
|
|
267
|
+
target.dispatchEvent(keypress);
|
|
268
|
+
});
|
|
269
|
+
if (!keypress.defaultPrevented && target instanceof HTMLButtonElement) {
|
|
270
|
+
act(() => target.click());
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const confirmation = document.querySelector('[role="alertdialog"]');
|
|
275
|
+
expect(confirmation).not.toBeNull();
|
|
276
|
+
expect(document.activeElement?.textContent).toBe("agentChat.common.cancel");
|
|
277
|
+
});
|
|
278
|
+
|
|
229
279
|
it("closes when the overlay is pressed", async () => {
|
|
230
280
|
const onClose = await renderDialog();
|
|
231
281
|
const overlay = document.querySelector<HTMLElement>(
|
|
@@ -27,10 +27,11 @@ import {
|
|
|
27
27
|
IconUsersGroup,
|
|
28
28
|
IconWorld,
|
|
29
29
|
} from "@tabler/icons-react";
|
|
30
|
-
import { type ReactNode } from "react";
|
|
30
|
+
import { useRef, type ReactNode, type RefObject } from "react";
|
|
31
31
|
|
|
32
32
|
import { AccessRequestsSection } from "./AccessRequestsSection.js";
|
|
33
33
|
import { AgentShareSection } from "./AgentShareSection.js";
|
|
34
|
+
import { ShareVisibilityConfirmDialog } from "./ShareVisibilityConfirmDialog.js";
|
|
34
35
|
|
|
35
36
|
export interface ShareDialogProps {
|
|
36
37
|
open: boolean;
|
|
@@ -43,6 +44,8 @@ export interface ShareDialogProps {
|
|
|
43
44
|
embedUrl?: string;
|
|
44
45
|
embedTabContent?: ReactNode;
|
|
45
46
|
linkTabExtras?: ReactNode;
|
|
47
|
+
/** Resource-specific text added to the organization confirmation. */
|
|
48
|
+
visibilityConfirmDetail?: ReactNode;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
const BUTTON_BASE =
|
|
@@ -73,7 +76,9 @@ export function ShareDialog({
|
|
|
73
76
|
embedUrl,
|
|
74
77
|
embedTabContent,
|
|
75
78
|
linkTabExtras,
|
|
79
|
+
visibilityConfirmDetail,
|
|
76
80
|
}: ShareDialogProps) {
|
|
81
|
+
const visibilityRef = useRef<HTMLElement>(null);
|
|
77
82
|
const controller = useShareDialogController({
|
|
78
83
|
open,
|
|
79
84
|
onClose,
|
|
@@ -139,6 +144,7 @@ export function ShareDialog({
|
|
|
139
144
|
<TabsContent value="link" className="m-0">
|
|
140
145
|
<LinkTab
|
|
141
146
|
controller={controller}
|
|
147
|
+
visibilityRef={visibilityRef}
|
|
142
148
|
extras={linkTabExtras}
|
|
143
149
|
accessRequests={accessRequests}
|
|
144
150
|
/>
|
|
@@ -147,6 +153,7 @@ export function ShareDialog({
|
|
|
147
153
|
{!controller.tabsEnabled || controller.activeTab === "invite" ? (
|
|
148
154
|
<InviteTab
|
|
149
155
|
controller={controller}
|
|
156
|
+
visibilityRef={visibilityRef}
|
|
150
157
|
showVisibility={!controller.tabsEnabled}
|
|
151
158
|
accessRequests={accessRequests}
|
|
152
159
|
/>
|
|
@@ -164,16 +171,23 @@ export function ShareDialog({
|
|
|
164
171
|
/>
|
|
165
172
|
</div>
|
|
166
173
|
</Tabs>
|
|
174
|
+
<ShareVisibilityConfirmDialog
|
|
175
|
+
transition={controller.visibility.transition}
|
|
176
|
+
detail={visibilityConfirmDetail}
|
|
177
|
+
returnFocusRef={visibilityRef}
|
|
178
|
+
/>
|
|
167
179
|
</DesignSystemDialog>
|
|
168
180
|
);
|
|
169
181
|
}
|
|
170
182
|
|
|
171
183
|
function LinkTab({
|
|
172
184
|
controller,
|
|
185
|
+
visibilityRef,
|
|
173
186
|
extras,
|
|
174
187
|
accessRequests,
|
|
175
188
|
}: {
|
|
176
189
|
controller: ShareDialogController;
|
|
190
|
+
visibilityRef: RefObject<HTMLElement | null>;
|
|
177
191
|
extras?: ReactNode;
|
|
178
192
|
accessRequests: ReactNode;
|
|
179
193
|
}) {
|
|
@@ -200,7 +214,10 @@ function LinkTab({
|
|
|
200
214
|
<Icon size={16} strokeWidth={1.75} />
|
|
201
215
|
</span>
|
|
202
216
|
<div className="min-w-0 flex-1">
|
|
203
|
-
<VisibilitySelect
|
|
217
|
+
<VisibilitySelect
|
|
218
|
+
controller={controller}
|
|
219
|
+
pickerRef={visibilityRef}
|
|
220
|
+
/>
|
|
204
221
|
<div className="sr-only text-xs text-muted-foreground">
|
|
205
222
|
{controller.visibility.description}
|
|
206
223
|
</div>
|
|
@@ -209,6 +226,7 @@ function LinkTab({
|
|
|
209
226
|
</div>
|
|
210
227
|
<InviteTab
|
|
211
228
|
controller={controller}
|
|
229
|
+
visibilityRef={visibilityRef}
|
|
212
230
|
showVisibility={false}
|
|
213
231
|
accessRequests={accessRequests}
|
|
214
232
|
/>
|
|
@@ -219,10 +237,12 @@ function LinkTab({
|
|
|
219
237
|
|
|
220
238
|
function InviteTab({
|
|
221
239
|
controller,
|
|
240
|
+
visibilityRef,
|
|
222
241
|
showVisibility,
|
|
223
242
|
accessRequests,
|
|
224
243
|
}: {
|
|
225
244
|
controller: ShareDialogController;
|
|
245
|
+
visibilityRef: RefObject<HTMLElement | null>;
|
|
226
246
|
showVisibility: boolean;
|
|
227
247
|
accessRequests: ReactNode;
|
|
228
248
|
}) {
|
|
@@ -242,7 +262,10 @@ function InviteTab({
|
|
|
242
262
|
<Icon size={16} strokeWidth={1.75} />
|
|
243
263
|
</span>
|
|
244
264
|
<div className="min-w-0 flex-1">
|
|
245
|
-
<VisibilitySelect
|
|
265
|
+
<VisibilitySelect
|
|
266
|
+
controller={controller}
|
|
267
|
+
pickerRef={visibilityRef}
|
|
268
|
+
/>
|
|
246
269
|
<div className="sr-only text-xs text-muted-foreground">
|
|
247
270
|
{controller.visibility.description}
|
|
248
271
|
</div>
|
|
@@ -487,22 +510,35 @@ function RoleSelect({ controller }: { controller: ShareDialogController }) {
|
|
|
487
510
|
|
|
488
511
|
function VisibilitySelect({
|
|
489
512
|
controller,
|
|
513
|
+
pickerRef,
|
|
490
514
|
}: {
|
|
491
515
|
controller: ShareDialogController;
|
|
516
|
+
pickerRef: RefObject<HTMLElement | null>;
|
|
492
517
|
}) {
|
|
493
518
|
return (
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
}
|
|
519
|
+
// React events from the picker's portaled list bubble to this wrapper
|
|
520
|
+
// after the option has selected. Enter may open a confirmation that takes
|
|
521
|
+
// focus; its keypress must not land on the button there.
|
|
522
|
+
<div
|
|
523
|
+
className="contents"
|
|
524
|
+
onKeyDown={(event) => {
|
|
525
|
+
if (event.key === "Enter") event.preventDefault();
|
|
502
526
|
}}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
527
|
+
>
|
|
528
|
+
<Picker
|
|
529
|
+
mode="select"
|
|
530
|
+
options={controller.visibility.options}
|
|
531
|
+
value={controller.visibility.value}
|
|
532
|
+
onChange={(value) => {
|
|
533
|
+
if (value === "private" || value === "org" || value === "public") {
|
|
534
|
+
controller.visibility.set(value);
|
|
535
|
+
}
|
|
536
|
+
}}
|
|
537
|
+
disabled={controller.visibility.disabled}
|
|
538
|
+
aria-label={controller.labels.generalAccess}
|
|
539
|
+
pickerRef={pickerRef}
|
|
540
|
+
className="-ms-1 w-auto"
|
|
541
|
+
/>
|
|
542
|
+
</div>
|
|
507
543
|
);
|
|
508
544
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import type { ShareVisibilityTransition } from "@agent-native/core/client/sharing/useShareVisibilityTransition";
|
|
4
|
+
import React, { act } from "react";
|
|
5
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
|
|
8
|
+
import { ShareVisibilityConfirmDialog } from "./ShareVisibilityConfirmDialog.js";
|
|
9
|
+
|
|
10
|
+
function makeTransition(
|
|
11
|
+
overrides: Partial<ShareVisibilityTransition> = {},
|
|
12
|
+
): ShareVisibilityTransition {
|
|
13
|
+
return {
|
|
14
|
+
pending: { from: "private", to: "org" },
|
|
15
|
+
organizationName: null,
|
|
16
|
+
submitting: false,
|
|
17
|
+
error: null,
|
|
18
|
+
request: vi.fn(),
|
|
19
|
+
confirm: vi.fn(async () => undefined),
|
|
20
|
+
cancel: vi.fn(),
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("ShareVisibilityConfirmDialog", () => {
|
|
26
|
+
let container: HTMLDivElement;
|
|
27
|
+
let root: Root;
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
31
|
+
container = document.createElement("div");
|
|
32
|
+
document.body.appendChild(container);
|
|
33
|
+
root = createRoot(container);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
act(() => root.unmount());
|
|
38
|
+
container.remove();
|
|
39
|
+
vi.unstubAllGlobals();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
async function render(
|
|
43
|
+
transition: ShareVisibilityTransition,
|
|
44
|
+
props: { detail?: React.ReactNode } = {},
|
|
45
|
+
) {
|
|
46
|
+
await act(async () => {
|
|
47
|
+
root.render(
|
|
48
|
+
<ShareVisibilityConfirmDialog transition={transition} {...props} />,
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const dialog = () =>
|
|
54
|
+
document.body.querySelector<HTMLElement>('[role="alertdialog"]');
|
|
55
|
+
const button = (label: string) =>
|
|
56
|
+
Array.from(
|
|
57
|
+
document.body.querySelectorAll<HTMLButtonElement>(
|
|
58
|
+
'[role="alertdialog"] button',
|
|
59
|
+
),
|
|
60
|
+
).find((candidate) => candidate.textContent?.trim() === label);
|
|
61
|
+
|
|
62
|
+
it("renders nothing until a broadening change is pending", async () => {
|
|
63
|
+
await render(makeTransition({ pending: null }));
|
|
64
|
+
|
|
65
|
+
expect(dialog()).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("names the organization only when it is known", async () => {
|
|
69
|
+
await render(makeTransition());
|
|
70
|
+
expect(dialog()?.textContent).toContain("Share with your organization?");
|
|
71
|
+
expect(dialog()?.textContent).toContain("Everyone in your organization");
|
|
72
|
+
|
|
73
|
+
await render(makeTransition({ organizationName: "Acme Co" }));
|
|
74
|
+
expect(dialog()?.textContent).toContain("Share with Acme Co?");
|
|
75
|
+
expect(dialog()?.textContent).toContain("Everyone in Acme Co");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("covers every kind of content and adds resource-specific detail", async () => {
|
|
79
|
+
await render(makeTransition(), { detail: "This also shares the chat." });
|
|
80
|
+
|
|
81
|
+
expect(dialog()?.textContent).toContain(
|
|
82
|
+
"including any messages, files, and other content",
|
|
83
|
+
);
|
|
84
|
+
expect(dialog()?.textContent).toContain("This also shares the chat.");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("opens with Cancel focused so Enter cannot widen access by accident", async () => {
|
|
88
|
+
await render(makeTransition());
|
|
89
|
+
|
|
90
|
+
expect(document.activeElement).toBe(button("Cancel"));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("confirms only from the confirm button and cancels from Cancel and Escape", async () => {
|
|
94
|
+
const transition = makeTransition();
|
|
95
|
+
await render(transition);
|
|
96
|
+
|
|
97
|
+
act(() => button("Cancel")?.click());
|
|
98
|
+
expect(transition.cancel).toHaveBeenCalledOnce();
|
|
99
|
+
expect(transition.confirm).not.toHaveBeenCalled();
|
|
100
|
+
|
|
101
|
+
act(() => {
|
|
102
|
+
dialog()?.dispatchEvent(
|
|
103
|
+
new KeyboardEvent("keydown", { key: "Escape", bubbles: true }),
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
expect(transition.cancel).toHaveBeenCalledTimes(2);
|
|
107
|
+
|
|
108
|
+
act(() => button("Share with organization")?.click());
|
|
109
|
+
expect(transition.confirm).toHaveBeenCalledOnce();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("shows progress, keeps the confirm button focusable, and blocks cancel", async () => {
|
|
113
|
+
await render(makeTransition({ submitting: true }));
|
|
114
|
+
|
|
115
|
+
const confirm = button("Sharing...");
|
|
116
|
+
expect(confirm?.getAttribute("aria-disabled")).toBe("true");
|
|
117
|
+
expect(confirm?.hasAttribute("disabled")).toBe(false);
|
|
118
|
+
expect(
|
|
119
|
+
(
|
|
120
|
+
document.body.querySelector(
|
|
121
|
+
'[role="alertdialog"] button',
|
|
122
|
+
) as HTMLButtonElement | null
|
|
123
|
+
)?.disabled,
|
|
124
|
+
).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("shows the failure in the dialog and offers a retry", async () => {
|
|
128
|
+
const transition = makeTransition({ error: "Could not update sharing" });
|
|
129
|
+
await render(transition);
|
|
130
|
+
|
|
131
|
+
expect(
|
|
132
|
+
document.body.querySelector('[role="alertdialog"] [role="alert"]')
|
|
133
|
+
?.textContent,
|
|
134
|
+
).toBe("Could not update sharing");
|
|
135
|
+
act(() => button("Retry")?.click());
|
|
136
|
+
expect(transition.confirm).toHaveBeenCalledOnce();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import type { ShareVisibilityTransition } from "@agent-native/core/client/sharing/useShareVisibilityTransition";
|
|
3
|
+
import {
|
|
4
|
+
AlertDialog,
|
|
5
|
+
AlertDialogCancel,
|
|
6
|
+
AlertDialogContent,
|
|
7
|
+
AlertDialogDescription,
|
|
8
|
+
AlertDialogFooter,
|
|
9
|
+
AlertDialogHeader,
|
|
10
|
+
AlertDialogTitle,
|
|
11
|
+
} from "@agent-native/toolkit/ui/alert-dialog";
|
|
12
|
+
import { Button } from "@agent-native/toolkit/ui/button";
|
|
13
|
+
import type { ReactNode, RefObject } from "react";
|
|
14
|
+
|
|
15
|
+
// ShareButton's nested overlays (the picker, member suggestions) sit at
|
|
16
|
+
// z-[100020] over the popover (z-[2000]) and ShareDialog (z-[2010]); the
|
|
17
|
+
// confirmation opens from the picker, so it goes just above them.
|
|
18
|
+
const CONFIRM_Z_INDEX = 100030;
|
|
19
|
+
|
|
20
|
+
export interface ShareVisibilityConfirmDialogProps {
|
|
21
|
+
transition: ShareVisibilityTransition;
|
|
22
|
+
/**
|
|
23
|
+
* What is specific to this resource, already translated by the caller, such
|
|
24
|
+
* as "Everyone will also see the conversation history." Shown under the
|
|
25
|
+
* generic text, which already covers messages, files and other content.
|
|
26
|
+
*/
|
|
27
|
+
detail?: ReactNode;
|
|
28
|
+
/** Gets focus back when the confirmation closes (the visibility control). */
|
|
29
|
+
returnFocusRef?: RefObject<HTMLElement | null>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Asks before a resource becomes visible to the whole organization. Pair it
|
|
34
|
+
* with the `transition` of a share controller; it renders nothing until a
|
|
35
|
+
* broadening change is waiting, and the change is saved only by Confirm.
|
|
36
|
+
*/
|
|
37
|
+
export function ShareVisibilityConfirmDialog({
|
|
38
|
+
transition,
|
|
39
|
+
detail,
|
|
40
|
+
returnFocusRef,
|
|
41
|
+
}: ShareVisibilityConfirmDialogProps) {
|
|
42
|
+
const t = useT();
|
|
43
|
+
const { pending, organizationName, submitting, error } = transition;
|
|
44
|
+
const named = organizationName !== null;
|
|
45
|
+
const values = { organization: organizationName ?? "" };
|
|
46
|
+
const title = named
|
|
47
|
+
? t("agentChat.share.confirmOrganizationTitleNamed", {
|
|
48
|
+
...values,
|
|
49
|
+
defaultValue: "Share with {{organization}}?",
|
|
50
|
+
})
|
|
51
|
+
: t("agentChat.share.confirmOrganizationTitle", {
|
|
52
|
+
defaultValue: "Share with your organization?",
|
|
53
|
+
});
|
|
54
|
+
const description = named
|
|
55
|
+
? t("agentChat.share.confirmOrganizationDescriptionNamed", {
|
|
56
|
+
...values,
|
|
57
|
+
defaultValue:
|
|
58
|
+
"Everyone in {{organization}}, including new members, will be able to open this and see everything in it, including any messages, files, and other content. You can change this later.",
|
|
59
|
+
})
|
|
60
|
+
: t("agentChat.share.confirmOrganizationDescription", {
|
|
61
|
+
defaultValue:
|
|
62
|
+
"Everyone in your organization, including new members, will be able to open this and see everything in it, including any messages, files, and other content. You can change this later.",
|
|
63
|
+
});
|
|
64
|
+
const actionLabel = submitting
|
|
65
|
+
? t("agentChat.share.confirmOrganizationSharing", {
|
|
66
|
+
defaultValue: "Sharing...",
|
|
67
|
+
})
|
|
68
|
+
: error
|
|
69
|
+
? t("agentChat.common.retry", { defaultValue: "Retry" })
|
|
70
|
+
: t("agentChat.share.confirmOrganizationAction", {
|
|
71
|
+
defaultValue: "Share with organization",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<AlertDialog
|
|
76
|
+
open={pending !== null}
|
|
77
|
+
onOpenChange={(open) => {
|
|
78
|
+
if (!open) transition.cancel();
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<AlertDialogContent
|
|
82
|
+
data-agent-native-share-overlay=""
|
|
83
|
+
style={{ zIndex: CONFIRM_Z_INDEX + 1 }}
|
|
84
|
+
overlayStyle={{ zIndex: CONFIRM_Z_INDEX }}
|
|
85
|
+
onCloseAutoFocus={(event) => {
|
|
86
|
+
const target = returnFocusRef?.current;
|
|
87
|
+
if (!target?.isConnected) return;
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
target.focus();
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<AlertDialogHeader>
|
|
93
|
+
<AlertDialogTitle>{title}</AlertDialogTitle>
|
|
94
|
+
<AlertDialogDescription>{description}</AlertDialogDescription>
|
|
95
|
+
{detail ? (
|
|
96
|
+
<div className="text-sm text-muted-foreground">{detail}</div>
|
|
97
|
+
) : null}
|
|
98
|
+
{error ? (
|
|
99
|
+
<div
|
|
100
|
+
role="alert"
|
|
101
|
+
className="rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-start text-xs text-destructive"
|
|
102
|
+
>
|
|
103
|
+
{error}
|
|
104
|
+
</div>
|
|
105
|
+
) : null}
|
|
106
|
+
</AlertDialogHeader>
|
|
107
|
+
<AlertDialogFooter>
|
|
108
|
+
<AlertDialogCancel disabled={submitting}>
|
|
109
|
+
{t("agentChat.common.cancel", { defaultValue: "Cancel" })}
|
|
110
|
+
</AlertDialogCancel>
|
|
111
|
+
<Button
|
|
112
|
+
type="button"
|
|
113
|
+
aria-disabled={submitting || undefined}
|
|
114
|
+
onClick={() => {
|
|
115
|
+
void transition.confirm();
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
{actionLabel}
|
|
119
|
+
</Button>
|
|
120
|
+
</AlertDialogFooter>
|
|
121
|
+
</AlertDialogContent>
|
|
122
|
+
</AlertDialog>
|
|
123
|
+
);
|
|
124
|
+
}
|