@agent-native/toolkit 0.214.0 → 0.217.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 (203) hide show
  1. package/agent-native.eject.json +5 -0
  2. package/dist/app/agentkit/react/chat.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/chat.js +2 -2
  4. package/dist/app/agentkit/react/chat.js.map +1 -1
  5. package/dist/app/agentkit/react/components.d.ts +4 -1
  6. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  7. package/dist/app/agentkit/react/components.js +69 -15
  8. package/dist/app/agentkit/react/components.js.map +1 -1
  9. package/dist/app/agentkit/react/context.d.ts +7 -1
  10. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  11. package/dist/app/agentkit/react/context.js +5 -1
  12. package/dist/app/agentkit/react/context.js.map +1 -1
  13. package/dist/app/agentkit/react/index.d.ts +1 -0
  14. package/dist/app/agentkit/react/index.d.ts.map +1 -1
  15. package/dist/app/agentkit/react/index.js +1 -0
  16. package/dist/app/agentkit/react/index.js.map +1 -1
  17. package/dist/app/agentkit/react/root.d.ts +4 -1
  18. package/dist/app/agentkit/react/root.d.ts.map +1 -1
  19. package/dist/app/agentkit/react/root.js +2 -2
  20. package/dist/app/agentkit/react/root.js.map +1 -1
  21. package/dist/app/agentkit/react/styles.css +43 -6
  22. package/dist/app/agentkit/react/transcript-layout.d.ts +51 -0
  23. package/dist/app/agentkit/react/transcript-layout.d.ts.map +1 -0
  24. package/dist/app/agentkit/react/transcript-layout.js +34 -0
  25. package/dist/app/agentkit/react/transcript-layout.js.map +1 -0
  26. package/dist/app/auth/AuthPage.d.ts.map +1 -1
  27. package/dist/app/auth/AuthPage.js +33 -44
  28. package/dist/app/auth/AuthPage.js.map +1 -1
  29. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  30. package/dist/app/chat/AgentKitAssistantChat.js +3 -3
  31. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  32. package/dist/app/chat/AgentPanel.d.ts +2 -0
  33. package/dist/app/chat/AgentPanel.d.ts.map +1 -1
  34. package/dist/app/chat/AgentPanel.js +6 -2
  35. package/dist/app/chat/AgentPanel.js.map +1 -1
  36. package/dist/app/chat/chat/index.d.ts +2 -0
  37. package/dist/app/chat/chat/index.d.ts.map +1 -1
  38. package/dist/app/chat/chat/index.js.map +1 -1
  39. package/dist/app/chat/chat/surface-types.d.ts +7 -0
  40. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  41. package/dist/app/chat/chat/surface-types.js.map +1 -1
  42. package/dist/app/chat/chat/tool-call-display.d.ts +7 -2
  43. package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
  44. package/dist/app/chat/chat/tool-call-display.js +10 -9
  45. package/dist/app/chat/chat/tool-call-display.js.map +1 -1
  46. package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts.map +1 -1
  47. package/dist/app/chat/chat/widgets/DataChartRenderer.js +196 -26
  48. package/dist/app/chat/chat/widgets/DataChartRenderer.js.map +1 -1
  49. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts.map +1 -1
  50. package/dist/app/chat/chat/widgets/DataChartWidget.js +5 -1
  51. package/dist/app/chat/chat/widgets/DataChartWidget.js.map +1 -1
  52. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts +2 -2
  53. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts.map +1 -1
  54. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js +6 -4
  55. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js.map +1 -1
  56. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  57. package/dist/app/i18n/catalogs/ar-SA.js +1 -0
  58. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  59. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  60. package/dist/app/i18n/catalogs/de-DE.js +1 -0
  61. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  62. package/dist/app/i18n/catalogs/en-US.d.ts +1 -0
  63. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  64. package/dist/app/i18n/catalogs/en-US.js +1 -0
  65. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  66. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  67. package/dist/app/i18n/catalogs/es-ES.js +1 -0
  68. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  69. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  70. package/dist/app/i18n/catalogs/fr-FR.js +1 -0
  71. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  72. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  73. package/dist/app/i18n/catalogs/hi-IN.js +1 -0
  74. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  75. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  76. package/dist/app/i18n/catalogs/ja-JP.js +1 -0
  77. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  78. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  79. package/dist/app/i18n/catalogs/ko-KR.js +1 -0
  80. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  81. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  82. package/dist/app/i18n/catalogs/pt-BR.js +1 -0
  83. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  84. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  85. package/dist/app/i18n/catalogs/zh-CN.js +1 -0
  86. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  87. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  88. package/dist/app/i18n/catalogs/zh-TW.js +1 -0
  89. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  90. package/dist/app/onboarding/GettingStartedCard.d.ts +35 -0
  91. package/dist/app/onboarding/GettingStartedCard.d.ts.map +1 -0
  92. package/dist/app/onboarding/GettingStartedCard.js +38 -0
  93. package/dist/app/onboarding/GettingStartedCard.js.map +1 -0
  94. package/dist/app/onboarding/index.d.ts +1 -0
  95. package/dist/app/onboarding/index.d.ts.map +1 -1
  96. package/dist/app/onboarding/index.js +1 -0
  97. package/dist/app/onboarding/index.js.map +1 -1
  98. package/dist/app/providers/AppProviders.d.ts.map +1 -1
  99. package/dist/app/providers/AppProviders.js +3 -1
  100. package/dist/app/providers/AppProviders.js.map +1 -1
  101. package/dist/app/settings/AccountSettingsCard.d.ts.map +1 -1
  102. package/dist/app/settings/AccountSettingsCard.js +2 -1
  103. package/dist/app/settings/AccountSettingsCard.js.map +1 -1
  104. package/dist/app/settings/index.d.ts +1 -0
  105. package/dist/app/settings/index.d.ts.map +1 -1
  106. package/dist/app/settings/index.js +1 -0
  107. package/dist/app/settings/index.js.map +1 -1
  108. package/dist/app/settings/shell/SettingsShell.d.ts +40 -1
  109. package/dist/app/settings/shell/SettingsShell.d.ts.map +1 -1
  110. package/dist/app/settings/shell/SettingsShell.js +87 -20
  111. package/dist/app/settings/shell/SettingsShell.js.map +1 -1
  112. package/dist/app/settings/shell/bridge.d.ts +5 -3
  113. package/dist/app/settings/shell/bridge.d.ts.map +1 -1
  114. package/dist/app/settings/shell/bridge.js +8 -5
  115. package/dist/app/settings/shell/bridge.js.map +1 -1
  116. package/dist/app/settings/shell/index.d.ts +2 -2
  117. package/dist/app/settings/shell/index.d.ts.map +1 -1
  118. package/dist/app/settings/shell/index.js.map +1 -1
  119. package/dist/app/settings/shell/registry.d.ts +9 -1
  120. package/dist/app/settings/shell/registry.d.ts.map +1 -1
  121. package/dist/app/settings/shell/registry.js +16 -0
  122. package/dist/app/settings/shell/registry.js.map +1 -1
  123. package/dist/app/settings/shell/routing.d.ts +5 -4
  124. package/dist/app/settings/shell/routing.d.ts.map +1 -1
  125. package/dist/app/settings/shell/routing.js +38 -10
  126. package/dist/app/settings/shell/routing.js.map +1 -1
  127. package/dist/app/shared/CommandMenu.d.ts +86 -6
  128. package/dist/app/shared/CommandMenu.d.ts.map +1 -1
  129. package/dist/app/shared/CommandMenu.js +303 -41
  130. package/dist/app/shared/CommandMenu.js.map +1 -1
  131. package/dist/app/shared/ConsentBanner.d.ts +9 -0
  132. package/dist/app/shared/ConsentBanner.d.ts.map +1 -0
  133. package/dist/app/shared/ConsentBanner.js +35 -0
  134. package/dist/app/shared/ConsentBanner.js.map +1 -0
  135. package/dist/composer/ComposerContextBanner.d.ts +3 -1
  136. package/dist/composer/ComposerContextBanner.d.ts.map +1 -1
  137. package/dist/composer/ComposerContextBanner.js +2 -2
  138. package/dist/composer/ComposerContextBanner.js.map +1 -1
  139. package/dist/ui/sonner.d.ts +2 -2
  140. package/dist/ui/sonner.d.ts.map +1 -1
  141. package/dist/ui/sonner.js +2 -2
  142. package/dist/ui/sonner.js.map +1 -1
  143. package/package.json +3 -3
  144. package/src/app/agentkit/react/chat.tsx +2 -0
  145. package/src/app/agentkit/react/components.interaction.spec.tsx +107 -0
  146. package/src/app/agentkit/react/components.spec.tsx +28 -1
  147. package/src/app/agentkit/react/components.tsx +153 -17
  148. package/src/app/agentkit/react/context.tsx +12 -0
  149. package/src/app/agentkit/react/index.ts +1 -0
  150. package/src/app/agentkit/react/lifecycle.spec.tsx +186 -16
  151. package/src/app/agentkit/react/root.tsx +7 -0
  152. package/src/app/agentkit/react/styles.css +43 -6
  153. package/src/app/agentkit/react/transcript-layout.spec.ts +72 -0
  154. package/src/app/agentkit/react/transcript-layout.ts +97 -0
  155. package/src/app/auth/AuthPage.attribution.spec.tsx +6 -0
  156. package/src/app/auth/AuthPage.interaction.spec.tsx +74 -0
  157. package/src/app/auth/AuthPage.tsx +32 -39
  158. package/src/app/chat/AgentKitAssistantChat.spec.tsx +39 -0
  159. package/src/app/chat/AgentKitAssistantChat.tsx +5 -0
  160. package/src/app/chat/AgentPanel.page-header-share.spec.tsx +101 -0
  161. package/src/app/chat/AgentPanel.tsx +14 -2
  162. package/src/app/chat/chat/index.ts +2 -0
  163. package/src/app/chat/chat/surface-types.ts +10 -0
  164. package/src/app/chat/chat/tool-call-display.spec.tsx +83 -0
  165. package/src/app/chat/chat/tool-call-display.tsx +40 -8
  166. package/src/app/chat/chat/widgets/DataChartRenderer.spec.tsx +632 -0
  167. package/src/app/chat/chat/widgets/DataChartRenderer.tsx +308 -51
  168. package/src/app/chat/chat/widgets/DataChartWidget.spec.tsx +126 -23
  169. package/src/app/chat/chat/widgets/DataChartWidget.tsx +10 -0
  170. package/src/app/chat/chat/widgets/DataWidgetDisplayControls.tsx +26 -6
  171. package/src/app/i18n/catalogs/ar-SA.ts +1 -0
  172. package/src/app/i18n/catalogs/de-DE.ts +2 -0
  173. package/src/app/i18n/catalogs/en-US.ts +2 -0
  174. package/src/app/i18n/catalogs/es-ES.ts +2 -0
  175. package/src/app/i18n/catalogs/fr-FR.ts +2 -0
  176. package/src/app/i18n/catalogs/hi-IN.ts +1 -0
  177. package/src/app/i18n/catalogs/ja-JP.ts +2 -0
  178. package/src/app/i18n/catalogs/ko-KR.ts +1 -0
  179. package/src/app/i18n/catalogs/pt-BR.ts +2 -0
  180. package/src/app/i18n/catalogs/zh-CN.ts +1 -0
  181. package/src/app/i18n/catalogs/zh-TW.ts +1 -0
  182. package/src/app/onboarding/GettingStartedCard.tsx +203 -0
  183. package/src/app/onboarding/index.ts +1 -0
  184. package/src/app/providers/AppProviders.tsx +7 -1
  185. package/src/app/settings/AccountSettingsCard.spec.tsx +31 -0
  186. package/src/app/settings/AccountSettingsCard.tsx +8 -0
  187. package/src/app/settings/index.ts +1 -0
  188. package/src/app/settings/shell/SettingsShell.spec.tsx +472 -1
  189. package/src/app/settings/shell/SettingsShell.tsx +245 -31
  190. package/src/app/settings/shell/bridge.spec.ts +12 -0
  191. package/src/app/settings/shell/bridge.ts +17 -4
  192. package/src/app/settings/shell/index.ts +6 -1
  193. package/src/app/settings/shell/registry.ts +33 -1
  194. package/src/app/settings/shell/routing.spec.ts +92 -1
  195. package/src/app/settings/shell/routing.ts +52 -9
  196. package/src/app/shared/CommandMenu.spec.tsx +1662 -1
  197. package/src/app/shared/CommandMenu.tsx +763 -164
  198. package/src/app/shared/ConsentBanner.spec.tsx +141 -0
  199. package/src/app/shared/ConsentBanner.tsx +141 -0
  200. package/src/composer/ComposerContextBanner.spec.tsx +31 -5
  201. package/src/composer/ComposerContextBanner.tsx +7 -2
  202. package/src/ui/sonner.spec.ts +10 -0
  203. package/src/ui/sonner.tsx +2 -2
