Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit9a76918

Browse files
committed
feat(ui): redesign snippet modal and code preview
1 parent502357e commit9a76918

File tree

7 files changed

+216
-9
lines changed

7 files changed

+216
-9
lines changed

‎package-lock.json‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@radix-ui/react-separator":"^1.1.7",
2222
"@radix-ui/react-slot":"^1.2.3",
2323
"@radix-ui/react-switch":"^1.2.5",
24+
"@radix-ui/react-tabs":"^1.1.13",
2425
"@radix-ui/react-tooltip":"^1.2.8",
2526
"@types/mdx":"^2.0.13",
2627
"class-variance-authority":"^0.7.1",

‎src/app/snippets/[category]/[snippet]/page.tsx‎

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,21 @@
33
import{use}from"react";
44

55
import{useRouter}from"next/navigation";
6+
importLinkfrom"next/link";
67

7-
import{Button}from"@/components/ui/button";
8+
import{Loader,XIcon}from"lucide-react";
89

9-
import{unslugify}from"@/lib/utils";
10+
import{FullSnippet}from"@/types";
11+
import{useFetch}from"@/hooks/use-fetch";
12+
import{Button}from"@/components/ui/button";
13+
importCodePreviewfrom"@/components/layouts/code-preview";
14+
import{
15+
Card,
16+
CardContent,
17+
CardFooter,
18+
CardHeader,
19+
}from"@/components/ui/card";
20+
import{Avatar,AvatarFallback,AvatarImage}from"@/components/ui/avatar";
1021

