diff --git a/packages/docs-app/src/blueprint.mdx b/packages/docs-app/src/blueprint.mdx index 175e262505c..2a5b755e18f 100644 --- a/packages/docs-app/src/blueprint.mdx +++ b/packages/docs-app/src/blueprint.mdx @@ -25,9 +25,7 @@ Check out the [migration guides to upgrade from v5.x →](https://github.com/ home to over 40 UI components. Install it with your Node.js package manager of choice: -```sh copy -pnpm add @blueprintjs/core react react-dom -``` +@reactDocs InstallSnippet Additional UI components and APIs are available in: diff --git a/packages/docs-app/src/components/installSnippet.tsx b/packages/docs-app/src/components/installSnippet.tsx new file mode 100644 index 00000000000..07abc70e4e1 --- /dev/null +++ b/packages/docs-app/src/components/installSnippet.tsx @@ -0,0 +1,37 @@ +/* + * Copyright 2026 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CodeToggle, type CodeToggleTab } from "@blueprintjs/docs-theme"; + +const PACKAGE_MANAGER_PREFERENCE_KEY = "bp-docs-pm-preference"; + +const INSTALL_TARGET = "@blueprintjs/core react react-dom"; + +// Append a new entry here to add another package manager (e.g. bun). +const TABS: readonly CodeToggleTab[] = [ + { code: `npm install ${INSTALL_TARGET}`, id: "npm", label: "npm" }, + { code: `yarn add ${INSTALL_TARGET}`, id: "yarn", label: "yarn" }, + { code: `pnpm add ${INSTALL_TARGET}`, id: "pnpm", label: "pnpm" }, +]; + +/** + * Tabbed install snippet showing the same package across the supported + * package managers. The user's choice is persisted across pages so the docs + * remember which client the reader uses. + */ +export const InstallSnippet: React.FC = () => ( + +); diff --git a/packages/docs-app/src/getting-started.mdx b/packages/docs-app/src/getting-started.mdx index 8bed0a2a58b..b360c76ce22 100644 --- a/packages/docs-app/src/getting-started.mdx +++ b/packages/docs-app/src/getting-started.mdx @@ -16,9 +16,7 @@ The JavaScript components are stable and their APIs adhere to [semantic versioni 1. Install the core package and its peer dependencies with an NPM client like `npm` or `pnpm`, pulling in all relevant dependencies: - ```sh copy - pnpm add @blueprintjs/core react react-dom - ``` + @reactDocs InstallSnippet 2. After installation, you'll be able to import the React components in your application: diff --git a/packages/docs-app/src/tags/reactDocs.ts b/packages/docs-app/src/tags/reactDocs.ts index 72d8d71a1c8..8a827c3d146 100644 --- a/packages/docs-app/src/tags/reactDocs.ts +++ b/packages/docs-app/src/tags/reactDocs.ts @@ -19,4 +19,5 @@ export * from "../components/colorPalettes"; export * from "../components/colorSchemes"; export * from "../components/icons"; +export * from "../components/installSnippet"; export * from "../components/welcome"; diff --git a/packages/docs-theme/src/components/codeToggle.tsx b/packages/docs-theme/src/components/codeToggle.tsx new file mode 100644 index 00000000000..291644d55e7 --- /dev/null +++ b/packages/docs-theme/src/components/codeToggle.tsx @@ -0,0 +1,95 @@ +/* ! + * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. + */ + +import { useCallback, useEffect, useMemo, useState } from "react"; + +import { Code, Pre, Tab, type TabId, Tabs } from "@blueprintjs/core"; + +export interface CodeToggleTab { + /** Stable identifier used for the tab and the persisted preference. */ + id: string; + /** Visible tab label. */ + label: string; + /** Code snippet rendered inside `
`. */
+    code: string;
+}
+
+export interface CodeToggleProps {
+    /**
+     * Tabs to render, in display order. The first tab is selected by default
+     * when no persisted preference is found.
+     */
+    tabs: readonly CodeToggleTab[];
+
+    /**
+     * Required `id` for the underlying `` element. Must be unique within
+     * the rendered page.
+     */
+    id: string;
+
+    /**
+     * If provided, the user's selection is persisted in `localStorage` under
+     * this key and shared across all `CodeToggle` instances using the same key.
+     * Useful for global preferences like the package manager choice.
+     */
+    storageKey?: string;
+}
+
+function readStoredPreference(storageKey: string | undefined, validIds: readonly string[]): string | undefined {
+    if (storageKey === undefined || typeof window === "undefined") {
+        return undefined;
+    }
+    const stored = window.localStorage.getItem(storageKey);
+    return stored != null && validIds.includes(stored) ? stored : undefined;
+}
+
+/**
+ * Generic tabbed code-snippet toggle. Each tab renders a `
` with
+ * its corresponding snippet. When `storageKey` is provided, the selection is
+ * persisted in `localStorage` so the same choice carries across pages.
+ */
+export const CodeToggle: React.FC = ({ tabs, id, storageKey }) => {
+    const validIds = useMemo(() => tabs.map(t => t.id), [tabs]);
+    const defaultId = tabs[0]?.id ?? "";
+    const [selected, setSelected] = useState(defaultId);
+
+    // Hydrate from localStorage after mount to avoid SSR / first-render mismatches.
+    useEffect(() => {
+        const stored = readStoredPreference(storageKey, validIds);
+        if (stored !== undefined) {
+            setSelected(stored);
+        }
+    }, [storageKey, validIds]);
+
+    const handleChange = useCallback(
+        (newTabId: TabId) => {
+            const next = String(newTabId);
+            if (!validIds.includes(next)) {
+                return;
+            }
+            setSelected(next);
+            if (storageKey !== undefined && typeof window !== "undefined") {
+                window.localStorage.setItem(storageKey, next);
+            }
+        },
+        [storageKey, validIds],
+    );
+
+    return (
+        
+            {tabs.map(tab => (
+                
+                            {tab.code}
+                        
+ } + /> + ))} +
+ ); +}; diff --git a/packages/docs-theme/src/index.ts b/packages/docs-theme/src/index.ts index 2b2af5a7c01..9544d7b944c 100644 --- a/packages/docs-theme/src/index.ts +++ b/packages/docs-theme/src/index.ts @@ -18,6 +18,7 @@ export * from "./components/banner"; export * from "./components/documentation"; export * from "./components/example"; export * from "./components/codeExample"; +export * from "./components/codeToggle"; export * from "./components/navMenuItem"; export * from "./components/navButton"; export * from "./common";