My Shadcn/ui powered Components

Header navigation

Requirements

bash
pnpm dlx shadcn@latest add button drawer separator

Components

tsx
"use client"; import type { ReactNode } from "react"; import { usePathname } from "next/navigation"; import { cn } from "@/lib/utils"; import Link from "next/link"; interface NavLinkProps { href: string; className?: string; children: React.ReactNode; mobile?: boolean; } export function NavLink({ href, className, children, mobile, ...props }: NavLinkProps): ReactNode | null { const pathname = usePathname(); const isActive = href === "/" ? pathname === "/" : pathname === href || pathname.startsWith(`${href}/`); return ( <Link className={cn( "hover:text-primary text-base capitalize transition-colors", isActive ? "text-primary font-bold" : "text-foreground", isActive && mobile && "bg-primary text-white", mobile && "border p-4", className, )} href={href} {...props} > {children} </Link> ); } ```</ToggleList> <br /> <ToggleList summary={<Color type="boolean">@/components/navigation/main-nav.tsx</Color>}> ```tsx import { navigation } from "@/config"; import { Button } from "../ui/button"; import { NavLink } from "./nav-link"; import Link from "next/link"; export default function MainNav() { return ( <nav className="hidden items-center gap-6 text-sm md:flex"> <div className="flex gap-6"> {navigation.map((link) => ( <NavLink key={link.href} href={link.href}> {link.label} </NavLink> ))} </div> {/* Call to action button */} <Button asChild className="bg-primary px-6"> <Link href="/join">Call to action!</Link> </Button> </nav> ); }

tsx
import { Menu } from "lucide-react"; import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger, } from "@/components/ui/drawer"; import { Button } from "@/components/ui/button"; import { navigation } from "@/config"; import { Logo } from "@/assets/vectors"; import { Separator } from "@/components/ui/separator"; import { NavLink } from "./nav-link"; export default function MobileNav() { return ( <Drawer> <DrawerTrigger className="block md:hidden"> <Menu /> </DrawerTrigger> <DrawerContent> <DrawerHeader> <DrawerTitle>Navigation</DrawerTitle> <DrawerDescription>Page navigation</DrawerDescription> </DrawerHeader> <div className="px-4"> <Separator /> </div> <nav className="flex max-h-[50vh] w-full flex-col gap-4 overflow-auto p-4"> {navigation.map((link) => ( <NavLink mobile key={link.href} href={link.href}> {link.label} </NavLink> ))} </nav> <div className="px-4"> <Separator /> </div> <DrawerFooter> <NavLink href="/" className="text-primary mb-1 flex items-center gap-2 self-center font-semibold" > <Logo className="fill-primary" width={36} height={36} /> <span className="text-2xl font-bold">My page</span> </NavLink> <DrawerClose asChild> <Button variant="outline">Close</Button> </DrawerClose> </DrawerFooter> </DrawerContent> </Drawer> ); }

tsx
import { Logo } from "@/assets/vectors"; import { NavLink } from "./navigation/nav-link"; import MainNav from "./navigation/main-nav"; import MobileNav from "./navigation/mobile-nav"; export default function Header() { return ( <header className="w-full"> <div className="mx-6 flex items-center py-6 md:mx-10"> <div className="flex w-full items-center justify-between gap-6"> <NavLink href="/" className="text-primary flex items-center gap-2 font-semibold" > <Logo className="fill-primary" width={36} height={36} /> <span className="text-2xl font-bold">My Page</span> </NavLink> <MainNav /> <MobileNav /> </div> </div> </header> ); }


Dialog Provider

Requirements

bash
pnpm dlx shadcn@latest add dialog

tsx
"use client"; import { createContext, useContext, useState, type ReactNode } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "@/components/ui/dialog"; type DialogOptions = { title?: string; description?: string; content?: () => ReactNode; }; type DialogContextType = { openDialog: (options: DialogOptions) => void; closeDialog: () => void; }; const DialogContext = createContext<DialogContextType | undefined>(undefined); export const DialogProvider = ({ children }: { children: ReactNode }) => { const [isOpen, setIsOpen] = useState(false); const [dialogContent, setDialogContent] = useState<DialogOptions>({}); const openDialog = (options: DialogOptions) => { setDialogContent(options); setIsOpen(true); }; const closeDialog = () => { setIsOpen(false); // Delay unmounting until after dialog transition ends setTimeout(() => { setDialogContent({}); }, 200); // Match Dialog exit animation duration }; return ( <DialogContext.Provider value={{ openDialog, closeDialog }}> {children} <Dialog open={isOpen} onOpenChange={setIsOpen}> <DialogContent className="max-h-[90vh]"> <DialogHeader> {dialogContent.title && ( <DialogTitle>{dialogContent.title}</DialogTitle> )} {dialogContent.description && ( <DialogDescription>{dialogContent.description}</DialogDescription> )} </DialogHeader> {dialogContent.content?.()} </DialogContent> </Dialog> </DialogContext.Provider> ); }; export const useDialog = (): DialogContextType => { const context = useContext(DialogContext); if (!context) { throw new Error("useDialog must be used within a DialogProvider"); } return context; };


Multi-value inputs

tsx
import * as React from "react"; import { X } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; interface FeatureInputProps { value: string[]; onChange: (features: string[]) => void; placeholder?: string; } export function FeatureInput({ value = [], onChange, placeholder, }: FeatureInputProps) { const [inputValue, setInputValue] = React.useState(""); const inputRef = React.useRef<HTMLInputElement>(null); const handleAddFeature = () => { if (inputValue.trim() !== "" && !value.includes(inputValue.trim())) { onChange([...value, inputValue.trim()]); setInputValue(""); } }; const handleRemoveFeature = (featureToRemove: string) => { onChange(value.filter((feature) => feature !== featureToRemove)); }; const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { if (e.key === "Enter") { e.preventDefault(); handleAddFeature(); } }; return ( <div> <div className="mb-4 flex items-center gap-2"> <Input ref={inputRef} value={inputValue} onChange={(e) => setInputValue(e.target.value)} onKeyDown={handleKeyDown} placeholder={placeholder ?? "Add a feature..."} /> <Button type="button" onClick={handleAddFeature}> Add </Button> </div> <div className="flex flex-wrap gap-2"> {value.map((feature, index) => ( <Badge key={index} variant="secondary"> {feature} <button type="button" className="ring-offset-background focus:ring-ring ml-2 rounded-full outline-none focus:ring-2 focus:ring-offset-2" onClick={() => handleRemoveFeature(feature)} > <X className="text-muted-foreground hover:text-foreground h-3 w-3" /> </button> </Badge> ))} </div> </div> ); }


Miscellaneous Snippets

Scroll for Chrome

css
::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background-color: rgba(100, 100, 100, 0.4); border-radius: 9999px; border: 2px solid transparent; /* optional padding */ background-clip: content-box; } ::-webkit-scrollbar-thumb:hover { background-color: rgba(100, 100, 100, 0.6); }

Loader

tsx
<Loader2 className="mr-2 h-6 w-6 animate-spin" />