1122
interfaceProps{
1223
params:Promise<{category:string;snippet:string}>;
@@ -15,6 +26,9 @@ interface Props {
1526
exportdefaultfunctionSnippetPage({ params}:Props){
1627
constrouter=useRouter();
1728
const{ category, snippet}=use(params);
29+
const{ data, loading}=useFetch<FullSnippet>(
30+
`/data/snippets/${category}/${snippet}.json`
31+
);
1832

1933
consthandleCloseModal=()=>{
2034
/**
@@ -26,15 +40,55 @@ export default function SnippetPage({ params }: Props) {
2640
elserouter.push("/snippets");
2741
};
2842

43+
if(loading)return<Loader/>;
44+
if(!data)returnnull;
45+
2946
return(
3047
<divclassName="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
31-
<divclassName="bg-secondary text-secondary-foreground p-6 rounded-lg space-y-4 shadow-lg w-[400px]">
32-
<h2className="text-lg font-bold">
33-
{unslugify(category)} /{unslugify(snippet)}
34-
</h2>
35-
<p>Snippet details here</p>
36-
<ButtononClick={handleCloseModal}>Close</Button>
37-
</div>
48+
<CardclassName="wrapper-xs">
49+
<CardHeaderclassName="flex items-center justify-between gap-4">
50+
<h2className="text-2xl font-bold">{data.title}</h2>
51+
<ButtononClick={handleCloseModal}size="icon">
52+
<XIcon/>
53+
</Button>
54+
</CardHeader>
55+
<CardContentclassName="space-y-4">
56+
<p>{data.description}</p>
57+
<CodePreviewlanguages={data.languages}snippets={data.snippets}/>
58+
</CardContent>
59+
<CardFooterclassName="grid gap-4">
60+
<divclassName="flex items-center gap-4">
61+
<pclassName="font-bold">Contributors:</p>
62+
<divclassName="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
63+
{data.contributors.map((contributor)=>(
64+
<Avatar
65+
key={contributor}
66+
className="w-8 h-8"
67+
title={`@${contributor}`}
68+
>
69+
<AvatarImage
70+
src={`https://github.com/${contributor}.png`}
71+
alt={`@${contributor}`}
72+
/>
73+
<AvatarFallback>{contributor.slice(0,2)}</AvatarFallback>
74+
</Avatar>
75+
))}
76+
</div>
77+
</div>
78+
<ulclassName="flex items-center flex-wrap gap-2">
79+
{data.tags.map((tag)=>(
80+
<likey={tag}>
81+
<Link
82+
className="border border-border font-semibold pt-1 pb-2 px-3 rounded-md leading-tight"
83+
href={`/snippets/tags/${tag}`}
84+
>
85+
{tag}
86+
</Link>
87+
</li>
88+
))}
89+
</ul>
90+
</CardFooter>
91+
</Card>
3892
</div>
3993
);
4094
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import{PrismasSyntaxHighlighter}from"react-syntax-highlighter";
2+
import{oneDark}from"react-syntax-highlighter/dist/esm/styles/prism";
3+
4+
import{Tabs,TabsContent,TabsList,TabsTrigger}from"@/components/ui/tabs";
5+
6+
// import CopyToClipboard from "./CopyToClipboard";
7+
// import CopyURLButton from "./CopyURLButton";
8+
9+
typeProps={
10+
languages:string[];
11+
snippets:Record<string,string>;
12+
};
13+
14+
constCodePreview=({ languages, snippets}:Props)=>{
15+
return(
16+
<divclassName="w-full">
17+
<TabsdefaultValue={languages[0]}>
18+
<TabsList>
19+
{languages.map((language)=>(
20+
<TabsTriggerkey={language}value={language}>
21+
{language}
22+
</TabsTrigger>
23+
))}
24+
</TabsList>
25+
{Object.keys(snippets).map((language)=>{
26+
constcode=snippets[languageaskeyoftypeofsnippets];
27+
28+
return(
29+
<TabsContentvalue={language}key={language}>
30+
<SyntaxHighlighter
31+
language={language}
32+
style={oneDark}
33+
wrapLines={true}
34+
customStyle={{margin:"0",maxHeight:"22rem"}}
35+
>
36+
{code}
37+
</SyntaxHighlighter>
38+
</TabsContent>
39+
);
40+
})}
41+
</Tabs>
42+
</div>
43+
);
44+
};
45+
46+
exportdefaultCodePreview;

‎src/components/ui/tabs.tsx‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"use client"
2+
3+
import*asReactfrom"react"
4+
import*asTabsPrimitivefrom"@radix-ui/react-tabs"
5+
6+
import{cn}from"@/lib/utils"
7+
8+
functionTabs({
9+
className,
10+
...props
11+
}:React.ComponentProps<typeofTabsPrimitive.Root>){
12+
return(
13+
<TabsPrimitive.Root
14+
data-slot="tabs"
15+
className={cn("flex flex-col gap-2",className)}
16+
{...props}
17+
/>
18+
)
19+
}
20+
21+
functionTabsList({
22+
className,
23+
...props
24+
}:React.ComponentProps<typeofTabsPrimitive.List>){
25+
return(
26+
<TabsPrimitive.List
27+
data-slot="tabs-list"
28+
className={cn(
29+
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
30+
className
31+
)}
32+
{...props}
33+
/>
34+
)
35+
}
36+
37+
functionTabsTrigger({
38+
className,
39+
...props
40+
}:React.ComponentProps<typeofTabsPrimitive.Trigger>){
41+
return(
42+
<TabsPrimitive.Trigger
43+
data-slot="tabs-trigger"
44+
className={cn(
45+
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
46+
className
47+
)}
48+
{...props}
49+
/>
50+
)
51+
}
52+
53+
functionTabsContent({
54+
className,
55+
...props
56+
}:React.ComponentProps<typeofTabsPrimitive.Content>){
57+
return(
58+
<TabsPrimitive.Content
59+
data-slot="tabs-content"
60+
className={cn("flex-1 outline-none",className)}
61+
{...props}
62+
/>
63+
)
64+
}
65+
66+
export{Tabs,TabsList,TabsTrigger,TabsContent}

‎src/hooks/use-fetch.ts‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import{useEffect,useState}from"react";
2+
3+
exportconstuseFetch=<T>(url:string)=>{
4+
const[data,setData]=useState<T|null>(null);
5+
const[error,setError]=useState<string|null>(null);
6+
const[loading,setLoading]=useState<boolean>(true);
7+
8+
useEffect(()=>{
9+
constfetchData=async()=>{
10+
try{
11+
constres=awaitfetch(url);
12+
if(!res.ok){
13+
thrownewError(`Failed to fetch data from${url}`);
14+
}
15+
constresult:T=awaitres.json();
16+
setData(result);
17+
}catch(err){
18+
setError((errasError).message);
19+
}finally{
20+
setLoading(false);
21+
}
22+
};
23+
24+
fetchData();
25+
},[url]);
26+
27+
return{ data, loading, error};
28+
};

‎src/types/index.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,14 @@ export type SnippetType = {
1616
contributors:string[];
1717
tags:string[];
1818
};
19+
20+
exporttypeFullSnippet={
21+
id:string;
22+
category:string;
23+
title:string;
24+
description:string;
25+
languages:string[];
26+
contributors:string[];
27+
tags:string[];
28+
snippets:Record<string,string>;
29+
};

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp