Skip to content

Commit dc15bfd

Browse files
authored
Add REACT code
1 parent a948948 commit dc15bfd

File tree

1 file changed

+256
-0
lines changed

1 file changed

+256
-0
lines changed

it-cheat-sheet.tsx

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
"use client"
2+
3+
import { useState, useEffect } from "react"
4+
import Fuse from "fuse.js"
5+
import { Search, Copy, Check } from "lucide-react"
6+
import { Input } from "@/components/ui/input"
7+
import { Button } from "@/components/ui/button"
8+
import {
9+
Accordion,
10+
AccordionContent,
11+
AccordionItem,
12+
AccordionTrigger,
13+
} from "@/components/ui/accordion"
14+
import { ScrollArea } from "@/components/ui/scroll-area"
15+
import { Skeleton } from "@/components/ui/skeleton"
16+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
17+
import { motion, AnimatePresence } from "framer-motion"
18+
19+
const CHEAT_SHEET_URL = "https://raw.githubusercontent.com/amine250/cheatsheet/refs/heads/main/cheat-sheet-data.json"
20+
21+
type CheatSheetItem = {
22+
title: string
23+
content: string
24+
}
25+
26+
type CheatSheetCategory = {
27+
category: string
28+
items: CheatSheetItem[]
29+
}
30+
31+
const fuseOptions = {
32+
keys: ["category", "items.title", "items.content"],
33+
threshold: 0.4,
34+
}
35+
36+
export default function ITCheatSheet() {
37+
const [cheatSheetData, setCheatSheetData] = useState<CheatSheetCategory[]>([])
38+
const [searchQuery, setSearchQuery] = useState("")
39+
const [searchResults, setSearchResults] = useState<CheatSheetCategory[]>([])
40+
const [isLoading, setIsLoading] = useState(true)
41+
const [error, setError] = useState<string | null>(null)
42+
const [copiedStates, setCopiedStates] = useState<{ [key: string]: boolean }>({})
43+
44+
useEffect(() => {
45+
const fetchCheatSheetData = async () => {
46+
try {
47+
const response = await fetch(CHEAT_SHEET_URL)
48+
if (!response.ok) {
49+
throw new Error("Failed to fetch cheat sheet data")
50+
}
51+
const data = await response.json()
52+
setCheatSheetData(data)
53+
setSearchResults(data)
54+
} catch (err) {
55+
setError("Error loading cheat sheet data. Please try again later.")
56+
} finally {
57+
setIsLoading(false)
58+
}
59+
}
60+
61+
fetchCheatSheetData()
62+
}, [])
63+
64+
useEffect(() => {
65+
if (cheatSheetData.length > 0) {
66+
const fuse = new Fuse(cheatSheetData, fuseOptions)
67+
68+
if (searchQuery === "") {
69+
setSearchResults(cheatSheetData)
70+
} else {
71+
const results = fuse.search(searchQuery).map((result) => result.item)
72+
setSearchResults(results)
73+
}
74+
}
75+
}, [searchQuery, cheatSheetData])
76+
77+
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
78+
setSearchQuery(e.target.value)
79+
}
80+
81+
const handleCopy = (content: string, itemId: string) => {
82+
navigator.clipboard.writeText(content)
83+
setCopiedStates({ ...copiedStates, [itemId]: true })
84+
setTimeout(() => {
85+
setCopiedStates({ ...copiedStates, [itemId]: false })
86+
}, 2000)
87+
}
88+
89+
if (isLoading) {
90+
return (
91+
<div className="container mx-auto p-4 max-w-6xl">
92+
<h1 className="text-4xl font-bold mb-6 text-primary text-center">Amine&apos;s Cheat Sheet</h1>
93+
<Skeleton className="w-full h-10 mb-6" />
94+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
95+
{[1, 2, 3, 4].map((i) => (
96+
<Skeleton key={i} className="w-full h-40" />
97+
))}
98+
</div>
99+
</div>
100+
)
101+
}
102+
103+
if (error) {
104+
return (
105+
<div className="container mx-auto p-4 max-w-6xl">
106+
<Alert variant="destructive">
107+
<AlertTitle>Error</AlertTitle>
108+
<AlertDescription>{error}</AlertDescription>
109+
</Alert>
110+
</div>
111+
)
112+
}
113+
114+
const leftColumnCategories = searchResults.filter((_, index) => index % 2 === 0)
115+
const rightColumnCategories = searchResults.filter((_, index) => index % 2 !== 0)
116+
117+
return (
118+
<div className="container mx-auto p-4 max-w-6xl">
119+
<motion.h1
120+
initial={{ opacity: 0, y: -20 }}
121+
animate={{ opacity: 1, y: 0 }}
122+
transition={{ duration: 0.5 }}
123+
className="text-4xl font-bold mb-6 text-primary text-center"
124+
>
125+
Amine&apos;s Cheat Sheet
126+
</motion.h1>
127+
<motion.div
128+
initial={{ opacity: 0, y: 20 }}
129+
animate={{ opacity: 1, y: 0 }}
130+
transition={{ duration: 0.5, delay: 0.2 }}
131+
className="relative mb-6"
132+
>
133+
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
134+
<Input
135+
type="search"
136+
placeholder="Search commands, scripts, etc..."
137+
value={searchQuery}
138+
onChange={handleSearch}
139+
className="pl-8 bg-background border-primary/20 focus:border-primary transition-all duration-300"
140+
/>
141+
</motion.div>
142+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
143+
<ScrollArea className="h-[calc(100vh-200px)] pr-4">
144+
<AnimatePresence>
145+
{leftColumnCategories.map((category, index) => (
146+
<motion.div
147+
key={category.category}
148+
initial={{ opacity: 0, y: 20 }}
149+
animate={{ opacity: 1, y: 0 }}
150+
exit={{ opacity: 0, y: -20 }}
151+
transition={{ duration: 0.3, delay: index * 0.1 }}
152+
>
153+
<Accordion type="single" collapsible className="mb-4">
154+
<AccordionItem value={`item-${index}`} className="border border-primary/20 rounded-lg">
155+
<AccordionTrigger className="hover:bg-primary/5 px-4 py-2 rounded-t-lg transition-all duration-300">
156+
{category.category}
157+
</AccordionTrigger>
158+
<AccordionContent className="px-4 py-2">
159+
{category.items.map((item, itemIndex) => (
160+
<Accordion key={itemIndex} type="single" collapsible className="mb-2">
161+
<AccordionItem value={`subitem-${itemIndex}`} className="border border-primary/10 rounded-md">
162+
<AccordionTrigger className="hover:bg-primary/5 px-3 py-1 rounded-t-md text-sm transition-all duration-300">
163+
{item.title}
164+
</AccordionTrigger>
165+
<AccordionContent className="px-3 py-2">
166+
<pre className="bg-muted p-2 rounded-md overflow-x-auto text-sm">
167+
<code>{item.content}</code>
168+
</pre>
169+
<Button
170+
variant="outline"
171+
size="sm"
172+
className="mt-2 transition-all duration-300 hover:bg-primary hover:text-primary-foreground"
173+
onClick={() => handleCopy(item.content, `${category.category}-${item.title}`)}
174+
>
175+
{copiedStates[`${category.category}-${item.title}`] ? (
176+
<>
177+
<Check className="w-4 h-4 mr-2" />
178+
Copied!
179+
</>
180+
) : (
181+
<>
182+
<Copy className="w-4 h-4 mr-2" />
183+
Copy to Clipboard
184+
</>
185+
)}
186+
</Button>
187+
</AccordionContent>
188+
</AccordionItem>
189+
</Accordion>
190+
))}
191+
</AccordionContent>
192+
</AccordionItem>
193+
</Accordion>
194+
</motion.div>
195+
))}
196+
</AnimatePresence>
197+
</ScrollArea>
198+
<ScrollArea className="h-[calc(100vh-200px)] pr-4">
199+
<AnimatePresence>
200+
{rightColumnCategories.map((category, index) => (
201+
<motion.div
202+
key={category.category}
203+
initial={{ opacity: 0, y: 20 }}
204+
animate={{ opacity: 1, y: 0 }}
205+
exit={{ opacity: 0, y: -20 }}
206+
transition={{ duration: 0.3, delay: index * 0.1 }}
207+
>
208+
<Accordion type="single" collapsible className="mb-4">
209+
<AccordionItem value={`item-${index}`} className="border border-primary/20 rounded-lg">
210+
<AccordionTrigger className="hover:bg-primary/5 px-4 py-2 rounded-t-lg transition-all duration-300">
211+
{category.category}
212+
</AccordionTrigger>
213+
<AccordionContent className="px-4 py-2">
214+
{category.items.map((item, itemIndex) => (
215+
<Accordion key={itemIndex} type="single" collapsible className="mb-2">
216+
<AccordionItem value={`subitem-${itemIndex}`} className="border border-primary/10 rounded-md">
217+
<AccordionTrigger className="hover:bg-primary/5 px-3 py-1 rounded-t-md text-sm transition-all duration-300">
218+
{item.title}
219+
</AccordionTrigger>
220+
<AccordionContent className="px-3 py-2">
221+
<pre className="bg-muted p-2 rounded-md overflow-x-auto text-sm">
222+
<code>{item.content}</code>
223+
</pre>
224+
<Button
225+
variant="outline"
226+
size="sm"
227+
className="mt-2 transition-all duration-300 hover:bg-primary hover:text-primary-foreground"
228+
onClick={() => handleCopy(item.content, `${category.category}-${item.title}`)}
229+
>
230+
{copiedStates[`${category.category}-${item.title}`] ? (
231+
<>
232+
<Check className="w-4 h-4 mr-2" />
233+
Copied!
234+
</>
235+
) : (
236+
<>
237+
<Copy className="w-4 h-4 mr-2" />
238+
Copy to Clipboard
239+
</>
240+
)}
241+
</Button>
242+
</AccordionContent>
243+
</AccordionItem>
244+
</Accordion>
245+
))}
246+
</AccordionContent>
247+
</AccordionItem>
248+
</Accordion>
249+
</motion.div>
250+
))}
251+
</AnimatePresence>
252+
</ScrollArea>
253+
</div>
254+
</div>
255+
)
256+
}

0 commit comments

Comments
 (0)