@tscircuit/fake-snippets 0.0.26 → 0.0.28

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 (58) hide show
  1. package/bun-tests/fake-snippets-api/routes/package_files/create.test.ts +5 -6
  2. package/bun-tests/fake-snippets-api/routes/packages/list-1.test.ts +3 -0
  3. package/bun-tests/fake-snippets-api/routes/packages/list-2.test.ts +2 -0
  4. package/bun-tests/fake-snippets-api/routes/snippets/list_newest.test.ts +5 -3
  5. package/bun-tests/fake-snippets-api/routes/snippets/list_trending.test.ts +8 -5
  6. package/bun-tests/fake-snippets-api/routes/snippets/update.test.ts +1 -1
  7. package/bun.lock +110 -5
  8. package/dist/bundle.js +62 -11
  9. package/dist/index.d.ts +5 -0
  10. package/dist/index.js +4 -2
  11. package/fake-snippets-api/lib/db/autoload-snippets.json +4 -0
  12. package/fake-snippets-api/lib/db/db-client.ts +2 -1
  13. package/fake-snippets-api/lib/db/schema.ts +1 -0
  14. package/fake-snippets-api/lib/public-mapping/public-map-package.ts +1 -0
  15. package/fake-snippets-api/routes/api/package_files/list.ts +6 -3
  16. package/fake-snippets-api/routes/api/package_releases/get.ts +67 -1
  17. package/fake-snippets-api/routes/api/packages/create.ts +2 -1
  18. package/fake-snippets-api/routes/api/snippets/create.ts +1 -0
  19. package/package.json +2 -1
  20. package/public/placeholder-logo.png +0 -0
  21. package/public/placeholder-logo.svg +1 -0
  22. package/public/placeholder-user.jpg +0 -0
  23. package/public/placeholder.jpg +0 -0
  24. package/public/placeholder.svg +1 -0
  25. package/src/App.tsx +6 -0
  26. package/src/components/ViewPackagePage/components/ShikiCodeViewer.tsx +50 -0
  27. package/src/components/ViewPackagePage/components/file-explorer.tsx +118 -0
  28. package/src/components/ViewPackagePage/components/important-files-view.tsx +231 -0
  29. package/src/components/ViewPackagePage/components/main-content-header.tsx +172 -0
  30. package/src/components/ViewPackagePage/components/main-content-view-selector.tsx +106 -0
  31. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +130 -0
  32. package/src/components/ViewPackagePage/components/package-header.tsx +107 -0
  33. package/src/components/ViewPackagePage/components/preview-image-squares.tsx +63 -0
  34. package/src/components/ViewPackagePage/components/readme-view.tsx +58 -0
  35. package/src/components/ViewPackagePage/components/repo-header-button.tsx +36 -0
  36. package/src/components/ViewPackagePage/components/repo-header.tsx +4 -0
  37. package/src/components/ViewPackagePage/components/repo-page-content.tsx +213 -0
  38. package/src/components/ViewPackagePage/components/repo-tab-header.tsx +12 -0
  39. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +103 -0
  40. package/src/components/ViewPackagePage/components/sidebar-contributors-section.tsx +31 -0
  41. package/src/components/ViewPackagePage/components/sidebar-packages-section.tsx +16 -0
  42. package/src/components/ViewPackagePage/components/sidebar-releases-section.tsx +44 -0
  43. package/src/components/ViewPackagePage/components/sidebar.tsx +40 -0
  44. package/src/components/ViewPackagePage/components/tab-views/3d-view.tsx +9 -0
  45. package/src/components/ViewPackagePage/components/tab-views/bom-view.tsx +9 -0
  46. package/src/components/ViewPackagePage/components/tab-views/files-view.tsx +166 -0
  47. package/src/components/ViewPackagePage/components/tab-views/pcb-view.tsx +9 -0
  48. package/src/components/ViewPackagePage/components/tab-views/schematic-view.tsx +9 -0
  49. package/src/components/ViewPackagePage/components/theme-toggle.tsx +42 -0
  50. package/src/components/ViewPackagePage/hooks/use-mobile.tsx +19 -0
  51. package/src/components/ViewPackagePage/hooks/use-toast.ts +191 -0
  52. package/src/components/ViewPackagePage/simulate-page.tsx +120 -0
  53. package/src/components/ViewPackagePage/utils/is-package-file-important.ts +21 -0
  54. package/src/hooks/use-package-files.ts +29 -0
  55. package/src/hooks/use-package-release.ts +22 -0
  56. package/src/index.css +15 -0
  57. package/src/pages/beta.tsx +282 -99
  58. package/src/pages/view-package.tsx +38 -0
