update versi docubook
This commit is contained in:
@@ -25,11 +25,11 @@ const EditThisPage: React.FC<EditThisPageProps> = ({ filePath }) => {
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
<span className='text-primary text-sm max-[480px]:hidden'>Edit this page on Github</span>
|
||||
<span className='text-primary text-sm max-[480px]:hidden'>Edit this page</span>
|
||||
<SquarePenIcon className="w-4 h-4 text-primary" />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditThisPage;
|
||||
export default EditThisPage;
|
||||
56
components/docs/footer.tsx
Normal file
56
components/docs/footer.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import Link from "next/link";
|
||||
import { ModeToggle } from "@/components/docs/theme-toggle";
|
||||
import docuConfig from "@/docu.json";
|
||||
import * as LucideIcons from "lucide-react"; // Import all icons
|
||||
|
||||
export function Footer() {
|
||||
const { footer } = docuConfig;
|
||||
const { meta } = docuConfig;
|
||||
return (
|
||||
<footer className="w-full py-4 border-t lg:py-8 bg-background">
|
||||
<div className="container flex flex-wrap items-center justify-between text-sm">
|
||||
<div className="items-start justify-center hidden gap-4 lg:flex-col lg:flex lg:w-3/5">
|
||||
<h3 className="text-lg font-bold font-code">{meta.title}</h3>
|
||||
<span className="w-3/4 text-base text-wrap text-muted-foreground">{meta.description}</span>
|
||||
<div className="flex items-center gap-6 mt-2">
|
||||
<FooterButtons />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-start justify-center w-full gap-4 mt-4 xl:items-end lg:w-2/5">
|
||||
<p className="text-center text-muted-foreground">
|
||||
Copyright © {new Date().getFullYear()} {footer.copyright} - Made with{" "}
|
||||
<Link href="https://www.docubook.pro" target="_blank" rel="noopener noreferrer" className="underline underline-offset-2">
|
||||
DocuBook
|
||||
</Link>
|
||||
</p>
|
||||
<div className="hidden lg:flex">
|
||||
<ModeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
export function FooterButtons() {
|
||||
const { footer } = docuConfig;
|
||||
|
||||
return (
|
||||
<>
|
||||
{footer.social?.map((item) => {
|
||||
const IconComponent = (LucideIcons[item.iconName as keyof typeof LucideIcons] ?? LucideIcons["Globe"]) as unknown as React.FC<{ className?: string }>;
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={item.name}
|
||||
>
|
||||
<IconComponent className="w-4 h-4 text-gray-800 transition-colors dark:text-gray-400 hover:text-primary" />
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -6,12 +6,13 @@ import {
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import { Logo, NavMenu } from "./navbar";
|
||||
import { Button } from "./ui/button";
|
||||
import { Button } from "../ui/button";
|
||||
import { AlignLeftIcon } from "lucide-react";
|
||||
import { FooterButtons } from "./footer";
|
||||
import { DialogTitle } from "./ui/dialog";
|
||||
import { DialogTitle } from "../ui/dialog";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import DocsMenu from "./docs-menu";
|
||||
import { ModeToggle } from "./theme-toggle";
|
||||
|
||||
export function Leftbar() {
|
||||
return (
|
||||
@@ -45,9 +46,12 @@ export function SheetLeftbar() {
|
||||
<div className="mx-2 px-5">
|
||||
<DocsMenu isSheet />
|
||||
</div>
|
||||
<div className="p-6 pb-4 flex gap-2.5">
|
||||
<div className="px-6 py-2 flex justify-start items-center gap-6">
|
||||
<FooterButtons />
|
||||
</div>
|
||||
<div className="flex w-2/4 px-5">
|
||||
<ModeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
@@ -1,8 +1,6 @@
|
||||
import { ModeToggle } from "@/components/theme-toggle";
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { buttonVariants } from "./ui/button";
|
||||
import Search from "./search";
|
||||
import Anchor from "./anchor";
|
||||
import { SheetLeftbar } from "./leftbar";
|
||||
@@ -10,43 +8,24 @@ import { SheetClose } from "@/components/ui/sheet";
|
||||
import docuConfig from "@/docu.json"; // Import JSON
|
||||
|
||||
export function Navbar() {
|
||||
const { social } = docuConfig; // Extract navbar and social from JSON
|
||||
|
||||
return (
|
||||
<nav className="w-full border-b h-16 sticky top-0 z-50 bg-background">
|
||||
<nav className="sticky top-0 z-50 w-full h-16 border-b bg-background">
|
||||
<div className="sm:container mx-auto w-[95vw] h-full flex items-center justify-between md:gap-2">
|
||||
<div className="flex items-center gap-5">
|
||||
<SheetLeftbar />
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="sm:flex hidden">
|
||||
<div className="hidden sm:flex">
|
||||
<Logo />
|
||||
</div>
|
||||
<div className="lg:flex hidden items-center gap-4 text-sm font-medium text-muted-foreground">
|
||||
<div className="items-center hidden gap-4 text-sm font-medium lg:flex text-muted-foreground">
|
||||
<NavMenu />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Search />
|
||||
<div className="flex ml-2.5 sm:ml-0 gap-2">
|
||||
{social.map((item) => {
|
||||
const Icon = require("lucide-react")[item.iconName]; // Dynamically load icon
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
className={buttonVariants({ variant: "ghost", size: "icon" })}
|
||||
>
|
||||
<Icon className="h-[1.1rem] w-[1.1rem]" />
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
<ModeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -59,7 +38,7 @@ export function Logo() {
|
||||
return (
|
||||
<Link href="/" className="flex items-center gap-2.5">
|
||||
<Image src={navbar.logo.src} alt={navbar.logo.alt} width="24" height="24" />
|
||||
<h2 className="text-md font-bold font-code">{navbar.logoText}</h2>
|
||||
<h2 className="font-bold font-code text-md">{navbar.logoText}</h2>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -69,12 +48,12 @@ export function NavMenu({ isSheet = false }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{navbar.menu.map((item) => {
|
||||
{navbar?.menu?.map((item) => {
|
||||
const isExternal = item.href.startsWith("http");
|
||||
|
||||
const Comp = (
|
||||
<Anchor
|
||||
key={item.title + item.href}
|
||||
key={`${item.title}-${item.href}`}
|
||||
activeClassName="!text-primary md:font-semibold font-medium"
|
||||
absolute
|
||||
className="flex items-center gap-1 dark:text-stone-300/85 text-stone-800"
|
||||
@@ -83,7 +62,7 @@ export function NavMenu({ isSheet = false }) {
|
||||
rel={isExternal ? "noopener noreferrer" : undefined}
|
||||
>
|
||||
{item.title}
|
||||
{isExternal && <ArrowUpRight className="h-4 w-4 text-muted-foreground" />}
|
||||
{isExternal && <ArrowUpRight className="w-4 h-4 text-muted-foreground" />}
|
||||
</Anchor>
|
||||
);
|
||||
return isSheet ? (
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getPreviousNext } from "@/lib/markdown";
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { buttonVariants } from "./ui/button";
|
||||
import { buttonVariants } from "../ui/button";
|
||||
|
||||
export default function Pagination({ pathname }: { pathname: string }) {
|
||||
const res = getPreviousNext(pathname);
|
||||
@@ -46,4 +46,4 @@ export default function Pagination({ pathname }: { pathname: string }) {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { ArrowUpIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Button } from "../ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function ScrollToTop() {
|
||||
@@ -96,14 +96,14 @@ export default function Search() {
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<div className="relative flex-1 cursor-pointer sm:w-60">
|
||||
<div className="relative flex-1 cursor-pointer max-w-[160px]">
|
||||
<SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-stone-500 dark:text-stone-400" />
|
||||
<Input
|
||||
className="md:w-full rounded-md dark:bg-background/95 bg-background border h-9 pl-10 pr-0 sm:pr-4 text-sm shadow-sm overflow-ellipsis"
|
||||
placeholder="Search"
|
||||
type="search"
|
||||
/>
|
||||
<div className="sm:flex hidden absolute top-1/2 -translate-y-1/2 right-2 text-xs font-medium font-mono items-center gap-0.5 dark:bg-black dark:border dark:border-white/20 bg-stone-200/50 border border-black/40 p-1 rounded-sm">
|
||||
<div className="flex absolute top-1/2 -translate-y-1/2 right-2 text-xs font-medium font-mono items-center gap-0.5 dark:bg-black dark:border dark:border-white/20 bg-stone-200/50 border border-black/40 p-1 rounded-sm">
|
||||
<CommandIcon className="w-3 h-3" />
|
||||
<span>K</span>
|
||||
</div>
|
||||
71
components/docs/theme-toggle.tsx
Normal file
71
components/docs/theme-toggle.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Moon, Sun, Monitor } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
|
||||
export function ModeToggle() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
const [selectedTheme, setSelectedTheme] = React.useState<string>("system");
|
||||
|
||||
// Pastikan toggle tetap di posisi yang benar setelah reload
|
||||
React.useEffect(() => {
|
||||
if (theme) {
|
||||
setSelectedTheme(theme);
|
||||
} else {
|
||||
setSelectedTheme("system"); // Default ke system jika undefined
|
||||
}
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={selectedTheme}
|
||||
onValueChange={(value) => {
|
||||
if (value) {
|
||||
setTheme(value);
|
||||
setSelectedTheme(value);
|
||||
}
|
||||
}}
|
||||
className="flex items-center gap-1 rounded-full border border-gray-300 dark:border-gray-700 p-1 transition-all"
|
||||
>
|
||||
<ToggleGroupItem
|
||||
value="light"
|
||||
size="sm"
|
||||
aria-label="Light Mode"
|
||||
className={`rounded-full p-1 transition-all ${
|
||||
selectedTheme === "light"
|
||||
? "bg-blue-500 text-white"
|
||||
: "bg-transparent"
|
||||
}`}
|
||||
>
|
||||
<Sun className={`h-4 w-4 ${selectedTheme === "light" ? "text-white" : "text-gray-600 dark:text-gray-300"}`} />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="system"
|
||||
size="sm"
|
||||
aria-label="System Mode"
|
||||
className={`rounded-full p-1 transition-all ${
|
||||
selectedTheme === "system"
|
||||
? "bg-blue-500 text-white"
|
||||
: "bg-transparent"
|
||||
}`}
|
||||
>
|
||||
<Monitor className={`h-4 w-4 ${selectedTheme === "system" ? "text-white" : "text-gray-600 dark:text-gray-300"}`} />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="dark"
|
||||
size="sm"
|
||||
aria-label="Dark Mode"
|
||||
className={`rounded-full p-1 transition-all ${
|
||||
selectedTheme === "dark"
|
||||
? "bg-blue-500 text-white"
|
||||
: "bg-transparent"
|
||||
}`}
|
||||
>
|
||||
<Moon className={`h-4 w-4 ${selectedTheme === "dark" ? "text-white" : "text-gray-600 dark:text-gray-300"}`} />
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import { buttonVariants } from "./ui/button";
|
||||
import docuConfig from "@/docu.json"; // Import JSON
|
||||
|
||||
export function Footer() {
|
||||
const { footer } = docuConfig; // Extract footer from JSON
|
||||
|
||||
return (
|
||||
<footer className="border-t w-full h-16">
|
||||
<div className="container flex items-center sm:justify-between justify-center sm:gap-0 gap-4 h-full text-muted-foreground text-sm flex-wrap sm:py-0 py-3 max-sm:px-4">
|
||||
{/* Footer Text */}
|
||||
<div className="flex items-center gap-3">
|
||||
<p className="text-center">
|
||||
Copyright © {new Date().getFullYear()} {footer.copyright} - Crafted with love using{" "}
|
||||
<Link
|
||||
href="https://www.docubook.pro"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline underline-offset-2"
|
||||
>
|
||||
DocuBook
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Footer Buttons */}
|
||||
<div className="gap-4 items-center hidden md:flex">
|
||||
<FooterButtons />
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
export function FooterButtons() {
|
||||
const { footer } = docuConfig; // Extract footer from JSON
|
||||
|
||||
return (
|
||||
<>
|
||||
{footer.buttons.map((button, index) => {
|
||||
const Icon = require("lucide-react")[button.iconName]; // Dynamically load icon
|
||||
return (
|
||||
<Link
|
||||
key={index}
|
||||
href={button.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={buttonVariants({ variant: "outline", size: "sm" })}
|
||||
>
|
||||
<Icon className="h-4 w-4 mr-2 dark:text-primary dark:hover:text-accent-foreground" />
|
||||
{button.text}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
33
components/home/copycommand.tsx
Normal file
33
components/home/copycommand.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { TerminalSquareIcon, ClipboardIcon, CheckIcon } from "lucide-react";
|
||||
|
||||
export function CopyCommand() {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const command = "npx @docubook/create@latest";
|
||||
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard.writeText(command);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 5000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-row items-center justify-center sm:gap-2 gap-0.5 text-muted-foreground text-md mt-10 mb-12 font-code text-base font-medium group">
|
||||
<TerminalSquareIcon className="w-5 h-5 mr-1 mt-0.5" />
|
||||
<span className="select-all">{command}</span>
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="p-1 ml-1 transition-opacity rounded-md opacity-0 group-hover:opacity-100 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
>
|
||||
{copied ? (
|
||||
<CheckIcon className="w-4 h-4 text-green-500" />
|
||||
) : (
|
||||
<ClipboardIcon className="w-4 h-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default CopyCommand;
|
||||
91
components/home/runtime.tsx
Normal file
91
components/home/runtime.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Play } from "lucide-react";
|
||||
|
||||
const scriptOutput = [
|
||||
"DocuBook CLI Installer",
|
||||
"✔ Enter your project directory name: docubook",
|
||||
"? Choose a package manager:",
|
||||
"> npm",
|
||||
" pnpm",
|
||||
" yarn",
|
||||
" bun",
|
||||
"",
|
||||
":: Cloning starter from GitLab...",
|
||||
"✔ Docubook project successfully created!",
|
||||
"Skipping rename postcss.config.js because Bun is not installed.",
|
||||
"",
|
||||
"[ DocuBook Version 1.8.0 ]",
|
||||
"",
|
||||
"Starting the installation process...",
|
||||
"Installation | ████████████████████████████████ | 100% 100/100",
|
||||
"",
|
||||
"Dependencies installed successfully using npm!",
|
||||
"",
|
||||
"┌────────────────────────────────────┐",
|
||||
"│ Next Steps: │",
|
||||
"│ │",
|
||||
"│ 1. Navigate to project directory: │",
|
||||
"│ cd docubook │",
|
||||
"│ │",
|
||||
"│ 2. Install dependencies: │",
|
||||
"│ npm install │",
|
||||
"│ │",
|
||||
"│ 3. Start the development server: │",
|
||||
"│ npm run dev │",
|
||||
"└────────────────────────────────────┘"
|
||||
];
|
||||
|
||||
export function RuntimeSimulator() {
|
||||
const [logs, setLogs] = useState<string[]>([]);
|
||||
const [running, setRunning] = useState(false);
|
||||
const terminalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (running) {
|
||||
setLogs([]);
|
||||
scriptOutput.forEach((line, index) => {
|
||||
setTimeout(() => {
|
||||
setLogs((prev) => [...prev, line]);
|
||||
}, index * 500);
|
||||
});
|
||||
}
|
||||
}, [running]);
|
||||
|
||||
useEffect(() => {
|
||||
terminalRef.current?.scrollTo({ top: terminalRef.current.scrollHeight, behavior: "smooth" });
|
||||
}, [logs]);
|
||||
|
||||
return (
|
||||
<div className="bg-gray-900 text-green-400 font-mono rounded-lg overflow-hidden w-full h-auto max-w-2xl shadow-lg">
|
||||
<div className="bg-gray-800 text-gray-300 px-4 py-2 flex items-center space-x-2">
|
||||
<div className="flex space-x-1">
|
||||
<span className="w-3 h-3 bg-red-500 rounded-full"></span>
|
||||
<span className="w-3 h-3 bg-yellow-500 rounded-full"></span>
|
||||
<span className="w-3 h-3 bg-green-500 rounded-full"></span>
|
||||
</div>
|
||||
<span className="ml-3">docubook@localhost</span>
|
||||
</div>
|
||||
<div ref={terminalRef} className="p-4 md:h-[400px] h-72 overflow-y-auto text-left">
|
||||
{logs.map((log, index) => (
|
||||
<pre key={index} className="whitespace-pre-wrap">{log}</pre>
|
||||
))}
|
||||
</div>
|
||||
<div className="bg-gray-800 p-2 flex items-center space-x-2">
|
||||
<input
|
||||
type="text"
|
||||
value="npx @docubook/create@latest"
|
||||
readOnly
|
||||
className="bg-gray-700 text-gray-300 px-2 py-1 rounded w-full text-left"
|
||||
/>
|
||||
<Button onClick={() => setRunning(true)} className="bg-green-600 hover:bg-green-500 px-4 py-1">
|
||||
<Play className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RuntimeSimulator;
|
||||
@@ -1,48 +0,0 @@
|
||||
import {
|
||||
AnimatedSpan,
|
||||
Terminal,
|
||||
TypingAnimation,
|
||||
} from "@/components/ui/terminal";
|
||||
|
||||
export function NpxTerminal() {
|
||||
return (
|
||||
<Terminal>
|
||||
<TypingAnimation className="text-left pl-6 dark:text-blue-300 text-blue-600">> npx @docubook/create@latest</TypingAnimation>
|
||||
|
||||
<AnimatedSpan delay={1500} className="text-muted-foreground text-left pl-6">
|
||||
<span>Need to install the following packages:</span>
|
||||
</AnimatedSpan>
|
||||
|
||||
<AnimatedSpan delay={2000} className="text-muted-foreground text-left pl-6">
|
||||
<span>@docubook/create@1.4.0</span>
|
||||
</AnimatedSpan>
|
||||
|
||||
<AnimatedSpan delay={2500} className="text-muted-foreground text-left pl-6">
|
||||
<span>Ok to proceed? (y)</span>
|
||||
</AnimatedSpan>
|
||||
|
||||
<AnimatedSpan delay={3000} className="dark:text-blue-300 text-blue-600 text-left pl-6">
|
||||
<span>✔ ? Enter a name for your project directory: (docubook)</span>
|
||||
</AnimatedSpan>
|
||||
|
||||
<AnimatedSpan delay={3500} className="text-muted-foreground text-left pl-6">
|
||||
<span>Creating a new Docubook project in /path/your/docubook from the starter branch...</span>
|
||||
</AnimatedSpan>
|
||||
|
||||
<AnimatedSpan delay={4000} className="text-muted-foreground text-left pl-6">
|
||||
<span>✔ Docubook project successfully created in /path/your/docubook!</span>
|
||||
</AnimatedSpan>
|
||||
|
||||
<AnimatedSpan delay={6000} className="text-foreground text-left pl-6">
|
||||
<span>Next Step</span>
|
||||
<span className="pl-2 dark:text-blue-300 text-blue-600">1. Navigate to your project directory: cd docubook</span>
|
||||
<span className="pl-2 dark:text-blue-300 text-blue-600">2. Install dependencies: npm install</span>
|
||||
<span className="pl-2 dark:text-blue-300 text-blue-600">3. Start the development server: npm run dev</span>
|
||||
</AnimatedSpan>
|
||||
|
||||
<TypingAnimation delay={6500} className="text-muted-foreground text-left pl-6">
|
||||
Open the apps via browser http://localhost:3000.
|
||||
</TypingAnimation>
|
||||
</Terminal>
|
||||
);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
export function ModeToggle() {
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Sun className="h-[1.1rem] w-[1.1rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Moon className="absolute h-[1.1rem] w-[1.1rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => setTheme("light")}>
|
||||
Light
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
||||
Dark
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme("system")}>
|
||||
System
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
61
components/ui/toggle-group.tsx
Normal file
61
components/ui/toggle-group.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
|
||||
import { type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { toggleVariants } from "@/components/ui/toggle"
|
||||
|
||||
const ToggleGroupContext = React.createContext<
|
||||
VariantProps<typeof toggleVariants>
|
||||
>({
|
||||
size: "default",
|
||||
variant: "default",
|
||||
})
|
||||
|
||||
const ToggleGroup = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, children, ...props }, ref) => (
|
||||
<ToggleGroupPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("flex items-center justify-center gap-1", className)}
|
||||
{...props}
|
||||
>
|
||||
<ToggleGroupContext.Provider value={{ variant, size }}>
|
||||
{children}
|
||||
</ToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
))
|
||||
|
||||
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
|
||||
|
||||
const ToggleGroupItem = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, children, variant, size, ...props }, ref) => {
|
||||
const context = React.useContext(ToggleGroupContext)
|
||||
|
||||
return (
|
||||
<ToggleGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
toggleVariants({
|
||||
variant: context.variant || variant,
|
||||
size: context.size || size,
|
||||
}),
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ToggleGroupPrimitive.Item>
|
||||
)
|
||||
})
|
||||
|
||||
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
|
||||
|
||||
export { ToggleGroup, ToggleGroupItem }
|
||||
46
components/ui/toggle.tsx
Normal file
46
components/ui/toggle.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const toggleVariants = cva(
|
||||
"inline-flex items-center justify-center gap-1 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
"border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-2 min-w-9",
|
||||
sm: "h-8 px-1.5 min-w-8",
|
||||
xs: "h-7 px-1 min-w-7",
|
||||
lg: "h-10 px-2.5 min-w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const Toggle = React.forwardRef<
|
||||
React.ElementRef<typeof TogglePrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, ...props }, ref) => (
|
||||
<TogglePrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(toggleVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
Toggle.displayName = TogglePrimitive.Root.displayName
|
||||
|
||||
export { Toggle, toggleVariants }
|
||||
Reference in New Issue
Block a user