diff --git a/packages/app/src/components/tools-menu.stories.tsx b/packages/app/src/components/tools-menu.stories.tsx new file mode 100644 index 00000000000..cae86e09460 --- /dev/null +++ b/packages/app/src/components/tools-menu.stories.tsx @@ -0,0 +1,136 @@ +import { For, type JSXElement } from "solid-js" +import { createStore } from "solid-js/store" +import { ToolsMenu, ToolsMenuIcon, type ToolsMenuProps, type ToolsMenuStatus, type ToolsMenuTab } from "./tools-menu" + +export default { + title: "Desktop V2/Tools Menu", + id: "desktop-v2-tools-menu", + component: ToolsMenu, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: `The desktop V2 Tools menu shown in the titlebar. Stories cover every MCP status, both LSP states, populated plugin rows, and empty states for all tabs.`, + }, + }, + }, +} + +const labels = { + menu: "Tools", + mcp: "MCP", + lsp: "LSP", + plugins: "Plugins", + mcpDescription: "Add MCP servers in opencode.json", + lspDescription: "LSPs are auto-detected as you open files", + pluginsDescription: "Add plugins in opencode.json", + disabled: "Disabled", + failed: "Failed", + reauthenticate: "Reauthenticate", +} + +const empty = { + mcp: "No MCPs configured yet", + lsp: "No active language servers yet", + plugins: "No plugins configured yet", +} + +const mcp = [ + { name: "ui-docs", status: "needs_auth" }, + { name: "browser-tools", status: "needs_client_registration" }, + { name: "brave-search", status: "disabled" }, + { name: "fastmail-mcp", status: "connected" }, + { name: "puppeteer", status: "failed" }, + { name: "figma", status: "connected" }, + { name: "playwright", status: "failed" }, +] satisfies Array<{ name: string; status: ToolsMenuStatus }> + +const lsp = [ + { name: "typescript", status: "connected" }, + { name: "eslint", status: "connected" }, + { name: "rust-analyzer", status: "error" }, +] satisfies ToolsMenuProps["lsp"] + +const plugins = ["opencode-devcontainers", "opencode-daytona", "~/.config/opencode/plugins/ping.ts"] + +const populated = { + labels, + empty, + mcp, + lsp, + plugins, +} satisfies Omit + +function InteractiveMenu() { + const [state, setState] = createStore({ mcp: mcp.map((item) => ({ ...item, pending: false })) }) + return ( + + ({ + ...item, + onToggle: () => { + if (item.status === "needs_auth" || item.status === "needs_client_registration") return + setState("mcp", index, "status", item.status === "connected" ? "disabled" : "connected") + }, + }))} + /> + + ) +} + +function MenuPreview(props: { warning?: boolean; children: JSXElement }) { + return ( +
+
+ + {props.warning ? "warning" : "normal"} +
+ {props.children} +
+ ) +} + +function Gallery(props: { empty?: boolean }) { + const tabs: ToolsMenuTab[] = ["mcp", "lsp", "plugins"] + return ( +
+ + {(tab) => ( + + + + )} + +
+ ) +} + +export const Interactive = { + render: () => , +} + +export const AllTabs = { + name: "All populated tabs", + render: () => , +} + +export const EmptyStates = { + name: "All empty tabs", + render: () => , +} + +export const McpPending = { + name: "MCP pending toggle", + render: () => ( + + ({ ...item, pending: item.name === "figma" }))} /> + + ), +}