@tscircuit/fake-snippets 0.0.67 → 0.0.69

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 (40) hide show
  1. package/bun-tests/fake-snippets-api/routes/packages/update.test.ts +104 -0
  2. package/bun.lock +26 -83
  3. package/dist/bundle.js +17 -5
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.js +2 -1
  6. package/dist/schema.d.ts +8 -0
  7. package/dist/schema.js +2 -1
  8. package/fake-snippets-api/lib/db/schema.ts +4 -0
  9. package/fake-snippets-api/routes/api/packages/create.ts +1 -0
  10. package/fake-snippets-api/routes/api/packages/update.ts +11 -2
  11. package/package.json +3 -4
  12. package/src/App.tsx +0 -4
  13. package/src/components/CmdKMenu.tsx +19 -19
  14. package/src/components/FAQ.tsx +3 -1
  15. package/src/components/FileSidebar.tsx +50 -1
  16. package/src/components/Header2.tsx +20 -9
  17. package/src/components/JLCPCBImportDialog.tsx +13 -16
  18. package/src/components/ViewPackagePage/components/important-files-view.tsx +1 -1
  19. package/src/components/ViewPackagePage/components/mobile-sidebar.tsx +1 -0
  20. package/src/components/ViewPackagePage/components/package-header.tsx +22 -12
  21. package/src/components/ViewPackagePage/components/repo-page-content.tsx +23 -7
  22. package/src/components/ViewPackagePage/components/sidebar-about-section.tsx +1 -0
  23. package/src/components/dialogs/confirm-delete-package-dialog.tsx +8 -0
  24. package/src/components/dialogs/edit-package-details-dialog.tsx +177 -139
  25. package/src/components/package-port/CodeAndPreview.tsx +40 -19
  26. package/src/components/package-port/CodeEditor.tsx +8 -27
  27. package/src/components/package-port/EditorNav.tsx +1 -11
  28. package/src/hooks/use-package-details-form.ts +15 -1
  29. package/src/hooks/useFileManagement.ts +59 -0
  30. package/src/index.css +13 -0
  31. package/src/lib/utils/isValidFileName.ts +5 -0
  32. package/src/pages/dashboard.tsx +1 -0
  33. package/src/pages/quickstart.tsx +5 -5
  34. package/src/pages/search.tsx +1 -1
  35. package/src/pages/user-profile.tsx +1 -0
  36. package/src/components/OrderPreviewContent.tsx +0 -61
  37. package/src/components/ViewSnippetSidebar.tsx +0 -162
  38. package/src/components/dialogs/create-order-dialog.tsx +0 -146
  39. package/src/pages/preview.tsx +0 -44
  40. package/src/pages/view-order.tsx +0 -111
@@ -28,7 +28,7 @@ import CodeEditorHeader from "@/components/package-port/CodeEditorHeader"
28
28
  import { useCodeCompletionApi } from "@/hooks/use-code-completion-ai-api"
29
29
  import FileSidebar from "../FileSidebar"
30
30
  import { findTargetFile } from "@/lib/utils/findTargetFile"
31
- import type { PackageFile } from "./CodeAndPreview"
31
+ import type { CreateFileProps, PackageFile } from "./CodeAndPreview"
32
32
  import { useShikiHighlighter } from "@/hooks/use-shiki-highlighter"
33
33
 
