feat: add snippet-card and snippets-page

feat: add Hello World snippets for additional languages in seed.ts
This commit is contained in:
ngc2207 2024-11-22 02:14:48 +08:00
parent 1196e50602
commit a284915c46
9 changed files with 460 additions and 5 deletions

View File

@ -13,11 +13,14 @@
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-scroll-area": "^1.2.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"devicons-react": "^1.3.0",
"lucide-react": "^0.460.0",
"next": "15.0.3",
"next-themes": "^0.4.3",

View File

@ -5,7 +5,7 @@ async function main() {
data: [
{
title: "Hello World in JavaScript",
language: "JavaScript",
language: "javascript",
code: `console.log("Hello, World!");
function greet(name) {
@ -16,7 +16,7 @@ console.log(greet("Prisma"));`,
},
{
title: "Hello World in Python",
language: "Python",
language: "python",
code: `print("Hello, World!")
def greet(name):
@ -26,7 +26,7 @@ print(greet("Prisma"))`,
},
{
title: "Hello World in Java",
language: "Java",
language: "java",
code: `public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
@ -39,6 +39,102 @@ print(greet("Prisma"))`,
public static void main(String[] args) {
System.out.println(greet("Prisma"));
}
}`,
},
{
title: "Hello World in C",
language: "c",
code: `#include <stdio.h>
void greet(char* name) {
printf("Hello, %s!\n", name);
}
int main() {
printf("Hello, World!\n");
greet("Prisma");
return 0;
}`,
},
{
title: "Hello World in C++",
language: "cpp",
code: `#include <iostream>
void greet(std::string name) {
std::cout << "Hello, " << name << "!" << std::endl;
}
int main() {
std::cout << "Hello, World!" << std::endl;
greet("Prisma");
return 0;
}`,
},
{
title: "Hello World in C#",
language: "csharp",
code: `using System;
class HelloWorld {
static void Main() {
Console.WriteLine("Hello, World!");
greet("Prisma");
}
static void greet(string name) {
Console.WriteLine("Hello, " + name + "!");
}
}`,
},
{
title: "Hello World in Go",
language: "go",
code: `package main
import "fmt"
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func main() {
fmt.Println("Hello, World!")
greet("Prisma")
}`,
},
{
title: "Hello World in Ruby",
language: "ruby",
code: `puts "Hello, World!"
def greet(name)
puts "Hello, #{name}!"
end
greet("Prisma")`,
},
{
title: "Hello World in Swift",
language: "swift",
code: `print("Hello, World!")
func greet(_ name: String) {
print("Hello, \(name)!")
}
greet("Prisma")`,
},
{
title: "Hello World in Kotlin",
language: "kotlin",
code: `fun main() {
println("Hello, World!")
greet("Prisma")
}
fun greet(name: String) {
println("Hello, $name!")
}`,
},
],

View File

@ -19,14 +19,14 @@ export default async function RootLayout({
<SidebarProvider defaultOpen={defaultOpen}>
<AppSidebar />
<SidebarInset>
<header className="flex h-16 shrink-0 items-center gap-2">
<header className="flex h-12 shrink-0 items-center gap-2">
<div className="flex items-center gap-2 px-4">
<SidebarTrigger className="-ml-1" />
<Separator orientation="vertical" className="mr-2 h-4" />
<ThemeToggle />
</div>
</header>
{children}
<div className="h-full rounded-b-xl">{children}</div>
</SidebarInset>
</SidebarProvider>
);

View File

@ -0,0 +1,106 @@
import {
CsharpOriginal,
JavaOriginal,
PythonOriginal,
JavascriptOriginal,
KotlinOriginal,
COriginal,
GoOriginal,
RubyOriginal,
SwiftOriginal,
CplusplusOriginal,
} from "devicons-react";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { cn } from "@/lib/utils";
import { Snippet } from "@prisma/client";
import { Badge } from "@/components/ui/badge";
import { ScrollArea } from "@/components/ui/scroll-area";
const colors = {
green: "bg-green-50",
gray: "bg-gray-50",
red: "bg-red-50",
yellow: "bg-yellow-50",
purple: "bg-purple-50",
blue: "bg-blue-50",
orange: "bg-orange-50",
} as const;
const languageIcons = {
c: { icon: COriginal, color: colors.green },
cpp: { icon: CplusplusOriginal, color: colors.gray },
java: { icon: JavaOriginal, color: colors.red },
python: { icon: PythonOriginal, color: colors.yellow },
javascript: { icon: JavascriptOriginal, color: colors.yellow },
csharp: { icon: CsharpOriginal, color: colors.purple },
go: { icon: GoOriginal, color: colors.blue },
ruby: { icon: RubyOriginal, color: colors.red },
swift: { icon: SwiftOriginal, color: colors.orange },
kotlin: { icon: KotlinOriginal, color: colors.blue },
} as const;
type Language = keyof typeof languageIcons;
interface SnippetCardProps extends React.ComponentProps<typeof Card> {
snippet: Snippet;
}
export function SnippetCard({
snippet,
className,
...props
}: SnippetCardProps) {
const languageInfo = languageIcons[snippet.language as Language];
const createdAt = new Date(snippet.createdAt);
const updatedAt = new Date(snippet.updatedAt);
const isUpdated = createdAt.getTime() !== updatedAt.getTime();
const renderLanguageBadge = () => (
<Badge
variant="outline"
className={cn(
languageInfo?.color,
"rounded-xl flex items-center justify-between whitespace-nowrap h-6"
)}
>
<div className="flex items-center gap-1">
{languageInfo?.icon && <languageInfo.icon />}
<span>{snippet.language}</span>
</div>
</Badge>
);
const renderTimestamp = () => (
<span className="text-sm text-muted-foreground">
{isUpdated ? "updatedAt: " : "createdAt: "}
{isUpdated ? updatedAt.toLocaleString() : createdAt.toLocaleString()}
</span>
);
return (
<Card className={cn("flex flex-col", className)} {...props}>
<CardHeader className="flex flex-col flex-none p-0">
<CardTitle className="h-8 p-2 pl-4 flex items-center justify-between bg-muted rounded-t-xl">
<span className="truncate">{snippet.title}</span>
{renderLanguageBadge()}
</CardTitle>
</CardHeader>
<CardContent className="flex-grow overflow-hidden p-0">
<ScrollArea className="h-full border-y">
<pre className="p-2 rounded-md overflow-auto whitespace-pre">
<code>{snippet.code}</code>
</pre>
</ScrollArea>
</CardContent>
<CardFooter className="flex-none h-6 p-2 pl-4 mb-0 bg-muted rounded-b-xl">
{renderTimestamp()}
</CardFooter>
</Card>
);
}

View File

@ -0,0 +1,35 @@
import prisma from "@/lib/prisma";
import { PlusCircle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { SnippetCard } from "./components/snippet-card";
export default async function SnippetsPage() {
const snippets = await prisma.snippet.findMany();
const renderedSnippets = snippets.map((snippet) => {
return <SnippetCard key={snippet.id} snippet={snippet} />;
});
return (
<div className="h-full px-4 py-2 lg:py-4 lg:px-8">
<div className="flex items-center justify-between">
<div className="space-y-1">
<h2 className="text-2xl font-semibold tracking-tight">Snippets</h2>
<p className="text-sm text-muted-foreground">
Top picks for you. Updated daily.
</p>
</div>
<div className="ml-auto mr-4">
<Button>
<PlusCircle />
Share Snippet
</Button>
</div>
</div>
<Separator className="my-4" />
<div className="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{renderedSnippets}
</div>
</div>
);
}

View File

@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View File

@ -0,0 +1,76 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@ -0,0 +1,48 @@
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }

View File

@ -0,0 +1,55 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }