Skip to content

Commit a9a9bda

Browse files
fahimahammedDeepanshu1230aneeshsunganahalli
authored
V1.0.3 (#130)
* Add light/dark theme toggle in header (#121) * t rebase --continue Enhanced:redesign the component * DarkMode Toggle * Refactored Code Snippet UI (#122) * Made Header & Cards more visually appealing * Improved Search & Filter Design * Formatted Code * Made theme button bigger * Changed animation settings for Header Text * Minor UI Adjustments * Redesigned the UI for code snippets * Used shiki to get more modern code snippets with highlighting * Refactored Code Snippet UI --------- Co-authored-by: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Co-authored-by: Aneesh S <aneeshappid@gmail.com>
1 parent 4990ee9 commit a9a9bda

8 files changed

Lines changed: 803 additions & 94 deletions

File tree

package-lock.json

Lines changed: 610 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"react-day-picker": "^9.11.0",
3030
"react-dom": "19.1.0",
3131
"react-syntax-highlighter": "^15.6.6",
32+
"shiki": "^3.13.0",
3233
"sonner": "^2.0.7",
3334
"tailwind-merge": "^3.3.1"
3435
},

src/app/layout.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Geist, Geist_Mono } from "next/font/google";
44
import BackToTopButton from '@/components/ui/BackToTopButton';
55
import "./globals.css";
66
import ThemeColorPicker from "@/components/ui/ThemeColorPicker";
7+
import { ThemeProvider } from "next-themes"; // ⬅️ import
78

