-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added navbar and CodeBlock for fast scripts
- Loading branch information
1 parent
6f5243e
commit 5f8a8dd
Showing
12 changed files
with
537 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
'use client' | ||
|
||
import { useState, useEffect, useRef } from 'react' | ||
import { Menu, Search, X, Sun, Moon } from 'lucide-react' | ||
import Link from 'next/link' | ||
import { useTheme } from '../ThemeContext' | ||
|
||
export default function Navbar() { | ||
const [isSearchOpen, setIsSearchOpen] = useState(false) | ||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) | ||
const mobileMenuRef = useRef<HTMLDivElement>(null) | ||
const { theme, toggleTheme } = useTheme() | ||
|
||
const toggleSearch = () => setIsSearchOpen(!isSearchOpen) | ||
const toggleMobileMenu = () => setIsMobileMenuOpen(!isMobileMenuOpen) | ||
|
||
useEffect(() => { | ||
const handleOutsideClick = (event: MouseEvent) => { | ||
if (mobileMenuRef.current && !mobileMenuRef.current.contains(event.target as Node)) { | ||
setIsMobileMenuOpen(false) | ||
} | ||
} | ||
|
||
document.addEventListener('mousedown', handleOutsideClick) | ||
return () => { | ||
document.removeEventListener('mousedown', handleOutsideClick) | ||
} | ||
}, []) | ||
|
||
const navItems = [ | ||
{ name: 'Home', href: '/' }, | ||
{ name: 'Products', href: '/products' }, | ||
{ name: 'About', href: '/about' }, | ||
{ name: 'Contact', href: '/contact' }, | ||
] | ||
|
||
return ( | ||
<nav className="bg-white dark:bg-gray-800 shadow-sm rounded-xl transition-colors duration-200"> | ||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | ||
<div className="flex items-center justify-between h-16"> | ||
<div className="flex items-center"> | ||
<Link href="/" className="flex-shrink-0" prefetch={false}> | ||
<svg className="h-8 w-8 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" /> | ||
</svg> | ||
</Link> | ||
<div className="hidden md:block ml-10"> | ||
<div className="flex items-baseline space-x-4"> | ||
{navItems.map((item) => ( | ||
<Link | ||
key={item.name} | ||
href={item.href} | ||
className="text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 px-3 py-2 rounded-md text-sm font-medium transition-colors no-underline" | ||
prefetch={false} | ||
> | ||
{item.name} | ||
</Link> | ||
))} | ||
</div> | ||
</div> | ||
</div> | ||
<div className="hidden md:flex items-center space-x-4"> | ||
<div className="relative flex items-center"> | ||
<input | ||
type="search" | ||
placeholder="Search..." | ||
className={`px-3 py-2 pr-10 rounded-md text-sm bg-gray-100 dark:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-300 ease-in-out ${isSearchOpen ? 'w-64 opacity-100' : 'w-0 opacity-0'}`} | ||
/> | ||
<button | ||
onClick={toggleSearch} | ||
className="p-2 rounded-md text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500 absolute right-0" | ||
aria-label="Search" | ||
> | ||
<Search className="h-5 w-5" /> | ||
</button> | ||
</div> | ||
<button | ||
onClick={toggleTheme} | ||
className="p-2 rounded-md text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500" | ||
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`} | ||
> | ||
{theme === 'light' ? <Moon className="h-5 w-5" /> : <Sun className="h-5 w-5" />} | ||
</button> | ||
</div> | ||
<div className="md:hidden"> | ||
<button | ||
onClick={toggleMobileMenu} | ||
className="p-2 rounded-md text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500" | ||
aria-label="Open menu" | ||
> | ||
<Menu className="h-5 w-5" /> | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
{/* Mobile menu */} | ||
<div | ||
ref={mobileMenuRef} | ||
className={`fixed inset-y-0 right-0 w-64 bg-white dark:bg-gray-800 shadow-lg transform transition-transform duration-300 ease-in-out ${isMobileMenuOpen ? 'translate-x-0' : 'translate-x-full'} z-50`} | ||
> | ||
<div className="p-6"> | ||
<div className="flex items-center justify-between mb-8"> | ||
<Link href="/" className="flex-shrink-0" prefetch={false}> | ||
<svg className="h-8 w-8 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" /> | ||
</svg> | ||
</Link> | ||
<button | ||
onClick={toggleMobileMenu} | ||
className="p-2 rounded-md text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500" | ||
aria-label="Close menu" | ||
> | ||
<X className="h-5 w-5" /> | ||
</button> | ||
</div> | ||
<nav className="space-y-1"> | ||
{navItems.map((item) => ( | ||
<Link | ||
key={item.name} | ||
href={item.href} | ||
className="block px-3 py-2 rounded-md text-base font-medium text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors no-underline" | ||
prefetch={false} | ||
> | ||
{item.name} | ||
</Link> | ||
))} | ||
</nav> | ||
<div className="mt-6 space-y-4"> | ||
<div className="relative"> | ||
<input | ||
type="search" | ||
placeholder="Search..." | ||
className="w-full px-3 py-2 rounded-md text-sm bg-gray-100 dark:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500" | ||
/> | ||
<Search className="absolute right-3 top-2.5 h-4 w-4 text-gray-400 dark:text-gray-500" /> | ||
</div> | ||
<button | ||
onClick={toggleTheme} | ||
className="w-full p-2 rounded-md text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center justify-center" | ||
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`} | ||
> | ||
{theme === 'light' ? <Moon className="h-5 w-5 mr-2" /> : <Sun className="h-5 w-5 mr-2" />} | ||
{theme === 'light' ? 'Dark Mode' : 'Light Mode'} | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
</nav> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
'use client' | ||
|
||
import React, { createContext, useContext, useState, useEffect } from 'react' | ||
|
||
type Theme = 'light' | 'dark' | ||
|
||
interface ThemeContextType { | ||
theme: Theme | ||
toggleTheme: () => void | ||
} | ||
|
||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined) | ||
|
||
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { | ||
const [theme, setTheme] = useState<Theme>('dark') | ||
|
||
useEffect(() => { | ||
const savedTheme = localStorage.getItem('theme') as Theme | null | ||
if (savedTheme) { | ||
setTheme(savedTheme) | ||
} | ||
}, []) | ||
|
||
useEffect(() => { | ||
document.documentElement.classList.toggle('dark', theme === 'dark') | ||
localStorage.setItem('theme', theme) | ||
}, [theme]) | ||
|
||
const toggleTheme = () => { | ||
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light') | ||
} | ||
|
||
return ( | ||
<ThemeContext.Provider value={{ theme, toggleTheme }}> | ||
{children} | ||
</ThemeContext.Provider> | ||
) | ||
} | ||
|
||
export const useTheme = () => { | ||
const context = useContext(ThemeContext) | ||
if (context === undefined) { | ||
throw new Error('useTheme must be used within a ThemeProvider') | ||
} | ||
return context | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
'use client' | ||
|
||
import React, { useState } from 'react' | ||
import { Copy, Check } from 'lucide-react' | ||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' | ||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism' | ||
|
||
interface CodeBlockProps { | ||
code: string | ||
fileName: string | ||
fileExtension: string | ||
className?: string | ||
} | ||
|
||
export default function CodeBlock({ | ||
code, | ||
fileName = 'file', | ||
fileExtension = 'tsx', | ||
className, | ||
}: CodeBlockProps) { | ||
const [copied, setCopied] = useState(false) | ||
|
||
const handleCopy = () => { | ||
navigator.clipboard.writeText(code).then(() => { | ||
setCopied(true) | ||
setTimeout(() => setCopied(false), 2000) | ||
}) | ||
} | ||
|
||
// Create a custom style by modifying vscDarkPlus | ||
const customStyle = { | ||
...vscDarkPlus, | ||
'pre[class*="language-"]': { | ||
...vscDarkPlus['pre[class*="language-"]'], | ||
background: '#1e1e1e', | ||
padding: '16px', | ||
margin: 0, | ||
}, | ||
'code[class*="language-"]': { | ||
...vscDarkPlus['code[class*="language-"]'], | ||
background: 'none', | ||
textShadow: 'none', | ||
}, | ||
} | ||
|
||
return ( | ||
<div className={`bg-[#1e1e1e] text-foreground rounded-md w-full max-w-3xl ${className}`}> | ||
<div className="flex justify-between items-center px-4 py-2 bg-[#252526] rounded-t-md"> | ||
<div className="flex items-center space-x-2"> | ||
<span className="flex items-center space-x-1 text-gray-300"> | ||
<span className="bg-[#007acc] text-white text-xs font-bold px-2 py-1 rounded-xl"> | ||
{fileExtension.toUpperCase()} | ||
</span> | ||
<div className='p-1'> | ||
<span><code>{fileName}.{fileExtension}</code></span> | ||
</div> | ||
</span> | ||
</div> | ||
<button className="text-gray-400 hover:text-white" onClick={handleCopy}> | ||
{copied ? <Check size={18} /> : <Copy size={18} />} | ||
</button> | ||
</div> | ||
|
||
<div className="bg-[#1e1e1e] rounded-b-md overflow-hidden"> | ||
<SyntaxHighlighter | ||
language={fileExtension} | ||
style={customStyle} | ||
customStyle={{ | ||
borderRadius: '0 0 8px 8px', | ||
}} | ||
showLineNumbers={false} | ||
wrapLines={true} | ||
> | ||
{code} | ||
</SyntaxHighlighter> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.