34
34
  const defaultImports = `
@@ -39,7 +39,6 @@ import type { CommonLayoutProps } from "@tscircuit/props"
39
39
 
40
40
  export const CodeEditor = ({
41
41
  onCodeChange,
42
- onDtsChange,
43
42
  readOnly = false,
44
43
  files = [],
45
44
  isStreaming = false,
@@ -48,10 +47,11 @@ export const CodeEditor = ({
48
47
  pkgFilesLoaded,
49
48
  currentFile,
50
49
  setCurrentFile,
50
+ handleCreateFile,
51
51
  }: {
52
52
  onCodeChange: (code: string, filename?: string) => void
53
- onDtsChange?: (dts: string) => void
54
53
  files: PackageFile[]
54
+ handleCreateFile: (props: CreateFileProps) => void
55
55
  readOnly?: boolean
56
56
  isStreaming?: boolean
57
57
  pkgFilesLoaded?: boolean
@@ -67,9 +67,8 @@ export const CodeEditor = ({
67
67
  const codeCompletionApi = useCodeCompletionApi()
68
68
  const [cursorPosition, setCursorPosition] = useState<number | null>(null)
69
69
  const [code, setCode] = useState(files[0]?.content || "")
70
- const [isCodeEditorReady, setIsCodeEditorReady] = useState(false)
71
70
 
72
- const { highlighter, isLoading } = useShikiHighlighter()
71
+ const { highlighter } = useShikiHighlighter()
73
72
 
74
73
  // Get URL search params for file_path
75
74
  const urlParams = new URLSearchParams(window.location.search)
@@ -191,9 +190,6 @@ export const CodeEditor = ({
191
190
  return fetch(input, init)
192
191
  },
193
192
  delegate: {
194
- finished: () => {
195
- setIsCodeEditorReady(true)
196
- },
197
193
  started: () => {
198
194
  const manualEditsTypeDeclaration = `