89
const geistSans = Geist({
910
variable: "--font-geist-sans",
@@ -17,7 +18,8 @@ const geistMono = Geist_Mono({
1718

1819
export const metadata: Metadata = {
1920
title: "DevUI",
20-
description: "a modern, open-source component library showcase built with shadcn/ui components",
21+
description:
22+
"a modern, open-source component library showcase built with shadcn/ui components",
2123
};
2224

2325
export default function RootLayout({
@@ -26,11 +28,14 @@ export default function RootLayout({
2628
children: React.ReactNode;
2729
}>) {
2830
return (
29-
<html lang="en">
31+
<html lang="en" suppressHydrationWarning>
3032
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
31-
<ThemeColorPicker />
32-
{children}
33-
<BackToTopButton />
33+
{/* ✅ Wrap everything in ThemeProvider */}
34+
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
35+
<ThemeColorPicker />
36+
{children}
37+
<BackToTopButton />
38+
</ThemeProvider>
3439
</body>
3540
</html>
3641
);

src/app/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from "lucide-react";
1616
import { Button } from "@/components/ui/button";
1717
import Link from "next/link";
18+
import Header from "@/components/Header";
1819

1920
const Index = () => {
2021
const [searchQuery, setSearchQuery] = useState("");
@@ -67,6 +68,8 @@ const Index = () => {
6768

6869
return (
6970
<div className="min-h-screen bg-background">
71+
72+
<Header />
7073
{/* Hero Section - Modern & Clean */}
7174
<section
7275
id="main-content"

src/components/CodeBlock.tsx

Lines changed: 126 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
"use client";
22

33
import { useState, useEffect } from "react";
4-
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
5-
import {
6-
vscDarkPlus,
7-
vs,
8-
} from "react-syntax-highlighter/dist/esm/styles/prism";
4+
import { codeToHtml } from "shiki";
95
import { Check, Copy } from "lucide-react";
106
import { Button } from "@/components/ui/button";
117
import { toast } from "sonner";
@@ -23,103 +19,171 @@ export const CodeBlock = ({
2319
showLineNumbers = true,
2420
}: CodeBlockProps) => {
2521
const [copied, setCopied] = useState(false);
26-
const { theme, resolvedTheme } = useTheme();
22+
const [highlightedCode, setHighlightedCode] = useState<string>("");
23+
const { resolvedTheme } = useTheme();
2724
const [mounted, setMounted] = useState(false);
2825

29-
// Prevent hydration mismatch
26+
// Calculate line count and create line numbers array
27+
const lines = code.split('\n');
28+
const lineCount = lines.length;
29+
const maxLineNumberWidth = lineCount.toString().length;
30+
3031
useEffect(() => {
3132
setMounted(true);
3233
}, []);
3334

34-
const currentTheme = mounted ? resolvedTheme || theme : "dark";
35-
const syntaxTheme = currentTheme === "dark" ? vscDarkPlus : vs;
35+
useEffect(() => {
36+
const generateHighlight = async () => {
37+
try {
38+
const theme = mounted && resolvedTheme === "dark" ? "github-dark" : "github-light";
39+
const html = await codeToHtml(code, {
40+
lang: language,
41+
theme,
42+
});
43+
44+
// If line numbers are enabled, process the HTML to add line number structure
45+
if (showLineNumbers) {
46+
const processedHtml = addLineNumbersToHtml(html, lineCount);
47+
setHighlightedCode(processedHtml);
48+
} else {
49+
setHighlightedCode(html);
50+
}
51+
} catch (error) {
52+
console.error("Highlighting error:", error);
53+
setHighlightedCode(`<pre><code>${code}</code></pre>`);
54+
}
55+
};
56+
57+
generateHighlight();
58+
}, [code, language, mounted, resolvedTheme, showLineNumbers, lineCount]);
59+
60+
// Function to add line numbers to the highlighted HTML
61+
const addLineNumbersToHtml = (html: string, totalLines: number) => {
62+
// Split HTML by newlines and add line number structure
63+
const htmlLines = html.split('\n');
64+
const preMatch = html.match(/<pre[^>]*>/);
65+
const codeMatch = html.match(/<code[^>]*>/);
66+
const preOpenTag = preMatch ? preMatch[0] : '<pre>';
67+
const codeOpenTag = codeMatch ? codeMatch[0] : '<code>';
68+
69+
// Extract the content between <code> and </code>
70+
const codeContent = html.match(/<code[^>]*>([\s\S]*?)<\/code>/)?.[1] || '';
71+
const lines = codeContent.split('\n');
72+
73+
// Wrap each line with line number data
74+
const numberedLines = lines.map((line, index) => {
75+
const lineNumber = index + 1;
76+
return `<span class="code-line" data-line-number="${lineNumber}">${line}</span>`;
77+
}).join('\n');
78+
79+
return `${preOpenTag}${codeOpenTag}${numberedLines}</code></pre>`;
80+
};
3681

3782
const handleCopy = async () => {
3883
try {
3984
await navigator.clipboard.writeText(code);
4085
setCopied(true);
41-
toast.success("Code copied to clipboard!", {
42-
duration: 2000,
43-
});
86+
toast.success("Code copied to clipboard!");
4487
setTimeout(() => setCopied(false), 2000);
4588
} catch (err) {
4689
toast.error("Failed to copy code");
4790
}
4891
};
4992

5093
return (
51-
<div className="relative rounded-xl overflow-hidden border border-border bg-card/50 backdrop-blur-sm shadow-sm hover:shadow-md transition-shadow">
52-
{/* macOS-style Header with Traffic Lights */}
53-
<div className="flex items-center justify-between px-4 py-2.5 border-b border-border bg-secondary/80 dark:bg-secondary/50">
94+
<div className="group relative overflow-hidden rounded-xl border border-primary/20 bg-card/50 shadow-lg transition-all duration-300 hover:shadow-xl hover:border-primary/30">
95+
{/* Header with primary color accents */}
96+
<div className="flex items-center justify-between px-4 py-3 border-b border-primary/20 bg-gradient-to-r from-primary/5 to-primary/10">
5497
<div className="flex items-center gap-3">
55-
{/* macOS Traffic Light Dots */}
56-
<div className="flex items-center gap-1.5">
57-
<div className="w-3 h-3 rounded-full bg-red-500 hover:bg-red-600 transition-colors" />
58-
<div className="w-3 h-3 rounded-full bg-yellow-500 hover:bg-yellow-600 transition-colors" />
59-
<div className="w-3 h-3 rounded-full bg-green-500 hover:bg-green-600 transition-colors" />
98+
{/* VS Code style dots with primary color */}
99+
<div className="flex gap-1.5">
100+
<div className="w-3 h-3 rounded-full bg-primary/60" />
101+
<div className="w-3 h-3 rounded-full bg-primary/40" />
102+
<div className="w-3 h-3 rounded-full bg-primary/20" />
60103
</div>
61-
{/* Language Label */}
62-
<span className="text-xs sm:text-sm font-mono text-muted-foreground uppercase tracking-wider font-semibold">
104+
<span className="text-xs font-mono text-primary font-semibold uppercase tracking-wider">
63105
{language}
64106
</span>
65107
</div>
66108

67-
{/* Copy Button */}
68109
<Button
69110
variant="ghost"
70111
size="sm"
71112
onClick={handleCopy}
72-
className="h-7 sm:h-8 px-2 sm:px-3 hover:bg-accent/50 transition-all"
73-
aria-label={copied ? "Code copied" : "Copy code"}
113+
className="h-8 px-3 text-primary hover:bg-primary/10 hover:text-primary transition-all"
74114
>
75115
{copied ? (
76116
<>
77-
<Check className="h-3 w-3 sm:h-4 sm:w-4 mr-1 text-green-500" />
78-
<span className="text-xs hidden sm:inline">Copied!</span>
117+
<Check className="w-4 h-4 mr-2" />
118+
<span className="text-xs">Copied!</span>
79119
</>
80120
) : (
81121
<>
82-
<Copy className="h-3 w-3 sm:h-4 sm:w-4 mr-1" />
83-
<span className="text-xs hidden sm:inline">Copy</span>
122+
<Copy className="w-4 h-4 mr-2" />
123+
<span className="text-xs">Copy</span>
84124
</>
85125
)}
86126
</Button>
87127
</div>
88128

89-
{/* Code Content */}
90-
<div className="overflow-x-auto">
91-
<SyntaxHighlighter
92-
language={language}
93-
style={{
94-
...syntaxTheme,
95-
'code[class*="language-"]': {
96-
...syntaxTheme['code[class*="language-"]'],
97-
background: "transparent",
98-
backgroundColor: "transparent",
99-
},
100-
}}
101-
showLineNumbers={showLineNumbers}
102-
customStyle={{
103-
margin: 0,
104-
padding: "0.875rem 1rem",
105-
background: "transparent",
106-
backgroundColor: "transparent",
107-
fontSize: "0.8125rem",
108-
lineHeight: "1.6",
109-
}}
110-
codeTagProps={{
111-
style: {
112-
fontSize: "0.8125rem",
113-
fontFamily:
114-
"'Fira Code', 'JetBrains Mono', 'Courier New', monospace",
115-
},
116-
}}
117-
wrapLongLines={false}
118-
className="scrollbar-thin scrollbar-thumb-muted scrollbar-track-transparent"
119-
>
120-
{code}
121-
</SyntaxHighlighter>
129+
{/* Code content with Shiki highlighting and logical line numbers */}
130+
<div
131+
className="overflow-x-auto text-sm leading-relaxed relative"
132+
style={{
133+
fontFamily: "'Fira Code', 'JetBrains Mono', Consolas, monospace",
134+
}}
135+
>
136+
{showLineNumbers && (
137+
<div
138+
className="absolute left-0 top-0 flex flex-col py-4 px-2 text-right select-none pointer-events-none z-10"
139+
style={{
140+
width: `${Math.max(2.5, maxLineNumberWidth * 0.6 + 1)}rem`,
141+
backgroundColor: 'transparent',
142+
borderRight: '1px solid hsl(var(--primary) / 0.2)',
143+
}}
144+
>
145+
{Array.from({ length: lineCount }, (_, i) => (
146+
<span
147+
key={i + 1}
148+
className="block text-xs leading-relaxed opacity-70 hover:opacity-100 transition-opacity"
149+
style={{
150+
color: 'hsl(var(--primary) / 0.8)',
151+
lineHeight: '1.5',
152+
height: '1.5em',
153+
}}
154+
>
155+
{i + 1}
156+
</span>
157+
))}
158+
</div>
159+
)}
160+
161+
{highlightedCode ? (
162+
<div
163+
dangerouslySetInnerHTML={{ __html: highlightedCode }}
164+
className={`
165+
[&_pre]:!bg-transparent [&_pre]:!m-0 [&_pre]:py-4 [&_code]:!bg-transparent
166+
${showLineNumbers ? `[&_pre]:pl-[${Math.max(2.5, maxLineNumberWidth * 0.6 + 1) + 1}rem]` : '[&_pre]:px-4'}
167+
`}
168+
style={{
169+
marginLeft: showLineNumbers ? `${Math.max(2.5, maxLineNumberWidth * 0.6 + 1)}rem` : '0',
170+
paddingLeft: showLineNumbers ? '1rem' : '1rem',
171+
}}
172+
/>
173+
) : (
174+
<pre
175+
className={`py-4 text-muted-foreground ${showLineNumbers ? 'pl-16' : 'px-4'}`}
176+
style={{
177+
marginLeft: showLineNumbers ? `${Math.max(2.5, maxLineNumberWidth * 0.6 + 1)}rem` : '0',
178+
}}
179+
>
180+
<code>{code}</code>
181+
</pre>
182+
)}
122183
</div>
184+
185+
{/* Subtle primary color accent at bottom */}
186+
<div className="h-px bg-gradient-to-r from-transparent via-primary/50 to-transparent" />
123187
</div>
124188
);
125189
};

src/components/ComponentCard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export const ComponentCard = ({
137137
{/* Tabs Section - Enhanced */}
138138
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
139139
<div className="px-4 sm:px-5 lg:px-6 py-3 border-b border-border ">
140-
<TabsList className="bg-secondary/70 h-10 sm:h-11 p-1">
140+
<TabsList className="bg-gray-200 h-10 sm:h-11 p-1">
141141
<TabsTrigger
142142
value="preview"
143143
className="flex items-center gap-2 text-sm px-4 data-[state=active]:bg-card data-[state=active]:shadow-sm"
@@ -159,7 +159,7 @@ export const ComponentCard = ({
159159
value="preview"
160160
className="p-4 sm:p-6 lg:p-8 min-h-[200px] sm:min-h-[240px]"
161161
>
162-
<div className="w-full flex items-center justify-center p-8 rounded-xl border-2 border-dashed border-border/50 bg-secondary/10 hover:border-border transition-colors">
162+
<div className="w-full flex items-center justify-center p-8 rounded-xl border-2 border-border/50 bg-hover:border-border transition-colors">
163163
<div className="scale-90 sm:scale-95 lg:scale-100 origin-center">
164164
{preview}
165165
</div>

0 commit comments

Comments
 (0)