@agent-native/toolkit 0.227.0 → 0.228.0-nightly-20261111004142

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 (144) hide show
  1. package/agent-native.eject.json +8 -0
  2. package/dist/app/agent-page/AgentTabFrame.d.ts +2 -1
  3. package/dist/app/agent-page/AgentTabFrame.d.ts.map +1 -1
  4. package/dist/app/agent-page/AgentTabFrame.js +3 -2
  5. package/dist/app/agent-page/AgentTabFrame.js.map +1 -1
  6. package/dist/app/chat/AgentPanel.d.ts +15 -15
  7. package/dist/app/chat/AgentPanel.d.ts.map +1 -1
  8. package/dist/app/chat/AgentPanel.js.map +1 -1
  9. package/dist/app/chat/AgentSidebar.d.ts +7 -1
  10. package/dist/app/chat/AgentSidebar.d.ts.map +1 -1
  11. package/dist/app/chat/AgentSidebar.js +47 -16
  12. package/dist/app/chat/AgentSidebar.js.map +1 -1
  13. package/dist/app/chat/chat/surface-types.d.ts +26 -26
  14. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  15. package/dist/app/extensions/EmbeddedExtension.d.ts.map +1 -1
  16. package/dist/app/extensions/EmbeddedExtension.js +6 -4
  17. package/dist/app/extensions/EmbeddedExtension.js.map +1 -1
  18. package/dist/app/extensions/ExtensionViewer.d.ts.map +1 -1
  19. package/dist/app/extensions/ExtensionViewer.js +6 -3
  20. package/dist/app/extensions/ExtensionViewer.js.map +1 -1
  21. package/dist/app/extensions/InlineExtensionFrame.d.ts.map +1 -1
  22. package/dist/app/extensions/InlineExtensionFrame.js +32 -28
  23. package/dist/app/extensions/InlineExtensionFrame.js.map +1 -1
  24. package/dist/app/extensions/use-extension-chat-submission.d.ts +14 -0
  25. package/dist/app/extensions/use-extension-chat-submission.d.ts.map +1 -0
  26. package/dist/app/extensions/use-extension-chat-submission.js +36 -0
  27. package/dist/app/extensions/use-extension-chat-submission.js.map +1 -0
  28. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  29. package/dist/app/i18n/catalogs/ar-SA.js +16 -0
  30. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  31. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  32. package/dist/app/i18n/catalogs/de-DE.js +12 -0
  33. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  34. package/dist/app/i18n/catalogs/en-US.d.ts +12 -0
  35. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  36. package/dist/app/i18n/catalogs/en-US.js +12 -0
  37. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  38. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  39. package/dist/app/i18n/catalogs/es-ES.js +13 -0
  40. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  41. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  42. package/dist/app/i18n/catalogs/fr-FR.js +13 -0
  43. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  44. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  45. package/dist/app/i18n/catalogs/hi-IN.js +12 -0
  46. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  47. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  48. package/dist/app/i18n/catalogs/ja-JP.js +12 -0
  49. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  50. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  51. package/dist/app/i18n/catalogs/ko-KR.js +12 -0
  52. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  53. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  54. package/dist/app/i18n/catalogs/pt-BR.js +13 -0
  55. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  56. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  57. package/dist/app/i18n/catalogs/zh-CN.js +12 -0
  58. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  59. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  60. package/dist/app/i18n/catalogs/zh-TW.js +12 -0
  61. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  62. package/dist/app/org/OrgSwitcher.d.ts.map +1 -1
  63. package/dist/app/org/OrgSwitcher.js +3 -2
  64. package/dist/app/org/OrgSwitcher.js.map +1 -1
  65. package/dist/app/resources/McpAccessSettings.d.ts +9 -2
  66. package/dist/app/resources/McpAccessSettings.d.ts.map +1 -1
  67. package/dist/app/resources/McpAccessSettings.js +264 -86
  68. package/dist/app/resources/McpAccessSettings.js.map +1 -1
  69. package/dist/app/settings/api-keys/ApiKeyDialogs.d.ts +6 -0
  70. package/dist/app/settings/api-keys/ApiKeyDialogs.d.ts.map +1 -1
  71. package/dist/app/settings/api-keys/ApiKeyDialogs.js +74 -2
  72. package/dist/app/settings/api-keys/ApiKeyDialogs.js.map +1 -1
  73. package/dist/app/settings/api-keys/ApiKeysSettingsPage.d.ts.map +1 -1
  74. package/dist/app/settings/api-keys/ApiKeysSettingsPage.js +23 -9
  75. package/dist/app/settings/api-keys/ApiKeysSettingsPage.js.map +1 -1
  76. package/dist/app/settings/api-keys/api-keys-client.d.ts +10 -1
  77. package/dist/app/settings/api-keys/api-keys-client.d.ts.map +1 -1
  78. package/dist/app/settings/api-keys/api-keys-client.js +19 -1
  79. package/dist/app/settings/api-keys/api-keys-client.js.map +1 -1
  80. package/dist/app/settings/shell/pages/mcp.d.ts +1 -1
  81. package/dist/app/settings/shell/pages/mcp.d.ts.map +1 -1
  82. package/dist/app/settings/shell/pages/mcp.js +26 -20
  83. package/dist/app/settings/shell/pages/mcp.js.map +1 -1
  84. package/dist/app/sharing/ShareButton.d.ts +2 -0
  85. package/dist/app/sharing/ShareButton.d.ts.map +1 -1
  86. package/dist/app/sharing/ShareButton.js +4 -3
  87. package/dist/app/sharing/ShareButton.js.map +1 -1
  88. package/dist/composer/TiptapComposer.js +3 -0
  89. package/dist/composer/TiptapComposer.js.map +1 -1
  90. package/dist/sharing/ShareControls.d.ts +2 -0
  91. package/dist/sharing/ShareControls.d.ts.map +1 -1
  92. package/dist/sharing/ShareControls.js +7 -2
  93. package/dist/sharing/ShareControls.js.map +1 -1
  94. package/dist/ui/index.d.ts +1 -0
  95. package/dist/ui/index.d.ts.map +1 -1
  96. package/dist/ui/index.js +1 -0
  97. package/dist/ui/index.js.map +1 -1
  98. package/dist/ui/numbered-steps.d.ts +14 -0
  99. package/dist/ui/numbered-steps.d.ts.map +1 -0
  100. package/dist/ui/numbered-steps.js +19 -0
  101. package/dist/ui/numbered-steps.js.map +1 -0
  102. package/package.json +4 -4
  103. package/src/app/agent-page/AgentTabFrame.tsx +21 -10
  104. package/src/app/chat/AgentPanel.header.spec.ts +4 -1
  105. package/src/app/chat/AgentPanel.tsx +19 -14
  106. package/src/app/chat/AgentSidebar.spec.tsx +167 -0
  107. package/src/app/chat/AgentSidebar.tsx +170 -101
  108. package/src/app/chat/chat/surface-types.ts +35 -31
  109. package/src/app/extensions/EmbeddedExtension.height.spec.tsx +45 -0
  110. package/src/app/extensions/EmbeddedExtension.tsx +6 -3
  111. package/src/app/extensions/ExtensionViewer.spec.tsx +28 -0
  112. package/src/app/extensions/ExtensionViewer.tsx +6 -2
  113. package/src/app/extensions/InlineExtensionFrame.spec.tsx +23 -0
  114. package/src/app/extensions/InlineExtensionFrame.tsx +23 -15
  115. package/src/app/extensions/use-extension-chat-submission.tsx +101 -0
  116. package/src/app/i18n/catalogs/ar-SA.ts +17 -0
  117. package/src/app/i18n/catalogs/de-DE.ts +15 -0
  118. package/src/app/i18n/catalogs/en-US.ts +13 -0
  119. package/src/app/i18n/catalogs/es-ES.ts +16 -0
  120. package/src/app/i18n/catalogs/fr-FR.ts +16 -0
  121. package/src/app/i18n/catalogs/hi-IN.ts +13 -0
  122. package/src/app/i18n/catalogs/ja-JP.ts +15 -0
  123. package/src/app/i18n/catalogs/ko-KR.ts +14 -0
  124. package/src/app/i18n/catalogs/pt-BR.ts +15 -0
  125. package/src/app/i18n/catalogs/zh-CN.ts +13 -0
  126. package/src/app/i18n/catalogs/zh-TW.ts +13 -0
  127. package/src/app/integrations/IntegrationsPanel.spec.tsx +1 -1
  128. package/src/app/org/OrgSwitcher.spec.tsx +43 -0
  129. package/src/app/org/OrgSwitcher.tsx +29 -24
  130. package/src/app/resources/McpAccessSettings.spec.tsx +354 -278
  131. package/src/app/resources/McpAccessSettings.tsx +1012 -323
  132. package/src/app/settings/api-keys/ApiKeyDialogs.tsx +148 -1
  133. package/src/app/settings/api-keys/ApiKeysSettingsPage.spec.tsx +190 -0
  134. package/src/app/settings/api-keys/ApiKeysSettingsPage.tsx +40 -1
  135. package/src/app/settings/api-keys/api-keys-client.spec.ts +49 -0
  136. package/src/app/settings/api-keys/api-keys-client.ts +32 -0
  137. package/src/app/settings/shell/pages/app-group-pages.spec.tsx +31 -21
  138. package/src/app/settings/shell/pages/mcp.tsx +50 -42
  139. package/src/app/sharing/ShareButton.tsx +10 -3
  140. package/src/composer/TiptapComposer.tsx +2 -0
  141. package/src/composer/TiptapComposer.voice-toolbar.spec.tsx +16 -0
  142. package/src/sharing/ShareControls.tsx +44 -24
  143. package/src/ui/index.ts +1 -0
  144. package/src/ui/numbered-steps.tsx +104 -0
