diff --git a/src/components/PackageManagerTabs.tsx b/src/components/PackageManagerTabs.tsx index a46ee5572..bca44b2d6 100644 --- a/src/components/PackageManagerTabs.tsx +++ b/src/components/PackageManagerTabs.tsx @@ -1,6 +1,7 @@ -import * as React from 'react' -import { useCurrentFramework } from './FrameworkSelect' -import { useLocalStorage } from '~/utils/useLocalStorage' +import { useLocalCurrentFramework } from './FrameworkSelect' +import { useCurrentUserQuery } from '~/hooks/useCurrentUser' +import { useParams } from '@tanstack/react-router' +import { create } from 'zustand' import { Tabs, type TabDefinition } from './Tabs' import { CodeBlock } from './CodeBlock' import type { Framework } from '~/libraries/types' @@ -8,9 +9,25 @@ import type { Framework } from '~/libraries/types' type PackageManager = 'bun' | 'npm' | 'pnpm' | 'yarn' type InstallMode = 'install' | 'dev-install' +// Use zustand for cross-component synchronization +// This ensures all PackageManagerTabs instances on the page stay in sync +const usePackageManagerStore = create<{ + packageManager: PackageManager + setPackageManager: (pm: PackageManager) => void +}>((set) => ({ + packageManager: + typeof document !== 'undefined' + ? (localStorage.getItem('packageManager') as PackageManager) || 'npm' + : 'npm', + setPackageManager: (pm: PackageManager) => { + localStorage.setItem('packageManager', pm) + set({ packageManager: pm }) + }, +})) + type PackageManagerTabsProps = { id: string - packagesByFramework: Record + packagesByFramework: Record mode: InstallMode frameworks: Framework[] } @@ -19,51 +36,74 @@ const PACKAGE_MANAGERS: PackageManager[] = ['npm', 'pnpm', 'yarn', 'bun'] function getInstallCommand( packageManager: PackageManager, - packages: string, + packages: string[], mode: InstallMode, -): string { +): string[] { + const commands: string[] = [] + if (mode === 'dev-install') { + for (const pkg of packages) { + switch (packageManager) { + case 'npm': + commands.push(`npm i -D ${pkg}`) + break + case 'pnpm': + commands.push(`pnpm add -D ${pkg}`) + break + case 'yarn': + commands.push(`yarn add -D ${pkg}`) + break + case 'bun': + commands.push(`bun add -d ${pkg}`) + break + } + } + return commands + } + + // install mode + for (const pkg of packages) { switch (packageManager) { case 'npm': - return `npm i -D ${packages}` + commands.push(`npm i ${pkg}`) + break case 'pnpm': - return `pnpm add -D ${packages}` + commands.push(`pnpm add ${pkg}`) + break case 'yarn': - return `yarn add -D ${packages}` + commands.push(`yarn add ${pkg}`) + break case 'bun': - return `bun add -d ${packages}` + commands.push(`bun add ${pkg}`) + break } } - - // install mode - switch (packageManager) { - case 'npm': - return `npm i ${packages}` - case 'pnpm': - return `pnpm add ${packages}` - case 'yarn': - return `yarn add ${packages}` - case 'bun': - return `bun add ${packages}` - } + return commands } export function PackageManagerTabs({ id, packagesByFramework, mode, - frameworks, }: PackageManagerTabsProps) { - const { framework: currentFramework } = useCurrentFramework(frameworks) - const [storedPackageManager, setStoredPackageManager] = - useLocalStorage('packageManager', PACKAGE_MANAGERS[0]) + const { packageManager: storedPackageManager, setPackageManager } = + usePackageManagerStore() + + const { framework: paramsFramework } = useParams({ strict: false }) + const localCurrentFramework = useLocalCurrentFramework() + const userQuery = useCurrentUserQuery() + const userFramework = userQuery.data?.lastUsedFramework + + const actualFramework = (paramsFramework || + userFramework || + localCurrentFramework.currentFramework || + 'react') as Framework - // Normalize framework key to lowercase for lookup - const normalizedFramework = currentFramework.toLowerCase() + const normalizedFramework = actualFramework.toLowerCase() const packages = packagesByFramework[normalizedFramework] // Hide component if current framework not in package list - if (!packages) { + if (!packages || packages.length === 0) { return null } @@ -81,10 +121,11 @@ export function PackageManagerTabs({ // Generate children (command blocks) for each package manager const children = PACKAGE_MANAGERS.map((pm) => { - const command = getInstallCommand(pm, packages, mode) + const commands = getInstallCommand(pm, packages, mode) + const commandText = commands.join('\n') return ( - {command} + {commandText} ) }) @@ -95,7 +136,7 @@ export function PackageManagerTabs({ tabs={tabs} children={children} activeSlug={selectedPackageManager} - onTabChange={(slug) => setStoredPackageManager(slug as PackageManager)} + onTabChange={(slug) => setPackageManager(slug as PackageManager)} /> ) } diff --git a/src/utils/markdown/plugins/transformTabsComponent.ts b/src/utils/markdown/plugins/transformTabsComponent.ts index 12f5afd1f..15eddc5a6 100644 --- a/src/utils/markdown/plugins/transformTabsComponent.ts +++ b/src/utils/markdown/plugins/transformTabsComponent.ts @@ -22,7 +22,7 @@ type TabExtraction = { } type PackageManagerExtraction = { - packagesByFramework: Record + packagesByFramework: Record mode: InstallMode } @@ -53,7 +53,7 @@ function normalizeFrameworkKey(key: string): string { */ function parseFrameworkLine(text: string): { framework: string - packages: string + packages: string[] } | null { const colonIndex = text.indexOf(':') if (colonIndex === -1) { @@ -61,9 +61,10 @@ function parseFrameworkLine(text: string): { } const framework = normalizeFrameworkKey(text.slice(0, colonIndex)) - const packages = text.slice(colonIndex + 1).trim() + const packagesStr = text.slice(colonIndex + 1).trim() + const packages = packagesStr.split(/\s+/).filter(Boolean) - if (!framework || !packages) { + if (!framework || packages.length === 0) { return null } @@ -75,7 +76,7 @@ function extractPackageManagerData( mode: InstallMode, ): PackageManagerExtraction | null { const children = node.children ?? [] - const packagesByFramework: Record = {} + const packagesByFramework: Record = {} // Recursively extract text from all children (including nested in

tags) function extractText(nodes: any[]): string { @@ -99,7 +100,12 @@ function extractPackageManagerData( const parsed = parseFrameworkLine(trimmed) if (parsed) { - packagesByFramework[parsed.framework] = parsed.packages + // Support multiple entries for same framework by concatenating packages + if (packagesByFramework[parsed.framework]) { + packagesByFramework[parsed.framework].push(...parsed.packages) + } else { + packagesByFramework[parsed.framework] = parsed.packages + } } }