@@ -0,0 +1,107 @@
1
+ import { TypeBadge } from "@/components/TypeBadge"
2
+ import { Button } from "@/components/ui/button"
3
+ import { LockClosedIcon } from "@radix-ui/react-icons"
4
+ import { Eye, GitFork, Star } from "lucide-react"
5
+ import { Link } from "wouter"
6
+ import { Skeleton } from "@/components/ui/skeleton"
7
+
8
+ interface PackageInfo {
9
+ name: string
10
+ unscoped_name: string
11
+ owner_github_username: string
12
+ star_count: string
13
+ description: string
14
+ ai_description: string
15
+ creator_account_id?: string
16
+ owner_org_id?: string
17
+ }
18
+
19
+ interface PackageHeaderProps {
20
+ packageInfo?: PackageInfo
21
+ isPrivate?: boolean
22
+ isStarred?: boolean
23
+ onStarClick?: () => void
24
+ onForkClick?: () => void
25
+ isCurrentUserAuthor?: boolean
26
+ }
27
+
28
+ export default function PackageHeader({
29
+ packageInfo,
30
+ isPrivate = false,
31
+ isStarred = false,
32
+ onStarClick,
33
+ onForkClick,
34
+ isCurrentUserAuthor = false,
35
+ }: PackageHeaderProps) {
36
+ const author = packageInfo?.owner_github_username
37
+ const packageName = packageInfo?.unscoped_name
38
+ const starCount = packageInfo?.star_count
39
+ ? parseInt(packageInfo.star_count)
40
+ : 0
41
+
42
+ return (
43
+ <header className="bg-white border-b border-gray-200 py-4">
44
+ <div className="max-w-[1200px] mx-auto px-4">
45
+ <div className="flex items-center justify-between">
46
+ <div className="flex items-center">
47
+ {author && packageName ? (
48
+ <>
49
+ <h1 className="text-xl font-bold mr-2">
50
+ <Link href={`/${author}`} className="text-blue-600">
51
+ {author}
52
+ </Link>
53
+ <span className="px-1 text-gray-500">/</span>
54
+ <Link
55
+ className="text-blue-600"
56
+ href={`/${author}/${packageName}`}
57
+ >
58
+ {packageName}
59
+ </Link>
60
+ </h1>
61
+ {packageInfo?.name && <TypeBadge type="package" />}
62
+ {isPrivate && (
63
+ <div className="relative group pl-2">
64
+ <LockClosedIcon className="h-4 w-4 text-gray-700" />
65
+ <span className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-1 hidden group-hover:block bg-black text-white text-xs rounded py-1 px-2">
66
+ private
67
+ </span>
68
+ </div>
69
+ )}
70
+ </>
71
+ ) : (
72
+ <Skeleton className="h-6 w-72" />
73
+ )}
74
+ </div>
75
+ <div className="flex items-center space-x-2">
76
+ <Button
77
+ variant="outline"
78
+ size="sm"
79
+ onClick={onStarClick}
80
+ disabled={!onStarClick}
81
+ >
82
+ <Star
83
+ className={`w-4 h-4 mr-2 ${isStarred ? "fill-yellow-500 text-yellow-500" : ""}`}
84
+ />
85
+ {isStarred ? "Starred" : "Star"}
86
+ {starCount > 0 && (
87
+ <span className="ml-1.5 bg-gray-100 text-gray-700 rounded-full px-1.5 py-0.5 text-xs font-medium">
88
+ {starCount}
89
+ </span>
90
+ )}
91
+ </Button>
92
+
93
+ <Button
94
+ variant="outline"
95
+ size="sm"
96
+ onClick={onForkClick}
97
+ disabled={!onForkClick}
98
+ >
99
+ <GitFork className="w-4 h-4 mr-2" />
100
+ {isCurrentUserAuthor ? "Save" : "Fork"}
101
+ </Button>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </header>
106
+ )
107
+ }
@@ -0,0 +1,63 @@
1
+ "use client"
2
+
3
+ import type { Package } from "fake-snippets-api/lib/db/schema"
4
+ import { useState } from "react"
5
+
6
+ interface ViewPlaceholdersProps {
7
+ packageInfo?: Pick<Package, "name">
8
+ onViewChange?: (view: "3d" | "pcb" | "schematic") => void
9
+ }
10
+
11
+ export default function PreviewImageSquares({
12
+ packageInfo,
13
+ onViewChange,
14
+ }: ViewPlaceholdersProps) {
15
+ const [activeView, setActiveView] = useState("code")
16
+
17
+ const views = [
18
+ { id: "3d", label: "3D View" },
19
+ {
20
+ id: "pcb",
21
+ label: "PCB View",
22
+ imageUrl: `https://registry-api.tscircuit.com/snippets/images/${packageInfo?.name}/pcb.png`,
23
+ },
24
+ {
25
+ id: "schematic",
26
+ label: "Schematic View",
27
+ imageUrl: `https://registry-api.tscircuit.com/snippets/images/${packageInfo?.name}/schematic.png`,
28
+ },
29
+ ] satisfies {
30
+ id: "3d" | "pcb" | "schematic"
31
+ label: string
32
+ imageUrl?: string
33
+ }[]
34
+
35
+ const handleViewClick = (viewId: string) => {
36
+ setActiveView(viewId)
37
+ onViewChange?.(viewId as "3d" | "pcb" | "schematic")
38
+ }
39
+
40
+ return (
41
+ <div className="grid grid-cols-3 gap-2 mb-6">
42
+ {views.map((view) => (
43
+ <button
44
+ key={view.id}
45
+ className={`aspect-square bg-gray-100 dark:bg-[#161b22] rounded-lg border border-gray-200 dark:border-[#30363d] hover:bg-gray-200 dark:hover:bg-[#21262d] flex items-center justify-center transition-colors`}
46
+ onClick={() => handleViewClick(view.id)}
47
+ >
48
+ {view.imageUrl ? (
49
+ <img
50
+ src={view.imageUrl}
51
+ alt={view.label}
52
+ className="w-full h-full object-cover"
53
+ />
54
+ ) : (
55
+ <span className="text-xs font-medium text-gray-700 dark:text-[#c9d1d9]">
56
+ {view.label}
57
+ </span>
58
+ )}
59
+ </button>
60
+ ))}
61
+ </div>
62
+ )
63
+ }
@@ -0,0 +1,58 @@
1
+ import { Edit, FileText, List } from "lucide-react"
2
+
3
+ export default function ReadmeView() {
4
+ return (
5
+ <div className="mt-4 border border-gray-200 dark:border-[#30363d] rounded-md overflow-hidden">
6
+ <div className="flex items-center px-4 py-2 bg-gray-100 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d]">
7
+ <div className="flex items-center">
8
+ <FileText className="h-4 w-4 mr-2" />
9
+ <span className="font-semibold">README</span>
10
+ </div>
11
+ <div className="ml-4 px-2 py-0.5 bg-gray-200 dark:bg-[#30363d] rounded-md text-xs">
12
+ MIT license
13
+ </div>
14
+ <div className="ml-auto flex items-center">
15
+ <Edit className="h-4 w-4 mr-1" />
16
+ <List className="h-4 w-4 ml-2" />
17
+ </div>
18
+ </div>
19
+ <div className="p-4 bg-white dark:bg-[#0d1117]">
20
+ <h2 className="text-2xl font-bold mb-4">@tscircuit/core</h2>
21
+ <p className="mb-4">
22
+ The core logic used to build Circuit JSON from tscircuit React
23
+ elements.
24
+ </p>
25
+ <div className="mb-4">
26
+ <a
27
+ href="#"
28
+ className="text-blue-600 dark:text-[#58a6ff] hover:underline"
29
+ >
30
+ tscircuit
31
+ </a>
32
+ <span className="mx-2">•</span>
33
+ <a
34
+ href="#"
35
+ className="text-blue-600 dark:text-[#58a6ff] hover:underline"
36
+ >
37
+ Development Guide
38
+ </a>
39
+ <span className="mx-2">•</span>
40
+ <a
41
+ href="#"
42
+ className="text-blue-600 dark:text-[#58a6ff] hover:underline"
43
+ >
44
+ Core Benchmarks
45
+ </a>
46
+ </div>
47
+ <p>
48
+ You can use{" "}
49
+ <code className="bg-gray-100 dark:bg-[#161b22] px-1 py-0.5 rounded">
50
+ core
51
+ </code>{" "}
52
+ to create Circuit JSON, which can then be converted into Gerbers,
53
+ viewed online, and much
54
+ </p>
55
+ </div>
56
+ </div>
57
+ )
58
+ }
@@ -0,0 +1,36 @@
1
+ import { Badge } from "@/components/ui/badge"
2
+ import { Button } from "@/components/ui/button"
3
+ import { ChevronDown } from "lucide-react"
4
+ import type { ReactNode } from "react"
5
+
6
+ interface RepoHeaderButtonProps {
7
+ icon: ReactNode
8
+ label: string
9
+ count?: number
10
+ }
11
+
12
+ export default function RepoHeaderButton({
13
+ icon,
14
+ label,
15
+ count,
16
+ }: RepoHeaderButtonProps) {
17
+ return (
18
+ <Button
19
+ variant="outline"
20
+ size="sm"
21
+ className="border-gray-300 dark:border-[#30363d] bg-gray-100 hover:bg-gray-200 dark:bg-[#21262d] dark:hover:bg-[#30363d] text-gray-700 dark:text-[#c9d1d9]"
22
+ >
23
+ {icon}
24
+ {label}
25
+ {count !== undefined && (
26
+ <Badge
27
+ variant="outline"
28
+ className="ml-2 text-xs rounded-full px-2 py-0.5 bg-transparent border-gray-300 dark:border-[#30363d]"
29
+ >
30
+ {count}
31
+ </Badge>
32
+ )}
33
+ <ChevronDown className="h-4 w-4 ml-1" />
34
+ </Button>
35
+ )
36
+ }
@@ -0,0 +1,4 @@
1
+ export default function RepoHeader() {
2
+ // This component is being handled by a different team
3
+ return null
4
+ }
@@ -0,0 +1,213 @@
1
+ "use client"
2
+
3
+ import { useState, useEffect, useMemo } from "react"
4
+ import MainContentHeader from "./main-content-header"
5
+ import Sidebar from "./sidebar"
6
+ import MobileSidebar from "./mobile-sidebar"
7
+ import ImportantFilesView from "./important-files-view"
8
+ import { ShikiCodeViewer } from "./ShikiCodeViewer"
9
+
10
+ // Tab Views
11
+ import FilesView from "./tab-views/files-view"
12
+ import ThreeDView from "./tab-views/3d-view"
13
+ import PCBView from "./tab-views/pcb-view"
14
+ import SchematicView from "./tab-views/schematic-view"
15
+ import BOMView from "./tab-views/bom-view"
16
+ import { isPackageFileImportant } from "../utils/is-package-file-important"
17
+ import Header from "@/components/Header"
18
+ import Footer from "@/components/Footer"
19
+ import ViewSnippetHeader from "@/components/ViewSnippetHeader"
20
+ import PackageHeader from "./package-header"
21
+
22
+ interface PackageFile {
23
+ package_file_id: string
24
+ package_release_id: string
25
+ file_path: string
26
+ content_text: string
27
+ created_at: string // iso-8601
28
+ }
29
+
30
+ interface PackageInfo {
31
+ name: string
32
+ unscoped_name: string
33
+ owner_github_username: string
34
+ star_count: string
35
+ description: string
36
+ ai_description: string
37
+ ai_usage_instructions: string
38
+ creator_account_id?: string
39
+ owner_org_id?: string
40
+ }
41
+
42
+ interface RepoPageContentProps {
43
+ packageFiles?: PackageFile[]
44
+ importantFilePaths?: string[]
45
+ packageInfo?: PackageInfo
46
+ onFileClicked?: (file: any) => void
47
+ onDirectoryClicked?: (directory: any) => void
48
+ onExportClicked?: (exportType: string) => void
49
+ onEditClicked?: () => void
50
+ }
51
+
52
+ export default function RepoPageContent({
53
+ packageFiles,
54
+ packageInfo,
55
+ onFileClicked,
56
+ onDirectoryClicked,
57
+ onExportClicked,
58
+ onEditClicked,
59
+ }: RepoPageContentProps) {
60
+ const [activeView, setActiveView] = useState("files")
61
+
62
+ const importantFilePaths = packageFiles
63
+ ?.filter((pf) => isPackageFileImportant(pf.file_path))
64
+ ?.map((pf) => pf.file_path)
65
+
66
+ // Parse package files to determine directories and files structure
67
+ const { directories, files } = useMemo(() => {
68
+ if (!packageFiles) {
69
+ return { directories: [], files: [] }
70
+ }
71
+
72
+ const dirs = new Set<string>()
73
+ const filesList: Array<{
74
+ type: "file"
75
+ path: string
76
+ name: string
77
+ created_at: string
78
+ }> = []
79
+
80
+ packageFiles.forEach((file) => {
81
+ // Extract directory path
82
+ const pathParts = file.file_path.split("/")
83
+ const fileName = pathParts.pop() || ""
84
+
85
+ // Add all parent directories
86
+ let currentPath = ""
87
+ pathParts.forEach((part) => {
88
+ currentPath += (currentPath ? "/" : "") + part
89
+ dirs.add(currentPath)
90
+ })
91
+
92
+ filesList.push({
93
+ type: "file",
94
+ path: file.file_path,
95
+ name: fileName,
96
+ created_at: file.created_at,
97
+ })
98
+ })
99
+
100
+ // Convert directories set to array of directory objects
101
+ const dirsList = Array.from(dirs)
102
+ .map((path) => {
103
+ const pathParts = path.split("/")
104
+ if (!path) return null
105
+ return {
106
+ type: "directory",
107
+ path,
108
+ name: pathParts[pathParts.length - 1],
109
+ }
110
+ })
111
+ .filter((dir) => dir !== null)
112
+
113
+ return {
114
+ directories: dirsList,
115
+ files: filesList,
116
+ }
117
+ }, [packageFiles])
118
+
119
+ // Find important files based on importantFilePaths
120
+ const importantFiles = useMemo(() => {
121
+ if (!packageFiles || !importantFilePaths) return []
122
+
123
+ return packageFiles.filter((file) =>
124
+ importantFilePaths.some((path) => file.file_path.endsWith(path)),
125
+ )
126
+ }, [packageFiles, importantFilePaths])
127
+
128
+ // Render the appropriate content based on the active view
129
+ const renderContent = () => {
130
+ switch (activeView) {
131
+ case "files":
132
+ return (
133
+ <FilesView
134
+ directories={directories as any}
135
+ files={files as any}
136
+ isLoading={!packageFiles}
137
+ onFileClicked={onFileClicked}
138
+ onDirectoryClicked={onDirectoryClicked}
139
+ />
140
+ )
141
+ case "3d":
142
+ return <ThreeDView />
143
+ case "pcb":
144
+ return <PCBView />
145
+ case "schematic":
146
+ return <SchematicView />
147
+ case "bom":
148
+ return <BOMView />
149
+ default:
150
+ return (
151
+ <FilesView
152
+ directories={directories as any}
153
+ files={files as any}
154
+ isLoading={!packageFiles}
155
+ onFileClicked={onFileClicked}
156
+ onDirectoryClicked={onDirectoryClicked}
157
+ />
158
+ )
159
+ }
160
+ }
161
+
162
+ return (
163
+ <div className="min-h-screen bg-white dark:bg-[#0d1117] text-gray-900 dark:text-[#c9d1d9] font-sans">
164
+ <Header />
165
+ <PackageHeader packageInfo={packageInfo} />
166
+
167
+ {/* Mobile Sidebar */}
168
+ <div className="max-w-[1200px] mx-auto">
169
+ <MobileSidebar packageInfo={packageInfo} isLoading={!packageInfo} />
170
+ </div>
171
+
172
+ {/* Main Content */}
173
+ <div className="max-w-[1200px] mx-auto">
174
+ <div className="flex flex-col md:flex-row">
175
+ {/* Main Content Area */}
176
+ <div className="w-full md:flex-1 border-r border-gray-200 dark:border-[#30363d] p-4 md:max-w-[calc(100%-296px)] max-w-full">
177
+ {/* Main Content Header with Tabs */}
178
+ <MainContentHeader
179
+ activeView={activeView}
180
+ onViewChange={setActiveView}
181
+ onExportClicked={onExportClicked}
182
+ packageInfo={packageInfo}
183
+ />
184
+
185
+ {/* Dynamic Content based on active view */}
186
+ {renderContent()}
187
+
188
+ {/* Important Files View - Always shown */}
189
+ <ImportantFilesView
190
+ importantFiles={importantFiles}
191
+ isLoading={!packageFiles}
192
+ onEditClicked={onEditClicked}
193
+ aiDescription={packageInfo?.ai_description}
194
+ aiUsageInstructions={packageInfo?.ai_usage_instructions}
195
+ />
196
+ </div>
197
+
198
+ {/* Sidebar - Hidden on mobile, shown on md and up */}
199
+ <div className="hidden md:block md:w-[296px] flex-shrink-0">
200
+ <Sidebar
201
+ packageInfo={packageInfo}
202
+ isLoading={!packageInfo}
203
+ onViewChange={(view) => {
204
+ setActiveView(view)
205
+ }}
206
+ />
207
+ </div>
208
+ </div>
209
+ </div>
210
+ <Footer />
211
+ </div>
212
+ )
213
+ }
@@ -0,0 +1,12 @@
1
+ interface RepoTabHeaderProps {
2
+ activeTab: string
3
+ setActiveTab: (tab: string) => void
4
+ }
5
+
6
+ export default function RepoTabHeader({
7
+ activeTab,
8
+ setActiveTab,
9
+ }: RepoTabHeaderProps) {
10
+ // This component is being handled by a different team
11
+ return null
12
+ }
@@ -0,0 +1,103 @@
1
+ import { Badge } from "@/components/ui/badge"
2
+ import { GitFork, Star } from "lucide-react"
3
+ import { Skeleton } from "@/components/ui/skeleton"
4
+
5
+ interface PackageInfo {
6
+ name: string
7
+ unscoped_name: string
8
+ owner_github_username: string
9
+ star_count: string
10
+ description: string
11
+ ai_description: string
12
+ creator_account_id?: string
13
+ owner_org_id?: string
14
+ is_package?: boolean
15
+ website?: string
16
+ }
17
+
18
+ interface SidebarAboutSectionProps {
19
+ packageInfo?: PackageInfo
20
+ isLoading?: boolean
21
+ }
22
+
23
+ const LinkIcon = () => (
24
+ <svg className="h-4 w-4 mr-1" viewBox="0 0 16 16" fill="currentColor">
25
+ <path d="M7.775 3.275a.75.75 0 0 0 1.06 1.06l1.25-1.25a2 2 0 1 1 2.83 2.83l-2.5 2.5a2 2 0 0 1-2.83 0 .75.75 0 0 0-1.06 1.06 3.5 3.5 0 0 0 4.95 0l2.5-2.5a3.5 3.5 0 0 0-4.95-4.95l-1.25 1.25Zm-4.69 9.64a2 2 0 0 1 0-2.83l2.5-2.5a2 2 0 0 1 2.83 0 .75.75 0 0 0 1.06-1.06 3.5 3.5 0 0 0-4.95 0l-2.5 2.5a3.5 3.5 0 0 0 4.95 4.95l1.25-1.25a.75.75 0 0 0-1.06-1.06l-1.25 1.25a2 2 0 0 1-2.83 0Z"></path>
26
+ </svg>
27
+ )
28
+
29
+ export default function SidebarAboutSection({
30
+ packageInfo,
31
+ isLoading = false,
32
+ }: SidebarAboutSectionProps) {
33
+ const topics = packageInfo?.is_package ? ["Package"] : ["Board"]
34
+
35
+ if (isLoading) {
36
+ return (
37
+ <div className="mb-6">
38
+ <h2 className="text-lg font-semibold mb-2">About</h2>
39
+ <Skeleton className="h-4 w-full mb-3" />
40
+ <Skeleton className="h-4 w-3/4 mb-4" />
41
+ <div className="flex flex-wrap gap-2 mb-4">
42
+ {[1, 2, 3].map((i) => (
43
+ <Skeleton key={i} className="h-6 w-20 rounded-full" />
44
+ ))}
45
+ </div>
46
+ <div className="space-y-2">
47
+ <Skeleton className="h-4 w-24" />
48
+ <Skeleton className="h-4 w-20" />
49
+ <Skeleton className="h-4 w-20" />
50
+ </div>
51
+ </div>
52
+ )
53
+ }
54
+
55
+ return (
56
+ <div className="mb-6">
57
+ <h2 className="text-lg font-semibold mb-2">About</h2>
58
+ <p className="text-sm mb-3">
59
+ {packageInfo?.description || packageInfo?.ai_description}
60
+ </p>
61
+ {packageInfo?.website && (
62
+ <a
63
+ href="#"
64
+ className="text-blue-600 dark:text-[#58a6ff] hover:underline text-sm flex items-center mb-4"
65
+ >
66
+ <LinkIcon />
67
+ {packageInfo?.website}
68
+ </a>
69
+ )}
70
+ <div className="flex flex-wrap gap-2 mb-4">
71
+ {topics.map((topic, index) => (
72
+ <Badge
73
+ key={index}
74
+ variant="outline"
75
+ className="text-xs rounded-full px-2 py-0.5 bg-blue-100 dark:bg-[#1f6feb33] text-blue-600 dark:text-[#58a6ff] border-blue-300 dark:border-[#1f6feb66]"
76
+ >
77
+ {topic}
78
+ </Badge>
79
+ ))}
80
+ </div>
81
+ <div className="space-y-2 text-sm">
82
+ <div className="flex items-center">
83
+ <svg
84
+ className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]"
85
+ viewBox="0 0 16 16"
86
+ fill="currentColor"
87
+ >
88
+ <path d="M8.75.75V2h.985c.304 0 .603.08.867.231l1.29.736c.038.022.08.033.124.033h2.234a.75.75 0 0 1 0 1.5h-.427l2.111 4.692a.75.75 0 0 1-.154.838l-.53-.53.53.53-.001.002-.002.002-.006.006-.006.005-.01.01-.045.04c-.21.176-.441.327-.686.45C14.556 10.78 13.88 11 13 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L12.178 4.5h-.162c-.305 0-.604-.079-.868-.231l-1.29-.736a.245.245 0 0 0-.124-.033H8.75V13h2.5a.75.75 0 0 1 0 1.5h-6.5a.75.75 0 0 1 0-1.5h2.5V3.5h-.984a.245.245 0 0 0-.124.033l-1.29.736c-.264.152-.563.231-.868.231h-.162l2.112 4.692a.75.75 0 0 1-.154.838l-.53-.53.53.53-.001.002-.002.002-.006.006-.016.015-.045.04c-.21.176-.441.327-.686.45C4.556 10.78 3.88 11 3 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L2.178 4.5H1.75a.75.75 0 0 1 0-1.5h2.234a.249.249 0 0 0 .125-.033l1.29-.736c.263-.15.561-.231.865-.231H7.25V.75a.75.75 0 0 1 1.5 0Zm2.945 8.477c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L13 6.327Zm-10 0c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L3 6.327Z"></path>
89
+ </svg>
90
+ <span>MIT license</span>
91
+ </div>
92
+ <div className="flex items-center">
93
+ <Star className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
94
+ <span>{packageInfo?.star_count || "16"} stars</span>
95
+ </div>
96
+ <div className="flex items-center">
97
+ <GitFork className="h-4 w-4 mr-2 text-gray-500 dark:text-[#8b949e]" />
98
+ <span>39 forks</span>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ )
103
+ }
@@ -0,0 +1,31 @@
1
+ import { Badge } from "@/components/ui/badge"
2
+
3
+ export default function SidebarContributorsSection() {
4
+ return (
5
+ <div>
6
+ <div className="flex items-center justify-between mb-2">
7
+ <h2 className="text-lg font-semibold">Contributors</h2>
8
+ <Badge
9
+ variant="outline"
10
+ className="text-xs rounded-full px-2 py-0.5 bg-transparent border-gray-300 dark:border-[#30363d]"
11
+ >
12
+ 18
13
+ </Badge>
14
+ </div>
15
+ <div className="flex flex-wrap gap-1 mb-2">
16
+ {[...Array(10)].map((_, i) => (
17
+ <div
18
+ key={i}
19
+ className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-500 to-purple-500"
20
+ ></div>
21
+ ))}
22
+ </div>
23
+ <a
24
+ href="#"
25
+ className="text-blue-600 dark:text-[#58a6ff] hover:underline text-sm"
26
+ >
27
+ + 4 contributors
28
+ </a>
29
+ </div>
30
+ )
31
+ }
@@ -0,0 +1,16 @@
1
+ export default function SidebarPackagesSection() {
2
+ return (
3
+ <div className="mb-6">
4
+ <h2 className="text-lg font-semibold mb-2">Packages</h2>
5
+ <p className="text-sm text-gray-500 dark:text-[#8b949e] mb-1">
6
+ No packages published
7
+ </p>
8
+ <a
9
+ href="#"
10
+ className="text-blue-600 dark:text-[#58a6ff] hover:underline text-sm"
11
+ >
12
+ Publish your first package
13
+ </a>
14
+ </div>
15
+ )
16
+ }