@@ -18,7 +18,7 @@ vi.mock("@agent-native/core/client/i18n", async () => {
18
18
  }
19
19
  return text;
20
20
  };
21
- return { useT: () => t };
21
+ return { useT: () => t, useLocale: () => ({ locale: "en-US" }) };
22
22
  });
23
23
 
24
24
  const modelDefault = vi.hoisted(() => ({
@@ -85,15 +85,33 @@ vi.mock("../../../chat/index.js", () => ({
85
85
  </button>
86
86
  ),
87
87
  }));
88
- vi.mock("../../../resources/index.js", () => ({
88
+ vi.mock("../../../resources/McpAccessSettings.js", () => ({
89
+ McpHostMark: () => <span />,
89
90
  McpAccessSettings: ({
90
91
  appName,
91
92
  hideHeader,
93
+ helpText,
94
+ guideId,
95
+ onSelectGuide,
96
+ guideHref,
92
97
  }: {
93
98
  appName: string;
94
99
  hideHeader: boolean;
100
+ helpText?: string;
101
+ guideId?: string | null;
102
+ onSelectGuide: (id: string) => void;
103
+ guideHref: (id: string) => string;
95
104
  }) => (
96
- <div data-testid="mcp" data-app={appName} data-hide-header={hideHeader} />
105
+ <div
106
+ data-testid="mcp"
107
+ data-app={appName}
108
+ data-hide-header={hideHeader}
109
+ data-help={helpText}
110
+ data-guide={guideId}
111
+ data-cursor-href={guideHref("cursor")}
112
+ >
113
+ <button onClick={() => onSelectGuide("cursor")}>Choose Cursor</button>
114
+ </div>
97
115
  ),
98
116
  }));
99
117
  const changelogCard = vi.hoisted(() => ({
@@ -563,35 +581,27 @@ describe("app group pages", () => {
563
581
  });
564
582
 
565
583
  describe("MCP server", () => {
566
- it("names the app in the about line and links Integrations", async () => {
584
+ it("routes client choices through the Settings shell", async () => {
567
585
  await renderPage(McpServerSettingsPage);
568
- expect(container.textContent).toContain(
569
- "Connect Clips to Claude, ChatGPT, Cursor, or any AI app that supports MCP.",
570
- );
571
586
  const mcp = container.querySelector('[data-testid="mcp"]');
572
587
  expect(mcp?.getAttribute("data-app")).toBe("Clips");
573
588
  expect(mcp?.getAttribute("data-hide-header")).toBe("true");
574
- expect(container.textContent).toContain(
575
- "For tools the agent itself uses, see Integrations.",
589
+ expect(mcp?.getAttribute("data-cursor-href")).toBe(
590
+ "/settings/mcp/cursor",
576
591
  );
577
- const link = [...container.querySelectorAll("a")].find(
578
- (anchor) => anchor.textContent === "Integrations",
579
- );
580
- act(() => {
581
- link?.dispatchEvent(
582
- new MouseEvent("click", { bubbles: true, cancelable: true }),
583
- );
584
- });
585
- expect(navigate).toHaveBeenCalledWith("integrations");
592
+ expect(mcp?.getAttribute("data-guide")).toBeNull();
593
+ expect(header).toBeNull();
594
+ act(() => container.querySelector<HTMLButtonElement>("button")?.click());
595
+ expect(navigate).toHaveBeenCalledWith("mcp", "cursor");
586
596
  });
587
597
 
588
598
  it("uses the app's own about line when it passes one", async () => {
589
599
  await renderPage(McpServerSettingsPage, {
590
600
  input: { mcpAbout: "Find recordings from any MCP app." },
591
601
  });
592
- expect(container.textContent).toContain(
593
- "Find recordings from any MCP app.",
594
- );
602
+ expect(
603
+ container.querySelector("[data-testid=mcp]")?.getAttribute("data-help"),
604
+ ).toBe("Find recordings from any MCP app.");
595
605
  });
596
606
  });
597
607
 
@@ -1,54 +1,62 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
- import type { MouseEvent } from "react";
2
+ import { useLocale } from "@agent-native/core/client/i18n";
3
+ import {
4
+ getMcpConnectGuides,
5
+ resolveMcpConnectGuideId,
6
+ } from "@agent-native/core/shared/mcp-connect-content";
7
+ import { useEffect, useMemo } from "react";
3
8
 
4
- import { McpAccessSettings } from "../../../resources/index.js";
5
- import { useSettingsShell } from "../context.js";
9
+ import { BreadcrumbTitle } from "../../../integrations/IntegrationDetailParts.js";
10
+ import {
11
+ McpAccessSettings,
12
+ McpHostMark,
13
+ } from "../../../resources/McpAccessSettings.js";
14
+ import { useSettingsPageHeader, useSettingsShell } from "../context.js";
6
15
  import type { SettingsPageProps } from "../registry.js";
7
16
  import { settingsPageHref } from "../routing.js";
8
17
 
9
- // Splits the translated footnote around the link, whatever the word order.
10
- const LINK_TOKEN = "\u0000";
11
-
12
- export default function McpServerSettingsPage({ bridge }: SettingsPageProps) {
18
+ export default function McpServerSettingsPage({
19
+ bridge,
20
+ sub,
21
+ }: SettingsPageProps) {
13
22
  const t = useT();
23
+ const { locale } = useLocale();
14
24
  const { navigate } = useSettingsShell();
25
+ const guides = useMemo(() => getMcpConnectGuides(locale), [locale]);
26
+ const guide = sub
27
+ ? (guides.find((item) => item.id === sub) ??
28
+ guides.find((item) => item.id === "other"))
29
+ : null;
30
+ const header = useMemo(
31
+ () =>
32
+ guide
33
+ ? {
34
+ title: (
35
+ <BreadcrumbTitle
36
+ name={guide.label}
37
+ mark={<McpHostMark id={guide.id} className="size-4" />}
38
+ />
39
+ ),
40
+ }
41
+ : null,
42
+ [guide],
43
+ );
44
+ useSettingsPageHeader(header);
45
+ useEffect(() => {
46
+ const query = new URLSearchParams(window.location.search).get("guide");
47
+ if (!sub && query)
48
+ navigate("mcp", resolveMcpConnectGuideId(query), { replace: true });
49
+ }, [navigate, sub]);
15
50
  const appName =
16
51
  bridge.appName ?? t("agentChat.settingsShell.appFallbackName");
17
- const [beforeLink, afterLink = ""] = t(
18
- "agentChat.settingsShell.appGroup.mcpFootnote",
19
- { integrations: LINK_TOKEN },
20
- ).split(LINK_TOKEN);
21
- const openIntegrations = (event: MouseEvent<HTMLAnchorElement>) => {
22
- if (
23
- event.button !== 0 ||
24
- event.metaKey ||
25
- event.ctrlKey ||
26
- event.shiftKey ||
27
- event.altKey
28
- ) {
29
- return;
30
- }
31
- event.preventDefault();
32
- navigate("integrations");
33
- };
34
52
  return (
35
- <div className="flex flex-col gap-6">
36
- <p className="max-w-2xl text-sm leading-6 text-muted-foreground">
37
- {bridge.mcpAbout ??
38
- t("agentChat.settingsShell.appGroup.mcpAbout", { app: appName })}
39
- </p>
40
- <McpAccessSettings appName={appName} hideHeader />
41
- <p className="text-xs text-muted-foreground">
42
- {beforeLink}
43
- <a
44
- href={settingsPageHref("integrations")}
45
- onClick={openIntegrations}
46
- className="font-medium text-foreground underline-offset-4 hover:underline"
47
- >
48
- {t("agentChat.settingsShell.page.integrations")}
49
- </a>
50
- {afterLink}
51
- </p>
52
- </div>
53
+ <McpAccessSettings
54
+ appName={appName}
55
+ hideHeader
56
+ {...(bridge.mcpAbout != null ? { helpText: bridge.mcpAbout } : {})}
57
+ guideId={guide?.id ?? null}
58
+ onSelectGuide={(id) => navigate("mcp", id)}
59
+ guideHref={(id) => settingsPageHref("mcp", id)}
60
+ />
53
61
  );
54
62
  }
@@ -130,6 +130,8 @@ export interface ShareButtonProps {
130
130
  copiedLabel: string;
131
131
  onCopy: () => Promise<boolean | void> | boolean | void;
132
132
  size?: "default" | "sm";
133
+ /** `plain` draws Share as text, set apart from Copy link. */
134
+ appearance?: "joined" | "plain";
133
135
  className?: string;
134
136
  };
135
137
  /** Tooltip for the Copy link button: which link it copies. */
@@ -413,10 +415,11 @@ export function ShareButton(props: ShareButtonProps) {
413
415
  controller.handleShareTabChange,
414
416
  ]);
415
417
  const triggerLabel = t("agentChat.share.share", { defaultValue: "Share" });
418
+ const plainQuickCopy = props.quickCopy?.appearance === "plain";
416
419
  const trigger = (
417
420
  <ShareTrigger
418
421
  label={
419
- props.quickCopy ? (
422
+ props.quickCopy && !plainQuickCopy ? (
420
423
  <span className="flex items-center gap-2">
421
424
  <IconUserPlus aria-hidden="true" />
422
425
  <span>{props.triggerContent ?? triggerLabel}</span>
@@ -428,8 +431,10 @@ export function ShareButton(props: ShareButtonProps) {
428
431
  className={props.triggerClassName}
429
432
  aria-label={triggerLabel}
430
433
  title={triggerLabel}
431
- intent={props.quickCopy ? "primary" : undefined}
432
- emphasis={props.quickCopy ? "solid" : undefined}
434
+ intent={props.quickCopy && !plainQuickCopy ? "primary" : undefined}
435
+ emphasis={
436
+ plainQuickCopy ? "ghost" : props.quickCopy ? "solid" : undefined
437
+ }
433
438
  />
434
439
  );
435
440
  if (isSmallScreen) {
@@ -442,6 +447,7 @@ export function ShareButton(props: ShareButtonProps) {
442
447
  copiedLabel={props.quickCopy.copiedLabel}
443
448
  onCopy={props.quickCopy.onCopy}
444
449
  size={props.quickCopy.size}
450
+ appearance={props.quickCopy.appearance}
445
451
  className={props.quickCopy.className}
446
452
  />
447
453
  ) : (
@@ -482,6 +488,7 @@ export function ShareButton(props: ShareButtonProps) {
482
488
  copiedLabel={props.quickCopy.copiedLabel}
483
489
  onCopy={props.quickCopy.onCopy}
484
490
  size={props.quickCopy.size}
491
+ appearance={props.quickCopy.appearance}
485
492
  className={props.quickCopy.className}
486
493
  />
487
494
  </PopoverAnchor>
@@ -4824,6 +4824,8 @@ function TiptapComposerSurface({
4824
4824
  (e.metaKey || e.ctrlKey) &&
4825
4825
  !e.altKey;
4826
4826
  if (isToggleCombo) {
4827
+ // A page that has already used the key keeps it.
4828
+ if (e.defaultPrevented) return;
4827
4829
  e.preventDefault();
4828
4830
  if (voiceChatActiveRef.current) return;
4829
4831
  if (voice.state === "recording" || voice.state === "starting") {
@@ -559,6 +559,22 @@ describe("TiptapComposer voice chat", () => {
559
559
  expect(voice.start).not.toHaveBeenCalled();
560
560
  });
561
561
 
562
+ it("leaves the dictation key to a page that has already used it", async () => {
563
+ await act(async () => root.render(<Harness />));
564
+
565
+ const event = new KeyboardEvent("keydown", {
566
+ key: "m",
567
+ metaKey: true,
568
+ shiftKey: true,
569
+ cancelable: true,
570
+ });
571
+ event.preventDefault();
572
+ act(() => {
573
+ window.dispatchEvent(event);
574
+ });
575
+ expect(voice.start).not.toHaveBeenCalled();
576
+ });
577
+
562
578
  it("shows a failed session as an amber composer notice with a retry", async () => {
563
579
  activate("error");
564
580
  realtime.errorMessage = "The realtime voice connection failed.";
@@ -13,6 +13,8 @@ export interface JoinedShareControlProps {
13
13
  copiedLabel: string;
14
14
  onCopy: () => Promise<boolean | void> | boolean | void;
15
15
  size?: "default" | "sm";
16
+ /** `plain` sets the two apart, as quiet controls in a toolbar. */
17
+ appearance?: "joined" | "plain";
16
18
  disabled?: boolean;
17
19
  blocked?: boolean;
18
20
  className?: string;
@@ -29,6 +31,7 @@ export const JoinedShareControl = forwardRef<
29
31
  copiedLabel,
30
32
  onCopy,
31
33
  size = "default",
34
+ appearance = "joined",
32
35
  disabled,
33
36
  blocked,
34
37
  className,
@@ -55,6 +58,46 @@ export const JoinedShareControl = forwardRef<
55
58
  };
56
59
 
57
60
  const label = copied ? copiedLabel : copyLabel;
61
+ const plain = appearance === "plain";
62
+ const copyButton = (
63
+ // A click closes a tooltip, so the confirmation holds it open.
64
+ <Tooltip {...(copied ? { open: true } : {})}>
65
+ <TooltipTrigger asChild>
66
+ <Button
67
+ type="button"
68
+ size="sm"
69
+ variant={plain ? "ghost" : "default"}
70
+ className={cn(
71
+ "w-8 shrink-0 px-0 shadow-none",
72
+ plain && "text-muted-foreground hover:text-foreground",
73
+ blocked && "opacity-50",
74
+ )}
75
+ aria-label={label}
76
+ disabled={disabled}
77
+ data-blocked={blocked ? "" : undefined}
78
+ onClick={() => void copy()}
79
+ >
80
+ {copied ? (
81
+ <IconCheck aria-hidden="true" />
82
+ ) : (
83
+ <IconLink aria-hidden="true" />
84
+ )}
85
+ </Button>
86
+ </TooltipTrigger>
87
+ <TooltipContent side="bottom">{label}</TooltipContent>
88
+ </Tooltip>
89
+ );
90
+ if (plain) {
91
+ return (
92
+ <div
93
+ ref={ref}
94
+ className={cn("flex shrink-0 items-center gap-0.5", className)}
95
+ >
96
+ {trigger}
97
+ {copyButton}
98
+ </div>
99
+ );
100
+ }
58
101
  const segmentHeightClass =
59
102
  size === "sm"
60
103
  ? "[&>*:not([data-slot=button-group-separator]):not([data-button-group-ignore]):not(select[aria-hidden=true])]:!h-8"
@@ -69,30 +112,7 @@ export const JoinedShareControl = forwardRef<
69
112
  )}
70
113
  >
71
114
  {trigger}
72
- <Tooltip>
73
- <TooltipTrigger asChild>
74
- <Button
75
- type="button"
76
- size="sm"
77
- variant="default"
78
- className={cn(
79
- "w-8 shrink-0 px-0 shadow-none",
80
- blocked && "opacity-50",
81
- )}
82
- aria-label={label}
83
- disabled={disabled}
84
- data-blocked={blocked ? "" : undefined}
85
- onClick={() => void copy()}
86
- >
87
- {copied ? (
88
- <IconCheck aria-hidden="true" />
89
- ) : (
90
- <IconLink aria-hidden="true" />
91
- )}
92
- </Button>
93
- </TooltipTrigger>
94
- <TooltipContent side="bottom">{label}</TooltipContent>
95
- </Tooltip>
115
+ {copyButton}
96
116
  </ButtonGroup>
97
117
  );
98
118
  });
package/src/ui/index.ts CHANGED
@@ -32,6 +32,7 @@ export * from "./label.js";
32
32
  export * from "./logo.js";
33
33
  export * from "./menubar.js";
34
34
  export * from "./navigation-menu.js";
35
+ export * from "./numbered-steps.js";
35
36
  export * from "./pagination.js";
36
37
  export * from "./pointer-lock.js";
37
38
  export * from "./popover.js";
@@ -0,0 +1,104 @@
1
+ import { Slot } from "@radix-ui/react-slot";
2
+ import type { ComponentProps } from "react";
3
+
4
+ import { cn } from "../utils.js";
5
+
6
+ export function NumberedSteps({ className, ...props }: ComponentProps<"ol">) {
7
+ return (
8
+ <ol
9
+ data-slot="numbered-steps"
10
+ role="list"
11
+ className={cn("my-6 list-none p-0", className)}
12
+ {...props}
13
+ />
14
+ );
15
+ }
16
+
17
+ export function NumberedStep({
18
+ number,
19
+ last = false,
20
+ showConnector = true,
21
+ variant = "outline",
22
+ contentClassName,
23
+ className,
24
+ children,
25
+ ...props
26
+ }: ComponentProps<"li"> & {
27
+ number: number;
28
+ last?: boolean;
29
+ showConnector?: boolean;
30
+ variant?: "outline" | "secondary";
31
+ contentClassName?: string;
32
+ }) {
33
+ return (
34
+ <li
35
+ data-slot="numbered-step"
36
+ className={cn("flex items-stretch gap-4", className)}
37
+ {...props}
38
+ >
39
+ <div
40
+ data-slot="numbered-step-aside"
41
+ className={cn(
42
+ "flex shrink-0 flex-col items-center",
43
+ variant === "outline" && "pt-0.5",
44
+ )}
45
+ aria-hidden="true"
46
+ >
47
+ <span
48
+ data-slot="numbered-step-indicator"
49
+ className={cn(
50
+ "relative z-10 flex size-[26px] shrink-0 items-center justify-center rounded-full text-xs font-semibold leading-none",
51
+ variant === "secondary"
52
+ ? "bg-secondary text-secondary-foreground"
53
+ : "border-[1.5px] border-primary bg-background text-primary",
54
+ )}
55
+ >
56
+ {number}
57
+ </span>
58
+ {!last && showConnector && (
59
+ <span
60
+ data-slot="numbered-step-connector"
61
+ className="my-1.5 min-h-6 w-[1.5px] flex-1 bg-border"
62
+ />
63
+ )}
64
+ </div>
65
+ <div
66
+ data-slot="numbered-step-content"
67
+ className={cn("min-w-0 flex-1", !last && "pb-7", contentClassName)}
68
+ >
69
+ {children}
70
+ </div>
71
+ </li>
72
+ );
73
+ }
74
+
75
+ export function NumberedStepTitle({
76
+ asChild = false,
77
+ className,
78
+ ...props
79
+ }: ComponentProps<"h3"> & { asChild?: boolean }) {
80
+ const Comp = asChild ? Slot : "h3";
81
+ return (
82
+ <Comp
83
+ data-slot="numbered-step-title"
84
+ className={cn(
85
+ "mt-0.5 mb-2 text-sm font-semibold leading-normal text-foreground",
86
+ className,
87
+ )}
88
+ {...props}
89
+ />
90
+ );
91
+ }
92
+
93
+ export function NumberedStepBody({
94
+ className,
95
+ ...props
96
+ }: ComponentProps<"div">) {
97
+ return (
98
+ <div
99
+ data-slot="numbered-step-body"
100
+ className={cn("text-sm leading-6 text-muted-foreground", className)}
101
+ {...props}
102
+ />
103
+ );
104
+ }