diff --git a/app/Components/Sections/Navbar.tsx b/app/Components/Sections/Navbar.tsx new file mode 100644 index 0000000..5ab21f1 --- /dev/null +++ b/app/Components/Sections/Navbar.tsx @@ -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(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 ( + + ) +} diff --git a/app/Components/StyledContainer.tsx b/app/Components/StyledContainer.tsx index 95f1816..9d5085b 100644 --- a/app/Components/StyledContainer.tsx +++ b/app/Components/StyledContainer.tsx @@ -1,3 +1,5 @@ +// This instead should be used for every component + "use client"; import React, { useState, ReactNode } from "react"; diff --git a/app/Components/ThemeContext.tsx b/app/Components/ThemeContext.tsx new file mode 100644 index 0000000..5c89269 --- /dev/null +++ b/app/Components/ThemeContext.tsx @@ -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(undefined) + +export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [theme, setTheme] = useState('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 ( + + {children} + + ) +} + +export const useTheme = () => { + const context = useContext(ThemeContext) + if (context === undefined) { + throw new Error('useTheme must be used within a ThemeProvider') + } + return context +} \ No newline at end of file diff --git a/app/DocsComponents/CodeBlock.tsx b/app/DocsComponents/CodeBlock.tsx new file mode 100644 index 0000000..b56adba --- /dev/null +++ b/app/DocsComponents/CodeBlock.tsx @@ -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 ( +
+
+
+ + + {fileExtension.toUpperCase()} + +
+ {fileName}.{fileExtension} +
+
+
+ +
+ +
+ + {code} + +
+
+ ) +} \ No newline at end of file diff --git a/app/docs/[[...slug]]/page.tsx b/app/docs/[[...slug]]/page.tsx index b0b6cb8..06ae9c0 100644 --- a/app/docs/[[...slug]]/page.tsx +++ b/app/docs/[[...slug]]/page.tsx @@ -3,6 +3,7 @@ import type { Metadata } from "next"; import { DocsPage, DocsBody } from "next-docs-ui/page"; import { notFound } from "next/navigation"; + export default async function Page({ params, }: { diff --git a/app/layout.tsx b/app/layout.tsx index be6266b..df1b778 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,6 +3,7 @@ import { RootProvider } from "next-docs-ui/provider"; import { Inter } from "next/font/google"; import type { ReactNode } from "react"; import { Metadata } from "next"; +import { ThemeProvider } from "./Components/ThemeContext"; const inter = Inter({ subsets: ["latin"], @@ -32,7 +33,9 @@ export default function Layout({ children }: { children: ReactNode }) { return ( - {children} + + {children} + ); diff --git a/content/docs/Components/Sections/navbar.mdx b/content/docs/Components/Sections/navbar.mdx new file mode 100644 index 0000000..696e867 --- /dev/null +++ b/content/docs/Components/Sections/navbar.mdx @@ -0,0 +1,232 @@ +--- +title: Navbar +--- + +import StyledContainer from '../../../../app/Components/StyledContainer'; +import Navbar from '../../../../app/Components/Sections/Navbar'; +import CodeBlock from '../../../../app/DocsComponents/CodeBlock'; + +## Component + +(null) + + 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 ( + + ) +}`}> + + + +### For Dark - Light Mode + + void; +} + +const ThemeContext = createContext(undefined); + +export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [theme, setTheme] = useState('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 ( + + {children} + + ); +}; + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (context === undefined) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +};`} +> + + + + + + + {children} + + + + ); +}`}> + + +### Usage Methods + +1. **Pass** `navItems` as a prop to allow customisation of menu links. +2. **Use a JSON file for `navItems` data** to facilitate editing and extending links. +3. **Outsource the search component** to a separate component to improve modularity. +4. **Create a separate** `MobileMenu` component to manage the mobile menu and improve code readability. +5. **Pass the theme as a prop** and manage it in a higher-level component to make the component more flexible. +6. **Handle clicks outside the mobile menu in a custom hook** to make the code cleaner and reusable. +7. **Allow customisation of icons via props** to allow different icons to be used. +8. **Add a prop to pass callback functions for menu and theme events** to allow customised event handling. \ No newline at end of file diff --git a/content/docs/Components/codewindow.mdx b/content/docs/Components/codewindow.mdx index 0977b0e..d73a83b 100644 --- a/content/docs/Components/codewindow.mdx +++ b/content/docs/Components/codewindow.mdx @@ -8,7 +8,7 @@ import CodeWindow2 from '../../../app/Components/codeWindow'; -## Usage +### Usage ```tsx diff --git a/content/docs/Components/contactform.mdx b/content/docs/Components/contactform.mdx index dbfbfca..886abd0 100644 --- a/content/docs/Components/contactform.mdx +++ b/content/docs/Components/contactform.mdx @@ -138,7 +138,7 @@ export default ContactForm; `}> -## Usage Methods +### Usage Methods 1. **Pass the form data as a prop** to customise the form with different data. 2. **Extract the form fields into an array or JSON file** to make them dynamic and configurable. @@ -150,7 +150,7 @@ export default ContactForm; 8. **Manage data submission via an external API** and pass the URL as a prop. 9. **Implement debounce on input fields** to improve the user experience when typing. -## Usage +### Usage ```tsx import ContactForm from 'your-path'; diff --git a/content/docs/Components/searchbar.mdx b/content/docs/Components/searchbar.mdx index 93dc4b8..b2d64a3 100644 --- a/content/docs/Components/searchbar.mdx +++ b/content/docs/Components/searchbar.mdx @@ -152,7 +152,7 @@ export default function FilterableSearchBar() { -## Usage Methods +### Usage Methods This component has more usage methods that you could use: 1. **Passing `items` as prop to the component** instead of defining them directly in the code. @@ -166,7 +166,7 @@ This component has more usage methods that you could use: 9. **Accept a prop to customise the layout** (e.g. horizontal or vertical list) of the items. 10. **Use a JSON file to hold the data and insert it** into the search bar. -## Use the component +### Use the component ```tsx import FilterableSearchBar from 'your-path'; diff --git a/package-lock.json b/package-lock.json index 3f0b603..188ca27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "next-docs-mdx": "7.1.2", "next-docs-ui": "7.1.2", "next-docs-zeta": "7.1.2", + "next-themes": "^0.3.0", "react": "18.2.0", "react-dom": "18.2.0", "react-syntax-highlighter": "^15.5.0" @@ -27,7 +28,6 @@ "@types/react-syntax-highlighter": "^15.5.13", "autoprefixer": "10.4.16", "postcss": "8.4.32", - "prettier": "3.3.3", "tailwindcss": "3.3.7", "typescript": "5.3.3" } @@ -3944,6 +3944,16 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/next-docs-ui/node_modules/next-themes": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", + "integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==", + "peerDependencies": { + "next": "*", + "react": "*", + "react-dom": "*" + } + }, "node_modules/next-docs-ui/node_modules/postcss-selector-parser": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", @@ -4019,13 +4029,12 @@ } }, "node_modules/next-themes": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", - "integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz", + "integrity": "sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==", "peerDependencies": { - "next": "*", - "react": "*", - "react-dom": "*" + "react": "^16.8 || ^17 || ^18", + "react-dom": "^16.8 || ^17 || ^18" } }, "node_modules/next/node_modules/postcss": { @@ -4368,21 +4377,6 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, - "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/prismjs": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", diff --git a/tsconfig.json b/tsconfig.json index 47d1cd9..967dff4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,6 @@ } ] }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "content/docs/Components/Sections/navbar.mdx"], "exclude": ["node_modules"] }