Skip to content

Commit 83e1eff

Browse files
PackageManagerTabs to support multiple packages per framework (#628)
* feat: update PackageManagerTabs to support multiple packages per framework * feat: implement zustand for package manager state synchronization
1 parent 2896481 commit 83e1eff

File tree

2 files changed

+85
-38
lines changed

2 files changed

+85
-38
lines changed
Lines changed: 73 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,33 @@
1-
import * as React from 'react'
2-
import { useCurrentFramework } from './FrameworkSelect'
3-
import { useLocalStorage } from '~/utils/useLocalStorage'
1+
import { useLocalCurrentFramework } from './FrameworkSelect'
2+
import { useCurrentUserQuery } from '~/hooks/useCurrentUser'
3+
import { useParams } from '@tanstack/react-router'
4+
import { create } from 'zustand'
45
import { Tabs, type TabDefinition } from './Tabs'
56
import { CodeBlock } from './CodeBlock'
67
import type { Framework } from '~/libraries/types'
78

89
type PackageManager = 'bun' | 'npm' | 'pnpm' | 'yarn'
910
type InstallMode = 'install' | 'dev-install'
1011

12+
// Use zustand for cross-component synchronization
13+
// This ensures all PackageManagerTabs instances on the page stay in sync
14+
const usePackageManagerStore = create<{
15+
packageManager: PackageManager
16+
setPackageManager: (pm: PackageManager) => void
17+
}>((set) => ({
18+
packageManager:
19+
typeof document !== 'undefined'
20+
? (localStorage.getItem('packageManager') as PackageManager) || 'npm'
21+
: 'npm',
22+
setPackageManager: (pm: PackageManager) => {
23+
localStorage.setItem('packageManager', pm)
24+
set({ packageManager: pm })
25+
},
26+
}))
27+
1128
type PackageManagerTabsProps = {
1229
id: string
13-
packagesByFramework: Record<string, string>
30+
packagesByFramework: Record<string, string[]>
1431
mode: InstallMode
1532
frameworks: Framework[]
1633
}
@@ -19,51 +36,74 @@ const PACKAGE_MANAGERS: PackageManager[] = ['npm', 'pnpm', 'yarn', 'bun']
1936

2037
function getInstallCommand(
2138
packageManager: PackageManager,
22-
packages: string,
39+
packages: string[],
2340
mode: InstallMode,
24-
): string {
41+
): string[] {
42+
const commands: string[] = []
43+
2544
if (mode === 'dev-install') {
45+
for (const pkg of packages) {
46+
switch (packageManager) {
47+
case 'npm':
48+
commands.push(`npm i -D ${pkg}`)
49+
break
50+
case 'pnpm':
51+
commands.push(`pnpm add -D ${pkg}`)
52+
break
53+
case 'yarn':
54+
commands.push(`yarn add -D ${pkg}`)
55+
break
56+
case 'bun':
57+
commands.push(`bun add -d ${pkg}`)
58+
break
59+
}
60+
}
61+
return commands
62+
}
63+
64+
// install mode
65+
for (const pkg of packages) {
2666
switch (packageManager) {
2767
case 'npm':
28-
return `npm i -D ${packages}`
68+
commands.push(`npm i ${pkg}`)
69+
break
2970
case 'pnpm':
30-
return `pnpm add -D ${packages}`
71+
commands.push(`pnpm add ${pkg}`)
72+
break
3173
case 'yarn':
32-
return `yarn add -D ${packages}`
74+
commands.push(`yarn add ${pkg}`)
75+
break
3376
case 'bun':
34-
return `bun add -d ${packages}`
77+
commands.push(`bun add ${pkg}`)
78+
break
3579
}
3680
}
37-
38-
// install mode
39-
switch (packageManager) {
40-
case 'npm':
41-
return `npm i ${packages}`
42-
case 'pnpm':
43-
return `pnpm add ${packages}`
44-
case 'yarn':
45-
return `yarn add ${packages}`
46-
case 'bun':
47-
return `bun add ${packages}`
48-
}
81+
return commands
4982
}
5083

5184
export function PackageManagerTabs({
5285
id,
5386
packagesByFramework,
5487
mode,
55-
frameworks,
5688
}: PackageManagerTabsProps) {
57-
const { framework: currentFramework } = useCurrentFramework(frameworks)
58-
const [storedPackageManager, setStoredPackageManager] =
59-
useLocalStorage<PackageManager>('packageManager', PACKAGE_MANAGERS[0])
89+
const { packageManager: storedPackageManager, setPackageManager } =
90+
usePackageManagerStore()
91+
92+
const { framework: paramsFramework } = useParams({ strict: false })
93+
const localCurrentFramework = useLocalCurrentFramework()
94+
const userQuery = useCurrentUserQuery()
95+
const userFramework = userQuery.data?.lastUsedFramework
96+
97+
const actualFramework = (paramsFramework ||
98+
userFramework ||
99+
localCurrentFramework.currentFramework ||
100+
'react') as Framework
60101

61-
// Normalize framework key to lowercase for lookup
62-
const normalizedFramework = currentFramework.toLowerCase()
102+
const normalizedFramework = actualFramework.toLowerCase()
63103
const packages = packagesByFramework[normalizedFramework]
64104

65105
// Hide component if current framework not in package list
66-
if (!packages) {
106+
if (!packages || packages.length === 0) {
67107
return null
68108
}
69109

@@ -81,10 +121,11 @@ export function PackageManagerTabs({
81121

82122
// Generate children (command blocks) for each package manager
83123
const children = PACKAGE_MANAGERS.map((pm) => {
84-
const command = getInstallCommand(pm, packages, mode)
124+
const commands = getInstallCommand(pm, packages, mode)
125+
const commandText = commands.join('\n')
85126
return (
86127
<CodeBlock key={pm}>
87-
<code className="language-bash">{command}</code>
128+
<code className="language-bash">{commandText}</code>
88129
</CodeBlock>
89130
)
90131
})
@@ -95,7 +136,7 @@ export function PackageManagerTabs({
95136
tabs={tabs}
96137
children={children}
97138
activeSlug={selectedPackageManager}
98-
onTabChange={(slug) => setStoredPackageManager(slug as PackageManager)}
139+
onTabChange={(slug) => setPackageManager(slug as PackageManager)}
99140
/>
100141
)
101142
}

src/utils/markdown/plugins/transformTabsComponent.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type TabExtraction = {
2222
}
2323

2424
type PackageManagerExtraction = {
25-
packagesByFramework: Record<string, string>
25+
packagesByFramework: Record<string, string[]>
2626
mode: InstallMode
2727
}
2828

@@ -53,17 +53,18 @@ function normalizeFrameworkKey(key: string): string {
5353
*/
5454
function parseFrameworkLine(text: string): {
5555
framework: string
56-
packages: string
56+
packages: string[]
5757
} | null {
5858
const colonIndex = text.indexOf(':')
5959
if (colonIndex === -1) {
6060
return null
6161
}
6262

6363
const framework = normalizeFrameworkKey(text.slice(0, colonIndex))
64-
const packages = text.slice(colonIndex + 1).trim()
64+
const packagesStr = text.slice(colonIndex + 1).trim()
65+
const packages = packagesStr.split(/\s+/).filter(Boolean)
6566

66-
if (!framework || !packages) {
67+
if (!framework || packages.length === 0) {
6768
return null
6869
}
6970

@@ -75,7 +76,7 @@ function extractPackageManagerData(
7576
mode: InstallMode,
7677
): PackageManagerExtraction | null {
7778
const children = node.children ?? []
78-
const packagesByFramework: Record<string, string> = {}
79+
const packagesByFramework: Record<string, string[]> = {}
7980

8081
// Recursively extract text from all children (including nested in <p> tags)
8182
function extractText(nodes: any[]): string {
@@ -99,7 +100,12 @@ function extractPackageManagerData(
99100

100101
const parsed = parseFrameworkLine(trimmed)
101102
if (parsed) {
102-
packagesByFramework[parsed.framework] = parsed.packages
103+
// Support multiple entries for same framework by concatenating packages
104+
if (packagesByFramework[parsed.framework]) {
105+
packagesByFramework[parsed.framework].push(...parsed.packages)
106+
} else {
107+
packagesByFramework[parsed.framework] = parsed.packages
108+
}
103109
}
104110
}
105111

0 commit comments

Comments
 (0)