@@ -0,0 +1,141 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import { act } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { afterEach, describe, expect, it, vi } from "vitest";
6
+
7
+ const consentStateKey = Symbol.for("agent-native.client.consent");
8
+
9
+ let root: Root | null = null;
10
+ let container: HTMLElement | null = null;
11
+
12
+ async function renderBanner(region: "consent" | "none") {
13
+ vi.stubGlobal(
14
+ "fetch",
15
+ vi.fn(async () => new Response(JSON.stringify({ region }))),
16
+ );
17
+ vi.resetModules();
18
+ const consent = await import("@agent-native/core/client/consent");
19
+ const { ConsentBanner } = await import("./ConsentBanner.js");
20
+ container = document.createElement("div");
21
+ document.body.appendChild(container);
22
+ root = createRoot(container);
23
+ await act(async () => {
24
+ root!.render(<ConsentBanner />);
25
+ });
26
+ await act(async () => {
27
+ await consent.resolveConsentRegion();
28
+ });
29
+ return consent;
30
+ }
31
+
32
+ function banner() {
33
+ return document.querySelector("[data-agent-native-consent-banner]");
34
+ }
35
+
36
+ function button(name: string) {
37
+ const match = [...document.querySelectorAll("button")].find(
38
+ (el) => el.textContent === name,
39
+ );
40
+ if (!match) throw new Error(`no ${name} button`);
41
+ return match;
42
+ }
43
+
44
+ async function click(name: string) {
45
+ await act(async () => {
46
+ button(name).click();
47
+ });
48
+ }
49
+
50
+ describe("ConsentBanner", () => {
51
+ afterEach(() => {
52
+ act(() => root?.unmount());
53
+ container?.remove();
54
+ root = null;
55
+ delete (globalThis as any)[consentStateKey];
56
+ window.sessionStorage.clear();
57
+ for (const cookie of document.cookie.split(";")) {
58
+ const name = cookie.split("=")[0]?.trim();
59
+ if (name) document.cookie = `${name}=; path=/; max-age=0`;
60
+ }
61
+ vi.unstubAllGlobals();
62
+ });
63
+
64
+ it("shows nothing outside the consent region", async () => {
65
+ await renderBanner("none");
66
+ expect(banner()).toBeNull();
67
+ });
68
+
69
+ it("shows on a later page once the region is already known this session", async () => {
70
+ window.sessionStorage.setItem("an_consent_region", "consent");
71
+ const fetchMock = vi.fn(async () => new Response("{}"));
72
+ vi.stubGlobal("fetch", fetchMock);
73
+ vi.resetModules();
74
+ const { ConsentBanner } = await import("./ConsentBanner.js");
75
+ container = document.createElement("div");
76
+ document.body.appendChild(container);
77
+ root = createRoot(container);
78
+ await act(async () => {
79
+ root!.render(<ConsentBanner />);
80
+ });
81
+
82
+ expect(banner()).not.toBeNull();
83
+ expect(fetchMock).not.toHaveBeenCalled();
84
+ });
85
+
86
+ it("stores accept-all and closes", async () => {
87
+ const consent = await renderBanner("consent");
88
+ expect(banner()).not.toBeNull();
89
+
90
+ await click("Accept all");
91
+
92
+ expect(consent.getConsentChoice()).toEqual({
93
+ performance: true,
94
+ targeting: true,
95
+ });
96
+ expect(banner()).toBeNull();
97
+ });
98
+
99
+ it("stores reject-all and closes", async () => {
100
+ const consent = await renderBanner("consent");
101
+
102
+ await click("Reject all");
103
+
104
+ expect(consent.getConsentChoice()).toEqual({
105
+ performance: false,
106
+ targeting: false,
107
+ });
108
+ expect(banner()).toBeNull();
109
+ });
110
+
111
+ it("saves one category from the settings", async () => {
112
+ const consent = await renderBanner("consent");
113
+
114
+ await click("Manage settings");
115
+ await act(async () => {
116
+ (
117
+ document.getElementById("an-consent-performance") as HTMLElement
118
+ ).click();
119
+ });
120
+ await click("Save choices");
121
+
122
+ expect(consent.getConsentChoice()).toEqual({
123
+ performance: true,
124
+ targeting: false,
125
+ });
126
+ });
127
+
128
+ it("reopens from the settings link with the stored choice", async () => {
129
+ const consent = await renderBanner("consent");
130
+ await click("Accept all");
131
+
132
+ await act(async () => consent.openConsentSettings());
133
+ await click("Manage settings");
134
+
135
+ expect(
136
+ document
137
+ .getElementById("an-consent-targeting")
138
+ ?.getAttribute("aria-checked"),
139
+ ).toBe("true");
140
+ });
141
+ });
@@ -0,0 +1,141 @@
1
+ import {
2
+ getConsentChoice,
3
+ resolveConsentRegion,
4
+ setConsentChoice,
5
+ shouldShowConsentBanner,
6
+ subscribeConsent,
7
+ } from "@agent-native/core/client/consent";
8
+ import { useT } from "@agent-native/core/client/i18n";
9
+ import { Button } from "@agent-native/toolkit/ui/button";
10
+ import { Label } from "@agent-native/toolkit/ui/label";
11
+ import { Switch } from "@agent-native/toolkit/ui/switch";
12
+ import { useEffect, useState, useSyncExternalStore } from "react";
13
+
14
+ const DEFAULT_PRIVACY_POLICY_HREF = "https://www.agent-native.com/privacy";
15
+
16
+ /**
17
+ * The cookie banner for visitors in the consent region. Renders nothing
18
+ * elsewhere, and nothing once the visitor has chosen, until
19
+ * `openConsentSettings()` reopens it.
20
+ */
21
+ export function ConsentBanner({
22
+ privacyPolicyHref = DEFAULT_PRIVACY_POLICY_HREF,
23
+ }: {
24
+ privacyPolicyHref?: string;
25
+ }) {
26
+ const t = useT();
27
+ const open = useSyncExternalStore(
28
+ subscribeConsent,
29
+ shouldShowConsentBanner,
30
+ () => false,
31
+ );
32
+ const [managing, setManaging] = useState(false);
33
+ const [performance, setPerformance] = useState(false);
34
+ const [targeting, setTargeting] = useState(false);
35
+
36
+ useEffect(() => {
37
+ void resolveConsentRegion();
38
+ }, []);
39
+
40
+ useEffect(() => {
41
+ if (!open) return;
42
+ const stored = getConsentChoice();
43
+ setPerformance(stored?.performance ?? false);
44
+ setTargeting(stored?.targeting ?? false);
45
+ setManaging(false);
46
+ }, [open]);
47
+
48
+ if (!open) return null;
49
+
50
+ return (
51
+ <section
52
+ role="region"
53
+ aria-label={t("consentBanner.label")}
54
+ data-agent-native-consent-banner
55
+ className="fixed bottom-4 left-4 z-[100] flex w-[min(420px,calc(100vw-2rem))] flex-col gap-3 rounded-lg border bg-popover p-4 text-sm text-popover-foreground shadow-lg"
56
+ >
57
+ <p className="m-0 leading-relaxed">
58
+ {t("consentBanner.message")}{" "}
59
+ <a
60
+ href={privacyPolicyHref}
61
+ target="_blank"
62
+ rel="noreferrer"
63
+ className="underline underline-offset-2"
64
+ >
65
+ {t("consentBanner.privacyPolicy")}
66
+ </a>
67
+ </p>
68
+ {managing ? (
69
+ <div className="flex flex-col gap-2">
70
+ <div className="flex items-center justify-between gap-3">
71
+ <Label htmlFor="an-consent-necessary">
72
+ {t("consentBanner.necessary")}
73
+ </Label>
74
+ <Switch id="an-consent-necessary" checked disabled />
75
+ </div>
76
+ <div className="flex items-center justify-between gap-3">
77
+ <Label htmlFor="an-consent-performance">
78
+ {t("consentBanner.performance")}
79
+ </Label>
80
+ <Switch
81
+ id="an-consent-performance"
82
+ checked={performance}
83
+ onCheckedChange={setPerformance}
84
+ />
85
+ </div>
86
+ <div className="flex items-center justify-between gap-3">
87
+ <Label htmlFor="an-consent-targeting">
88
+ {t("consentBanner.targeting")}
89
+ </Label>
90
+ <Switch
91
+ id="an-consent-targeting"
92
+ checked={targeting}
93
+ onCheckedChange={setTargeting}
94
+ />
95
+ </div>
96
+ </div>
97
+ ) : null}
98
+ <div className="flex flex-wrap gap-2">
99
+ <Button
100
+ variant="outline"
101
+ size="sm"
102
+ className="flex-1"
103
+ onClick={() =>
104
+ setConsentChoice({ performance: false, targeting: false })
105
+ }
106
+ >
107
+ {t("consentBanner.rejectAll")}
108
+ </Button>
109
+ <Button
110
+ variant="outline"
111
+ size="sm"
112
+ className="flex-1"
113
+ onClick={() =>
114
+ setConsentChoice({ performance: true, targeting: true })
115
+ }
116
+ >
117
+ {t("consentBanner.acceptAll")}
118
+ </Button>
119
+ {managing ? (
120
+ <Button
121
+ variant="outline"
122
+ size="sm"
123
+ className="flex-1"
124
+ onClick={() => setConsentChoice({ performance, targeting })}
125
+ >
126
+ {t("consentBanner.save")}
127
+ </Button>
128
+ ) : (
129
+ <Button
130
+ variant="ghost"
131
+ size="sm"
132
+ className="flex-1"
133
+ onClick={() => setManaging(true)}
134
+ >
135
+ {t("consentBanner.manage")}
136
+ </Button>
137
+ )}
138
+ </div>
139
+ </section>
140
+ );
141
+ }
@@ -55,11 +55,12 @@ describe("ComposerContextBanner", () => {
55
55
  "Improve answers by adding data definitions.",
56
56
  );
57
57
 
58
- await act(async () =>
59
- Array.from(container.querySelectorAll("button"))
60
- .find((button) => button.textContent?.includes("Add context"))!
61
- .click(),
62
- );
58
+ const addContextButton = Array.from(
59
+ container.querySelectorAll<HTMLButtonElement>("button"),
60
+ ).find((button) => button.textContent?.includes("Add context"))!;
61
+ expect(addContextButton.classList).toContain("px-1.5");
62
+
63
+ await act(async () => addContextButton.click());
63
64
  await act(async () =>
64
65
  container
65
66
  .querySelector<HTMLButtonElement>(
@@ -71,4 +72,29 @@ describe("ComposerContextBanner", () => {
71
72
  expect(onAddContext).toHaveBeenCalledOnce();
72
73
  expect(onDismiss).toHaveBeenCalledOnce();
73
74
  });
75
+
76
+ it("renders a host-provided decorative add-context icon", async () => {
77
+ await act(async () =>
78
+ root.render(
79
+ <ComposerContextBanner
80
+ ariaLabel="Context guidance"
81
+ addContextLabel="Add context"
82
+ addContextIcon={<svg data-testid="custom-action-icon" />}
83
+ onAddContext={vi.fn()}
84
+ >
85
+ Improve answers by adding data definitions.
86
+ </ComposerContextBanner>,
87
+ ),
88
+ );
89
+
90
+ const button = container.querySelector<HTMLButtonElement>("button")!;
91
+ const actionIcon = container.querySelector<SVGSVGElement>(
92
+ '[data-testid="custom-action-icon"]',
93
+ )!;
94
+
95
+ expect(button.textContent).toBe("Add context");
96
+ expect(button.classList).toContain("px-1.5");
97
+ expect(Array.from(button.querySelectorAll("svg"))).toEqual([actionIcon]);
98
+ expect(actionIcon.parentElement?.getAttribute("aria-hidden")).toBe("true");
99
+ });
74
100
  });
@@ -11,6 +11,8 @@ interface ComposerContextBannerBaseProps {
11
11
  /** Localized label for the action that opens or adds context. */
12
12
  addContextLabel: string;
13
13
  onAddContext: () => void;
14
+ /** Optional decorative icon displayed after the add-context label. */
15
+ addContextIcon?: ReactNode;
14
16
  icon?: ReactNode;
15
17
  className?: string;
16
18
  }
@@ -27,6 +29,7 @@ export function ComposerContextBanner({
27
29
  children,
28
30
  addContextLabel,
29
31
  onAddContext,
32
+ addContextIcon,
30
33
  dismissLabel,
31
34
  onDismiss,
32
35
  icon,
@@ -46,14 +49,16 @@ export function ComposerContextBanner({
46
49
  </span>
47
50
  <span className="min-w-0 flex-1">{children}</span>
48
51
  <Button
49
- className="shrink-0"
52
+ className="shrink-0 px-1.5"
50
53
  onClick={onAddContext}
51
54
  size="xs"
52
55
  type="button"
53
56
  variant="outline"
54
57
  >
55
58
  {addContextLabel}
56
- <IconPlus aria-hidden="true" />
59
+ <span aria-hidden="true">
60
+ {addContextIcon ?? <IconPlus aria-hidden="true" />}
61
+ </span>
57
62
  </Button>
58
63
  {onDismiss ? (
59
64
  <Button
@@ -0,0 +1,10 @@
1
+ import { toast } from "sonner";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { sonnerToast } from "./sonner.js";
5
+
6
+ describe("shared Sonner API", () => {
7
+ it("re-exports the toast helper", () => {
8
+ expect(sonnerToast).toBe(toast);
9
+ });
10
+ });
package/src/ui/sonner.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useTheme } from "next-themes";
2
- import { Toaster as Sonner } from "sonner";
2
+ import { toast as sonnerToast, Toaster as Sonner } from "sonner";
3
3
 
4
4
  import { cn } from "../utils.js";
5
5
 
@@ -55,4 +55,4 @@ const Toaster = ({ className, toastOptions, ...props }: ToasterProps) => {
55
55
  );
56
56
  };
57
57
 
58
- export { Toaster };
58
+ export { Toaster, sonnerToast };