199
195
  declare module "manual-edits.json" {
@@ -248,20 +244,6 @@ export const CodeEditor = ({
248
244
  // setCode(newContent)
249
245
  onCodeChange(newContent, currentFile)
250
246
  onFileContentChanged?.(currentFile, newContent)
251
-
252
- // Generate TypeScript declarations for TypeScript/TSX files
253
- if (currentFile.endsWith(".ts") || currentFile.endsWith(".tsx")) {
254
- const { outputFiles } = env.languageService.getEmitOutput(
255
- currentFile,
256
- true,
257
- )
258
- const dtsFile = outputFiles.find((file) =>
259
- file.name.endsWith(".d.ts"),
260
- )
261
- if (dtsFile?.text && onDtsChange) {
262
- onDtsChange(dtsFile.text)
263
- }
264
- }
265
247
  }
266
248
  if (update.selectionSet) {
267
249
  const pos = update.state.selection.main.head
@@ -452,8 +434,6 @@ export const CodeEditor = ({
452
434
 
453
435
  if (currentFile.endsWith(".tsx") || currentFile.endsWith(".ts")) {
454
436
  ata(`${defaultImports}${code}`)
455
- } else if (!!currentFile) {
456
- setIsCodeEditorReady(true)
457
437
  }
458
438
 
459
439
  return () => {
@@ -542,6 +522,7 @@ export const CodeEditor = ({
542
522
  [sidebarOpen, setSidebarOpen] as ReturnType<typeof useState<boolean>>
543
523
  }
544
524
  onFileSelect={handleFileChange}
525
+ handleCreateFile={handleCreateFile}
545
526
  />
546
527
  <div className="flex flex-col flex-1 w-full min-w-0 h-full">
547
528
  {showImportAndFormatButtons && (
@@ -560,9 +541,9 @@ export const CodeEditor = ({
560
541
  )}
561
542
  <div
562
543
  ref={editorRef}
563
- className={`flex-1 overflow-auto [&_.cm-editor]:h-full [&_.cm-scroller]:!h-full ${
564
- !isCodeEditorReady ? "opacity-50" : ""
565
- }`}
544
+ className={
545
+ "flex-1 overflow-auto [&_.cm-editor]:h-full [&_.cm-scroller]:!h-full"
546
+ }
566
547
  />
567
548
  </div>
568
549
  </div>
@@ -39,7 +39,6 @@ import { Link, useLocation } from "wouter"
39
39
  import { useAxios } from "@/hooks/use-axios"
40
40
  import { useToast } from "@/hooks/use-toast"
41
41
  import { useConfirmDeletePackageDialog } from "@/components/dialogs/confirm-delete-package-dialog"
42
- import { useCreateOrderDialog } from "@/components/dialogs/create-order-dialog"
43
42
  import { useFilesDialog } from "@/components/dialogs/files-dialog"
44
43
  import { useViewTsFilesDialog } from "@/components/dialogs/view-ts-files-dialog"
45
44
  import { DownloadButtonAndMenu } from "@/components/DownloadButtonAndMenu"
@@ -81,8 +80,6 @@ export default function EditorNav({
81
80
  } = useUpdatePackageDescriptionDialog()
82
81
  const { Dialog: DeleteDialog, openDialog: openDeleteDialog } =
83
82
  useConfirmDeletePackageDialog()
84
- const { Dialog: CreateOrderDialog, openDialog: openCreateOrderDialog } =
85
- useCreateOrderDialog()
86
83
  const { Dialog: FilesDialog, openDialog: openFilesDialog } = useFilesDialog()
87
84
  const { Dialog: ViewTsFilesDialog, openDialog: openViewTsFilesDialog } =
88
85
  useViewTsFilesDialog()
@@ -358,13 +355,6 @@ export default function EditorNav({
358
355
  </Button>
359
356
  </DropdownMenuTrigger>
360
357
  <DropdownMenuContent>
361
- <DropdownMenuItem
362
- className="text-xs"
363
- onClick={() => openCreateOrderDialog()}
364
- >
365
- <PackageIcon className="mr-2 h-3 w-3" />
366
- Submit Order
367
- </DropdownMenuItem>
368
358
  <DropdownMenuItem
369
359
  className="text-xs"
370
360
  onClick={() => openFilesDialog()}
@@ -519,8 +509,8 @@ export default function EditorNav({
519
509
  <DeleteDialog
520
510
  packageId={pkg?.package_id ?? ""}
521
511
  packageName={pkg?.unscoped_name ?? ""}
512
+ packageOwner={pkg?.owner_github_username ?? ""}
522
513
  />
523
- <CreateOrderDialog />
524
514
  <FilesDialog snippetId={pkg?.package_id ?? ""} />
525
515
  <ViewTsFilesDialog />
526
516
  </nav>
@@ -15,6 +15,7 @@ interface PackageDetailsForm {
15
15
  website: string
16
16
  license: string | null
17
17
  visibility: string
18
+ defaultView: string
18
19
  }
19
20
 
20
21
  interface UsePackageDetailsFormProps {
@@ -22,6 +23,7 @@ interface UsePackageDetailsFormProps {
22
23
  initialWebsite: string
23
24
  initialLicense: string | null
24
25
  initialVisibility: string
26
+ initialDefaultView: string
25
27
  isDialogOpen: boolean
26
28
  }
27
29
 
@@ -30,6 +32,7 @@ export const usePackageDetailsForm = ({
30
32
  initialWebsite,
31
33
  initialLicense,
32
34
  initialVisibility,
35
+ initialDefaultView,
33
36
  isDialogOpen,
34
37
  }: UsePackageDetailsFormProps) => {
35
38
  const [formData, setFormData] = useState<PackageDetailsForm>({
@@ -37,6 +40,7 @@ export const usePackageDetailsForm = ({
37
40
  website: initialWebsite,
38
41
  license: initialLicense || null,
39
42
  visibility: initialVisibility,
43
+ defaultView: initialDefaultView,
40
44
  })
41
45
  const [websiteError, setWebsiteError] = useState<string | null>(null)
42
46
 
@@ -47,6 +51,7 @@ export const usePackageDetailsForm = ({
47
51
  website: initialWebsite,
48
52
  license: initialLicense || null,
49
53
  visibility: initialVisibility,
54
+ defaultView: initialDefaultView,
50
55
  })
51
56
  setWebsiteError(null)
52
57
  }
@@ -56,6 +61,7 @@ export const usePackageDetailsForm = ({
56
61
  initialWebsite,
57
62
  initialLicense,
58
63
  initialVisibility,
64
+ initialDefaultView,
59
65
  ])
60
66
 
61
67
  useEffect(() => {
@@ -76,18 +82,25 @@ export const usePackageDetailsForm = ({
76
82
  [formData.visibility, initialVisibility],
77
83
  )
78
84
 
85
+ const hasDefaultViewChanged = useMemo(
86
+ () => formData.defaultView !== initialDefaultView,
87
+ [formData.defaultView, initialDefaultView],
88
+ )
89
+
79
90
  const hasChanges = useMemo(
80
91
  () =>
81
92
  formData.description !== initialDescription ||
82
93
  formData.website !== initialWebsite ||
83
94
  formData.license !== initialLicense ||
84
- formData.visibility !== initialVisibility,
95
+ formData.visibility !== initialVisibility ||
96
+ formData.defaultView !== initialDefaultView,
85
97
  [
86
98
  formData,
87
99
  initialDescription,
88
100
  initialWebsite,
89
101
  initialLicense,
90
102
  initialVisibility,
103
+ initialDefaultView,
91
104
  ],
92
105
  )
93
106
 
@@ -99,6 +112,7 @@ export const usePackageDetailsForm = ({
99
112
  websiteError,
100
113
  hasLicenseChanged,
101
114
  hasVisibilityChanged,
115
+ hasDefaultViewChanged,
102
116
  hasChanges,
103
117
  isFormValid,
104
118
  }
@@ -0,0 +1,59 @@
1
+ import { Dispatch, SetStateAction } from "react"
2
+ import { isValidFileName } from "@/lib/utils/isValidFileName"
3
+ import {
4
+ CodeAndPreviewState,
5
+ CreateFileProps,
6
+ } from "../components/package-port/CodeAndPreview"
7
+
8
+ export function useFileManagement(
9
+ state: CodeAndPreviewState,
10
+ setState: Dispatch<SetStateAction<CodeAndPreviewState>>,
11
+ ) {
12
+ const handleCreateFile = async ({
13
+ newFileName,
14
+ setErrorMessage,
15
+ onFileSelect,
16
+ setNewFileName,
17
+ setIsCreatingFile,
18
+ }: CreateFileProps) => {
19
+ newFileName = newFileName.trim()
20
+ if (!newFileName) {
21
+ setErrorMessage("File name cannot be empty")
22
+ return
23
+ }
24
+ if (!isValidFileName(newFileName)) {
25
+ setErrorMessage(
26
+ 'Invalid file name. Avoid using special characters like <>:"/\\|?*',
27
+ )
28
+ return
29
+ }
30
+ setErrorMessage("")
31
+
32
+ const fileExists = state.pkgFilesWithContent.some(
33
+ (file) => file.path === newFileName,
34
+ )
35
+
36
+ if (fileExists) {
37
+ setErrorMessage("A file with this name already exists")
38
+ return
39
+ }
40
+
41
+ setState((prev) => {
42
+ const updatedFiles = [
43
+ ...prev.pkgFilesWithContent,
44
+ { path: newFileName, content: "" },
45
+ ]
46
+ return {
47
+ ...prev,
48
+ pkgFilesWithContent: updatedFiles,
49
+ } as CodeAndPreviewState
50
+ })
51
+ onFileSelect(newFileName)
52
+ setIsCreatingFile(false)
53
+ setNewFileName("")
54
+ }
55
+
56
+ return {
57
+ handleCreateFile,
58
+ }
59
+ }
package/src/index.css CHANGED
@@ -7,6 +7,19 @@
7
7
  }
8
8
  }
9
9
 
10
+ /* add the code bellow */
11
+ @layer utilities {
12
+ /* Hide scrollbar for Chrome, Safari and Opera */
13
+ .no-scrollbar::-webkit-scrollbar {
14
+ display: none;
15
+ }
16
+ /* Hide scrollbar for IE, Edge and Firefox */
17
+ .no-scrollbar {
18
+ -ms-overflow-style: none; /* IE and Edge */
19
+ scrollbar-width: none; /* Firefox */
20
+ }
21
+ }
22
+
10
23
  .shiki {
11
24
  font-family: "Fira Code", monospace;
12
25
  font-size: 14px;
@@ -0,0 +1,5 @@
1
+ export const isValidFileName = (name: string) => {
2
+ // Basic checks for file naming conventions
3
+ const invalidChars = /[<>:"/\\|?*]/
4
+ return name.length > 0 && !invalidChars.test(name)
5
+ }
@@ -194,6 +194,7 @@ export const DashboardPage = () => {
194
194
  <DeleteDialog
195
195
  packageId={packageToDelete.package_id}
196
196
  packageName={packageToDelete.unscoped_name}
197
+ packageOwner={packageToDelete.owner_github_username ?? ""}
197
198
  />
198
199
  )}
199
200
  </div>
@@ -3,7 +3,7 @@ import { useQuery } from "react-query"
3
3
  import { useAxios } from "@/hooks/use-axios"
4
4
  import Header from "@/components/Header"
5
5
  import Footer from "@/components/Footer"
6
- import { Package, Snippet } from "fake-snippets-api/lib/db/schema"
6
+ import { Package } from "fake-snippets-api/lib/db/schema"
7
7
  import { Button } from "@/components/ui/button"
8
8
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
9
9
  import { TypeBadge } from "@/components/TypeBadge"
@@ -48,7 +48,7 @@ export const QuickstartPage = () => {
48
48
  <Header />
49
49
  <div className="container mx-auto px-4 py-8">
50
50
  <div className="mb-8 hidden md:block">
51
- <h2 className="text-xl font-semibold mb-4">Recent Snippets</h2>
51
+ <h2 className="text-xl font-semibold mb-4">Recent Packages</h2>
52
52
  {isLoading ? (
53
53
  <div>Loading...</div>
54
54
  ) : (
@@ -63,7 +63,7 @@ export const QuickstartPage = () => {
63
63
  .map((pkg) => (
64
64
  <PrefetchPageLink
65
65
  key={pkg.package_id}
66
- href={`/editor?snippet_id=${pkg.package_id}`}
66
+ href={`/editor?package_id=${pkg.package_id}`}
67
67
  >
68
68
  <Card className="hover:shadow-md transition-shadow rounded-md flex flex-col h-full">
69
69
  <CardHeader className="pb-0 p-4">
@@ -85,7 +85,7 @@ export const QuickstartPage = () => {
85
85
  </div>
86
86
 
87
87
  <div className="mb-8">
88
- <h2 className="text-xl font-semibold mb-4">Start Blank Snippet</h2>
88
+ <h2 className="text-xl font-semibold mb-4">Start Blank Packages</h2>
89
89
  <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
90
90
  {blankTemplates.map((template, index) => (
91
91
  <PrefetchPageLink
@@ -117,7 +117,7 @@ export const QuickstartPage = () => {
117
117
  </div>
118
118
 
119
119
  <div className="mt-12">
120
- <h2 className="text-xl font-semibold mb-4">Import as Snippet</h2>
120
+ <h2 className="text-xl font-semibold mb-4">Import as Package</h2>
121
121
  <div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4">
122
122
  {[
123
123
  { name: "KiCad Footprint", type: "footprint" },
@@ -97,7 +97,7 @@ export const SearchPage = () => {
97
97
  <div className="mb-6">
98
98
  <div className="flex items-center gap-2 mb-3">
99
99
  <h1 className="text-3xl font-bold text-gray-900">
100
- Search tscircuit Packages
100
+ Search Packages
101
101
  </h1>
102
102
  </div>
103
103
  <div className="flex flex-col sm:flex-row gap-4 mb-4">
@@ -212,6 +212,7 @@ export const UserProfilePage = () => {
212
212
  <DeleteDialog
213
213
  packageId={packageToDelete.package_id}
214
214
  packageName={packageToDelete.unscoped_name}
215
+ packageOwner={packageToDelete.owner_github_username ?? ""}
215
216
  refetchUserPackages={refetchUserPackages}
216
217
  />
217
218
  )}
@@ -1,61 +0,0 @@
1
- import React from "react"
2
- import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
3
- import { PCBViewer } from "@tscircuit/pcb-viewer"
4
- import { CadViewer } from "@tscircuit/3d-viewer"
5
- import { CircuitJsonTableViewer } from "./TableViewer/CircuitJsonTableViewer"
6
- import { AnyCircuitElement } from "circuit-json"
7
-
8
- interface OrderPreviewContentProps {
9
- circuitJson: AnyCircuitElement[] | null
10
- className?: string
11
- }
12
-
13
- export const OrderPreviewContent: React.FC<OrderPreviewContentProps> = ({
14
- circuitJson,
15
- className,
16
- }) => {
17
- return (
18
- <div className={className}>
19
- <Tabs defaultValue="pcb" className="w-full">
20
- <TabsList>
21
- <TabsTrigger value="pcb">PCB</TabsTrigger>
22
- <TabsTrigger value="cad">3D</TabsTrigger>
23
- <TabsTrigger value="json-table">JSON</TabsTrigger>
24
- </TabsList>
25
- <TabsContent value="pcb">
26
- <div className="h-[500px] shadow overflow-hidden sm:rounded-lg mb-6">
27
- {circuitJson ? (
28
- <PCBViewer height={500} circuitJson={circuitJson} />
29
- ) : (
30
- <div className="flex items-center justify-center h-full bg-gray-100">
31
- No PCB data available
32
- </div>
33
- )}
34
- </div>
35
- </TabsContent>
36
- <TabsContent value="cad">
37
- <div className="h-[500px] shadow overflow-hidden sm:rounded-lg mb-6">
38
- {circuitJson ? (
39
- <CadViewer soup={circuitJson as any} />
40
- ) : (
41
- <div className="flex items-center justify-center h-full bg-gray-100">
42
- No 3D data available
43
- </div>
44
- )}
45
- </div>
46
- </TabsContent>
47
- <TabsContent value="json-table">
48
- <div className="h-[500px] shadow overflow-hidden sm:rounded-lg mb-6">
49
- {circuitJson ? (
50
- <CircuitJsonTableViewer elements={circuitJson} />
51
- ) : (
52
- <div className="flex items-center justify-center h-full bg-gray-100">
53
- No JSON data available
54
- </div>
55
- )}
56
- </div>
57
- </TabsContent>
58
- </Tabs>
59
- </div>
60
- )
61
- }
@@ -1,162 +0,0 @@
1
- import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
2
- import { useCurrentSnippet } from "@/hooks/use-current-snippet"
3
- import { useToast } from "@/hooks/use-toast"
4
- import { cn } from "@/lib/utils"
5
- import { AtSign, Bot, Clock, Code, File, GitFork, Package } from "lucide-react"
6
- import { useFilesDialog } from "./dialogs/files-dialog"
7
- import { PrefetchPageLink } from "./PrefetchPageLink"
8
-
9
- export default function ViewSnippetSidebar({
10
- className,
11
- }: {
12
- className?: string
13
- }) {
14
- const { snippet } = useCurrentSnippet()
15
- const { toast } = useToast()
16
- const { Dialog: FilesDialog, openDialog: openFilesDialog } = useFilesDialog()
17
- const { copyToClipboard } = useCopyToClipboard()
18
-
19
- return (
20
- <div
21
- className={cn(
22
- "w-64 h-full bg-gray-100 text-gray-700 flex flex-col flex-shrink-0",
23
- "hidden sm:block h-screen sticky top-0",
24
- className,
25
- )}
26
- >
27
- <nav className="flex-grow overflow-y-auto">
28
- <ul className="p-2 space-y-2">
29
- {[
30
- {
31
- icon: <Code className="w-5 h-5" />,
32
- label: "Edit Code",
33
- href: `/editor?snippet_id=${snippet?.snippet_id}`,
34
- },
35
- {
36
- icon: <Bot className="w-5 h-5" />,
37
- label: "Edit with AI",
38
- badge: "AI",
39
- href: `/ai?snippet_id=${snippet?.snippet_id}`,
40
- },
41
- // {
42
- // icon: <GitHubLogoIcon className="w-5 h-5" />,
43
- // label: "Github",
44
- // },
45
- {
46
- icon: <GitFork className="w-5 h-5" />,
47
- label: "Forks",
48
- notImplemented: true,
49
- },
50
- {
51
- icon: <AtSign className="w-5 h-5" />,
52
- label: "References",
53
- notImplemented: true,
54
- },
55
- {
56
- icon: <Package className="w-5 h-5" />,
57
- label: "Dependencies",
58
- notImplemented: true,
59
- },
60
- {
61
- icon: <Clock className="w-5 h-5" />,
62
- label: "Versions",
63
- notImplemented: true,
64
- },
65
- {
66
- icon: <File className="w-5 h-5" />,
67
- label: "Files",
68
- onClick: () => {
69
- if (snippet) {
70
- openFilesDialog()
71
- }
72
- },
73
- },
74
- // { icon: <Settings className="w-5 h-5" />, label: "Settings" },
75
- ].map((item, index) => (
76
- <li key={index}>
77
- <PrefetchPageLink
78
- href={item.href ?? "#"}
79
- onClick={
80
- item.notImplemented
81
- ? () => {
82
- toast({
83
- title: "Not Implemented!",
84
- description: (
85
- <div>
86
- The {item.label} selection is not implemented yet.
87
- Help us out!{" "}
88
- <a
89
- className="text-blue-500 hover:underline font-semibold"
90
- href="https://github.com/tscircuit/tscircuit.com"
91
- >
92
- Check out our Github
93
- </a>
94
- </div>
95
- ),
96
- })
97
- }
98
- : item.onClick
99
- }
100
- className="flex items-center gap-3 px-2 py-1.5 hover:bg-gray-200 rounded-md"
101
- >
102
- {item.icon}
103
- <span className="text-sm">{item.label}</span>
104
- {item.badge && (
105
- <span className="ml-auto bg-blue-500 text-white text-xs px-1.5 py-0.5 rounded">
106
- {item.badge}
107
- </span>
108
- )}
109
- </PrefetchPageLink>
110
- </li>
111
- ))}
112
- </ul>
113
- </nav>
114
- <div className="p-4 border-t border-gray-200 space-y-4">
115
- <div className="space-y-1">
116
- <div className="text-xs font-medium">Copy embed code</div>
117
- <div
118
- className="text-[0.5em] p-2 rounded-sm bg-blue-50 border border-blue-200 cursor-pointer font-mono whitespace-nowrap overflow-hidden text-ellipsis"
119
- onClick={() => {
120
- const embedCode = `<iframe src="${window.location.origin}/preview?snippet_id=${snippet?.snippet_id}" width="100%" height="500" frameborder="0"></iframe>`
121
- navigator.clipboard.writeText(embedCode)
122
- toast({
123
- title: "Copied!",
124
- description: "Embed code copied to clipboard",
125
- })
126
- }}
127
- >
128
- {`<iframe src="${window.location.origin}/preview?snippet_id=${snippet?.snippet_id}" width="100%" height="500" frameborder="0"></iframe>`}
129
- </div>
130
- </div>
131
- <div className="space-y-1">
132
- <div className="text-xs font-medium">Copy import code</div>
133
- <div
134
- className="text-[0.5em] p-2 rounded-sm bg-blue-50 border border-blue-200 cursor-pointer font-mono whitespace-nowrap overflow-hidden text-ellipsis"
135
- onClick={() =>
136
- copyToClipboard(
137
- `import CircuitModule from "@tsci/${snippet?.owner_name}.${snippet?.unscoped_name}"`,
138
- )
139
- }
140
- >
141
- import CircuitModule from "@tsci/{snippet?.owner_name}.
142
- {snippet?.unscoped_name}"
143
- </div>
144
- </div>
145
- <div className="space-y-1">
146
- <div className="text-xs font-medium">Copy install command</div>
147
- <div
148
- className="text-[0.5em] p-2 rounded-sm bg-blue-50 border border-blue-200 cursor-pointer font-mono whitespace-nowrap overflow-hidden text-ellipsis"
149
- onClick={() =>
150
- copyToClipboard(
151
- `tsci add @tsci/${snippet?.owner_name}.${snippet?.unscoped_name}`,
152
- )
153
- }
154
- >
155
- tsci add @tsci/{snippet?.owner_name}.{snippet?.unscoped_name}
156
- </div>
157
- </div>
158
- </div>
159
- {snippet && <FilesDialog snippetId={snippet.snippet_id} />}
160
- </div>
161
- )
162
- }