From 039d8d5a50eb49361a60ab25c6a4ea3a6a41cbae Mon Sep 17 00:00:00 2001 From: Bot DocuBook Date: Sun, 18 Jan 2026 13:42:31 +0000 Subject: [PATCH] chore: Sync package version v2.0.0-beta.1 --- app/docs/[[...slug]]/page.tsx | 20 +- app/layout.tsx | 3 + app/page.tsx | 12 +- components/SearchModal.tsx | 78 +++--- components/context-popover.tsx | 3 +- components/contexts/theme-provider.tsx | 3 +- components/leftbar.tsx | 2 +- components/markdown/AccordionMdx.tsx | 12 +- components/markdown/CardGroupMdx.tsx | 12 +- components/markdown/FileTreeMdx.tsx | 4 +- components/markdown/ImageMdx.tsx | 6 +- components/markdown/PreMdx.tsx | 2 +- components/mob-toc.tsx | 2 +- components/scroll-to-top.tsx | 1 + components/sublink.tsx | 1 + components/theme-toggle.tsx | 75 +++--- components/ui/icon-cloud.tsx | 324 ----------------------- eslint.config.mjs | 35 +++ hooks/useScrollPosition.ts | 11 +- package.json | 83 +++--- postcss.config.js | 2 +- styles/globals.css | 341 +++++++++++++++++-------- tailwind.config.ts | 212 +++++++-------- tsconfig.json | 27 +- 24 files changed, 579 insertions(+), 692 deletions(-) delete mode 100644 components/ui/icon-cloud.tsx create mode 100644 eslint.config.mjs diff --git a/app/docs/[[...slug]]/page.tsx b/app/docs/[[...slug]]/page.tsx index c4f6f05..47a49cc 100644 --- a/app/docs/[[...slug]]/page.tsx +++ b/app/docs/[[...slug]]/page.tsx @@ -12,13 +12,19 @@ import MobToc from "@/components/mob-toc"; const { meta } = docuConfig; type PageProps = { - params: { + params: Promise<{ slug: string[]; - }; + }>; }; // Function to generate metadata dynamically -export async function generateMetadata({ params: { slug = [] } }: PageProps) { +export async function generateMetadata(props: PageProps) { + const params = await props.params; + + const { + slug = [] + } = params; + const pathName = slug.join("/"); const res = await getDocsForSlug(pathName); @@ -62,7 +68,13 @@ export async function generateMetadata({ params: { slug = [] } }: PageProps) { }; } -export default async function DocsPage({ params: { slug = [] } }: PageProps) { +export default async function DocsPage(props: PageProps) { + const params = await props.params; + + const { + slug = [] + } = params; + const pathName = slug.join("/"); const res = await getDocsForSlug(pathName); diff --git a/app/layout.tsx b/app/layout.tsx index 2eb4c75..79d3fb4 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -6,6 +6,9 @@ import { GeistMono } from "geist/font/mono"; import { Footer } from "@/components/footer"; import docuConfig from "@/docu.json"; import { Toaster } from "@/components/ui/sonner"; +import "@docsearch/css"; +import "@/styles/algolia.css"; +import "@/styles/syntax.css"; import "@/styles/globals.css"; const { meta } = docuConfig; diff --git a/app/page.tsx b/app/page.tsx index 10c9130..7049c9f 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -25,21 +25,21 @@ export default function Home() { )} > - 🚀 New Version - Release v1.16.1 + 🚀 Release v2.0.0-beta.1 -
+

DocuBook Starter Templates

- Get started by editing app/page.tsx . Save and see your changes instantly.{' '} - + Get started by editing app/page.tsx . Save and see your changes instantly.{' '} + Read Documentations - +

-
+
{ if (!isOpen) { + // eslint-disable-next-line react-hooks/set-state-in-effect setSearchedInput(""); } }, [isOpen]); @@ -71,9 +72,9 @@ export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) { return advanceSearch(trimmedInput) as unknown as SearchResult[]; }, [searchedInput]); - useEffect(() => { - setSelectedIndex(0); - }, [filteredResults]); + // useEffect(() => { + // setSelectedIndex(0); + // }, [filteredResults]); useEffect(() => { const handleNavigation = (event: KeyboardEvent) => { @@ -114,10 +115,13 @@ export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) { Search Documentation Search through the documentation - + setSearchedInput(e.target.value)} + onChange={(e) => { + setSearchedInput(e.target.value); + setSelectedIndex(0); + }} placeholder="Type something to search..." autoFocus className="h-14 px-6 bg-transparent border-b text-[14px] outline-none w-full" @@ -138,38 +142,38 @@ export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) { const isActive = index === selectedIndex; return ( - - { - itemRefs.current[index] = el as HTMLDivElement | null; - }} + + { + itemRefs.current[index] = el as HTMLDivElement | null; + }} + className={cn( + "dark:hover:bg-accent/15 hover:bg-accent/10 w-full px-3 rounded-sm text-sm flex items-center gap-2.5", + isActive && "bg-primary/20 dark:bg-primary/30", + paddingClass + )} + href={`/docs${item.href}`} + tabIndex={-1} + > +
1 && "border-l pl-4" )} - href={`/docs${item.href}`} - tabIndex={-1} - > -
1 && "border-l pl-4" - )} - > -
- - {item.title} -
- {isActive && ( -
- Return - -
- )} + > +
+ + {item.title}
- - + {isActive && ( +
+ Return + +
+ )} +
+ + ); })}
@@ -177,14 +181,14 @@ export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) {
- + - +

to navigate

- +

to select

diff --git a/components/context-popover.tsx b/components/context-popover.tsx index bbc454a..14ce4e7 100644 --- a/components/context-popover.tsx +++ b/components/context-popover.tsx @@ -45,6 +45,7 @@ export default function ContextPopover({ className }: ContextPopoverProps) { useEffect(() => { if (pathname.startsWith("/docs")) { + // eslint-disable-next-line react-hooks/set-state-in-effect setActiveRoute(getActiveContextRoute(pathname)); } else { setActiveRoute(undefined); @@ -61,7 +62,7 @@ export default function ContextPopover({ className }: ContextPopoverProps) { {isOpen && ( diff --git a/components/markdown/CardGroupMdx.tsx b/components/markdown/CardGroupMdx.tsx index a1423b8..3dda4fe 100644 --- a/components/markdown/CardGroupMdx.tsx +++ b/components/markdown/CardGroupMdx.tsx @@ -8,13 +8,21 @@ interface CardGroupProps { } const CardGroup: React.FC = ({ children, cols = 2, className }) => { - const cardsArray = React.Children.toArray(children); // Pastikan children berupa array + const cardsArray = React.Children.toArray(children); + + // Static grid column classes for Tailwind v4 compatibility + const gridColsClass = { + 1: "grid-cols-1", + 2: "grid-cols-1 sm:grid-cols-2", + 3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", + 4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4", + }[cols] || "grid-cols-1 sm:grid-cols-2"; return (
diff --git a/components/markdown/FileTreeMdx.tsx b/components/markdown/FileTreeMdx.tsx index 6abdd3b..ee5ab79 100644 --- a/components/markdown/FileTreeMdx.tsx +++ b/components/markdown/FileTreeMdx.tsx @@ -100,8 +100,8 @@ export const Files = ({ children }: { children: ReactNode }) => { return (
["height"]; type Width = ComponentProps["width"]; +type ImageProps = Omit, "src"> & { + src?: ComponentProps["src"]; +}; + export default function Image({ src, alt = "alt", width = 800, height = 350, ...props -}: ComponentProps<"img">) { +}: ImageProps) { if (!src) return null; return ( , callback: () => void) => { +const useClickOutside = (ref: React.RefObject, callback: () => void) => { const handleClick = React.useCallback((event: MouseEvent) => { if (ref.current && !ref.current.contains(event.target as Node)) { callback(); diff --git a/components/scroll-to-top.tsx b/components/scroll-to-top.tsx index e072451..9dcd60d 100644 --- a/components/scroll-to-top.tsx +++ b/components/scroll-to-top.tsx @@ -32,6 +32,7 @@ export function ScrollToTop({ useEffect(() => { // Initial check + // eslint-disable-next-line react-hooks/set-state-in-effect checkScroll(); // Set up scroll listener with debounce for better performance diff --git a/components/sublink.tsx b/components/sublink.tsx index eb5a425..ffb9195 100644 --- a/components/sublink.tsx +++ b/components/sublink.tsx @@ -44,6 +44,7 @@ export default function SubLink({ // Auto-expand if current path is a child of this item useEffect(() => { if (items && (path.startsWith(fullHref) && path !== fullHref)) { + // eslint-disable-next-line react-hooks/set-state-in-effect setIsOpen(true); } }, [path, fullHref, items]); diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx index fa4c740..faee41a 100644 --- a/components/theme-toggle.tsx +++ b/components/theme-toggle.tsx @@ -1,68 +1,69 @@ "use client"; import * as React from "react"; -import { Moon, Sun, Monitor } from "lucide-react"; +import { Moon, Sun } 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("system"); + const { theme, setTheme, resolvedTheme } = useTheme(); + const [mounted, setMounted] = React.useState(false); - // Pastikan toggle tetap di posisi yang benar setelah reload + // Untuk menghindari hydration mismatch React.useEffect(() => { - if (theme) { - setSelectedTheme(theme); + setMounted(true); + }, []); + + // Jika belum mounted, jangan render apapun untuk menghindari mismatch + if (!mounted) { + return ( +
+
+
+
+ ); + } + + // Tentukan theme yang aktif: gunakan resolvedTheme untuk menampilkan ikon yang sesuai + // jika theme === "system", resolvedTheme akan menjadi "light" atau "dark" sesuai device + const activeTheme = theme === "system" || !theme ? resolvedTheme : theme; + + const handleToggle = () => { + // Toggle antara light dan dark + // Jika sekarang light, ganti ke dark, dan sebaliknya + if (activeTheme === "light") { + setTheme("dark"); } else { - setSelectedTheme("system"); // Default ke system jika undefined + setTheme("light"); } - }, [theme]); + }; return ( { - if (value) { - setTheme(value); - setSelectedTheme(value); - } - }} + value={activeTheme} + onValueChange={handleToggle} className="flex items-center gap-1 rounded-full border border-border bg-background/50 p-1 transition-all" > - - - diff --git a/components/ui/icon-cloud.tsx b/components/ui/icon-cloud.tsx deleted file mode 100644 index 1f6136e..0000000 --- a/components/ui/icon-cloud.tsx +++ /dev/null @@ -1,324 +0,0 @@ -"use client"; - -import React, { useEffect, useRef, useState } from "react"; -import { renderToString } from "react-dom/server"; - -interface Icon { - x: number; - y: number; - z: number; - scale: number; - opacity: number; - id: number; -} - -interface IconCloudProps { - icons?: React.ReactNode[]; - images?: string[]; -} - -function easeOutCubic(t: number): number { - return 1 - Math.pow(1 - t, 3); -} - -export function IconCloud({ icons, images }: IconCloudProps) { - const canvasRef = useRef(null); - const [iconPositions, setIconPositions] = useState([]); - const [rotation] = useState({ x: 0, y: 0 }); - const [isDragging, setIsDragging] = useState(false); - const [lastMousePos, setLastMousePos] = useState({ x: 0, y: 0 }); - const [mousePos, setMousePos] = useState({ x: 0, y: 0 }); - const [targetRotation, setTargetRotation] = useState<{ - x: number; - y: number; - startX: number; - startY: number; - distance: number; - startTime: number; - duration: number; - } | null>(null); - const animationFrameRef = useRef(); - const rotationRef = useRef(rotation); - const iconCanvasesRef = useRef([]); - const imagesLoadedRef = useRef([]); - - // Create icon canvases once when icons/images change - useEffect(() => { - if (!icons && !images) return; - - const items = icons || images || []; - imagesLoadedRef.current = new Array(items.length).fill(false); - - const newIconCanvases = items.map((item, index) => { - const offscreen = document.createElement("canvas"); - offscreen.width = 40; - offscreen.height = 40; - const offCtx = offscreen.getContext("2d"); - - if (offCtx) { - if (images) { - // Handle image URLs directly - const img = new Image(); - img.crossOrigin = "anonymous"; - img.src = items[index] as string; - img.onload = () => { - offCtx.clearRect(0, 0, offscreen.width, offscreen.height); - - // Create circular clipping path - offCtx.beginPath(); - offCtx.arc(20, 20, 20, 0, Math.PI * 2); - offCtx.closePath(); - offCtx.clip(); - - // Draw the image - offCtx.drawImage(img, 0, 0, 40, 40); - - imagesLoadedRef.current[index] = true; - }; - } else { - // Handle SVG icons - offCtx.scale(0.4, 0.4); - const svgString = renderToString(item as React.ReactElement); - const img = new Image(); - img.src = "data:image/svg+xml;base64," + btoa(svgString); - img.onload = () => { - offCtx.clearRect(0, 0, offscreen.width, offscreen.height); - offCtx.drawImage(img, 0, 0); - imagesLoadedRef.current[index] = true; - }; - } - } - return offscreen; - }); - - iconCanvasesRef.current = newIconCanvases; - }, [icons, images]); - - // Generate initial icon positions on a sphere - useEffect(() => { - const items = icons || images || []; - const newIcons: Icon[] = []; - const numIcons = items.length || 20; - - // Fibonacci sphere parameters - const offset = 2 / numIcons; - const increment = Math.PI * (3 - Math.sqrt(5)); - - for (let i = 0; i < numIcons; i++) { - const y = i * offset - 1 + offset / 2; - const r = Math.sqrt(1 - y * y); - const phi = i * increment; - - const x = Math.cos(phi) * r; - const z = Math.sin(phi) * r; - - newIcons.push({ - x: x * 100, - y: y * 100, - z: z * 100, - scale: 1, - opacity: 1, - id: i, - }); - } - setIconPositions(newIcons); - }, [icons, images]); - - // Handle mouse events - const handleMouseDown = (e: React.MouseEvent) => { - const rect = canvasRef.current?.getBoundingClientRect(); - if (!rect || !canvasRef.current) return; - - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; - - const ctx = canvasRef.current.getContext("2d"); - if (!ctx) return; - - iconPositions.forEach((icon) => { - const cosX = Math.cos(rotationRef.current.x); - const sinX = Math.sin(rotationRef.current.x); - const cosY = Math.cos(rotationRef.current.y); - const sinY = Math.sin(rotationRef.current.y); - - const rotatedX = icon.x * cosY - icon.z * sinY; - const rotatedZ = icon.x * sinY + icon.z * cosY; - const rotatedY = icon.y * cosX + rotatedZ * sinX; - - const screenX = canvasRef.current!.width / 2 + rotatedX; - const screenY = canvasRef.current!.height / 2 + rotatedY; - - const scale = (rotatedZ + 200) / 300; - const radius = 20 * scale; - const dx = x - screenX; - const dy = y - screenY; - - if (dx * dx + dy * dy < radius * radius) { - const targetX = -Math.atan2( - icon.y, - Math.sqrt(icon.x * icon.x + icon.z * icon.z), - ); - const targetY = Math.atan2(icon.x, icon.z); - - const currentX = rotationRef.current.x; - const currentY = rotationRef.current.y; - const distance = Math.sqrt( - Math.pow(targetX - currentX, 2) + Math.pow(targetY - currentY, 2), - ); - - const duration = Math.min(2000, Math.max(800, distance * 1000)); - - setTargetRotation({ - x: targetX, - y: targetY, - startX: currentX, - startY: currentY, - distance, - startTime: performance.now(), - duration, - }); - return; - } - }); - - setIsDragging(true); - setLastMousePos({ x: e.clientX, y: e.clientY }); - }; - - const handleMouseMove = (e: React.MouseEvent) => { - const rect = canvasRef.current?.getBoundingClientRect(); - if (rect) { - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; - setMousePos({ x, y }); - } - - if (isDragging) { - const deltaX = e.clientX - lastMousePos.x; - const deltaY = e.clientY - lastMousePos.y; - - rotationRef.current = { - x: rotationRef.current.x + deltaY * 0.002, - y: rotationRef.current.y + deltaX * 0.002, - }; - - setLastMousePos({ x: e.clientX, y: e.clientY }); - } - }; - - const handleMouseUp = () => { - setIsDragging(false); - }; - - // Animation and rendering - useEffect(() => { - const canvas = canvasRef.current; - const ctx = canvas?.getContext("2d"); - if (!canvas || !ctx) return; - - const animate = () => { - ctx.clearRect(0, 0, canvas.width, canvas.height); - - const centerX = canvas.width / 2; - const centerY = canvas.height / 2; - const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY); - const dx = mousePos.x - centerX; - const dy = mousePos.y - centerY; - const distance = Math.sqrt(dx * dx + dy * dy); - const speed = 0.003 + (distance / maxDistance) * 0.01; - - if (targetRotation) { - const elapsed = performance.now() - targetRotation.startTime; - const progress = Math.min(1, elapsed / targetRotation.duration); - const easedProgress = easeOutCubic(progress); - - rotationRef.current = { - x: - targetRotation.startX + - (targetRotation.x - targetRotation.startX) * easedProgress, - y: - targetRotation.startY + - (targetRotation.y - targetRotation.startY) * easedProgress, - }; - - if (progress >= 1) { - setTargetRotation(null); - } - } else if (!isDragging) { - rotationRef.current = { - x: rotationRef.current.x + (dy / canvas.height) * speed, - y: rotationRef.current.y + (dx / canvas.width) * speed, - }; - } - - iconPositions.forEach((icon, index) => { - const cosX = Math.cos(rotationRef.current.x); - const sinX = Math.sin(rotationRef.current.x); - const cosY = Math.cos(rotationRef.current.y); - const sinY = Math.sin(rotationRef.current.y); - - const rotatedX = icon.x * cosY - icon.z * sinY; - const rotatedZ = icon.x * sinY + icon.z * cosY; - const rotatedY = icon.y * cosX + rotatedZ * sinX; - - const scale = (rotatedZ + 200) / 300; - const opacity = Math.max(0.2, Math.min(1, (rotatedZ + 150) / 200)); - - ctx.save(); - ctx.translate( - canvas.width / 2 + rotatedX, - canvas.height / 2 + rotatedY, - ); - ctx.scale(scale, scale); - ctx.globalAlpha = opacity; - - if (icons || images) { - // Only try to render icons/images if they exist - if ( - iconCanvasesRef.current[index] && - imagesLoadedRef.current[index] - ) { - ctx.drawImage(iconCanvasesRef.current[index], -20, -20, 40, 40); - } - } else { - // Show numbered circles if no icons/images are provided - ctx.beginPath(); - ctx.arc(0, 0, 20, 0, Math.PI * 2); - ctx.fillStyle = "#4444ff"; - ctx.fill(); - ctx.fillStyle = "white"; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.font = "16px Arial"; - ctx.fillText(`${icon.id + 1}`, 0, 0); - } - - ctx.restore(); - }); - animationFrameRef.current = requestAnimationFrame(animate); - }; - - animate(); - - return () => { - if (animationFrameRef.current) { - cancelAnimationFrame(animationFrameRef.current); - } - }; - }, [icons, images, iconPositions, isDragging, mousePos, targetRotation]); - - return ( - - ); -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..1b53f6f --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,35 @@ +import { defineConfig } from "eslint/config"; +import nextCoreWebVitals from "eslint-config-next/core-web-vitals"; +import nextTypescript from "eslint-config-next/typescript"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import js from "@eslint/js"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all +}); + +export default defineConfig([{ + extends: [ + ...nextCoreWebVitals, + ...nextTypescript, + ...compat.extends("plugin:@typescript-eslint/recommended") + ], + + rules: { + "@typescript-eslint/no-explicit-any": "warn", + + "@typescript-eslint/no-unused-vars": ["warn", { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + }], + + "@typescript-eslint/no-empty-object-type": "off", + }, +}]); \ No newline at end of file diff --git a/hooks/useScrollPosition.ts b/hooks/useScrollPosition.ts index 65905e8..3060aa4 100644 --- a/hooks/useScrollPosition.ts +++ b/hooks/useScrollPosition.ts @@ -2,27 +2,28 @@ import { useState, useCallback, useEffect } from 'react'; export function useScrollPosition(threshold = 0.5) { const [isScrolled, setIsScrolled] = useState(false); - + const handleScroll = useCallback(() => { if (typeof window === 'undefined') return; - + const scrollPosition = window.scrollY; const viewportHeight = window.innerHeight; const shouldBeSticky = scrollPosition > viewportHeight * threshold; - + setIsScrolled(prev => shouldBeSticky !== prev ? shouldBeSticky : prev); }, [threshold]); // Add scroll event listener useEffect(() => { // Initial check + // eslint-disable-next-line react-hooks/set-state-in-effect handleScroll(); - + window.addEventListener('scroll', handleScroll, { passive: true }); return () => { window.removeEventListener('scroll', handleScroll); }; }, [handleScroll]); - + return isScrolled; } diff --git a/package.json b/package.json index 17250be..0552629 100644 --- a/package.json +++ b/package.json @@ -1,62 +1,67 @@ { "name": "docubook", - "version": "1.16.1", + "version": "2.0.0-beta.1", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "eslint ." }, "dependencies": { - "@docsearch/css": "3", + "@docsearch/css": "^3.9.0", "@docsearch/react": "^3.9.0", - "@radix-ui/react-accordion": "^1.2.0", - "@radix-ui/react-avatar": "^1.1.0", - "@radix-ui/react-collapsible": "^1.1.0", - "@radix-ui/react-dialog": "^1.1.6", - "@radix-ui/react-dropdown-menu": "^2.1.1", - "@radix-ui/react-popover": "^1.1.6", - "@radix-ui/react-scroll-area": "^1.2.0", - "@radix-ui/react-separator": "^1.0.3", - "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-tabs": "^1.1.0", - "@radix-ui/react-toggle": "^1.1.2", - "@radix-ui/react-toggle-group": "^1.1.2", - "algoliasearch": "^5.35.0", - "class-variance-authority": "^0.7.0", + "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-toggle-group": "^1.1.11", + "algoliasearch": "^5.46.3", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "cmdk": "1.0.0", - "framer-motion": "^12.4.1", - "geist": "^1.3.1", + "cmdk": "^1.1.1", + "framer-motion": "^12.26.2", + "geist": "^1.5.1", "gray-matter": "^4.0.3", "lucide-react": "^0.511.0", - "next": "^14.2.6", + "next": "16.1.3", "next-mdx-remote": "^5.0.0", - "next-themes": "^0.3.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "next-themes": "^0.4.4", + "react": "19.2.3", + "react-dom": "19.2.3", "react-icons": "^5.5.0", "rehype-autolink-headings": "^7.1.0", - "rehype-code-titles": "^1.2.0", - "rehype-prism-plus": "^2.0.0", + "rehype-code-titles": "^1.2.1", + "rehype-prism-plus": "^2.0.1", "rehype-slug": "^6.0.0", - "remark-gfm": "^4.0.0", - "sonner": "^1.4.3", - "tailwind-merge": "^2.5.2", + "remark-gfm": "^4.0.1", + "sonner": "^1.7.4", + "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", "unist-util-visit": "^5.0.0" }, "devDependencies": { - "@tailwindcss/typography": "^0.5.14", - "@types/node": "^20", - "@types/react": "^18", - "@types/react-dom": "^18", - "autoprefixer": "^10.4.20", - "eslint": "^8", - "eslint-config-next": "^14.2.6", - "postcss": "^8", - "tailwindcss": "^3.4.10", - "typescript": "^5" + "@tailwindcss/postcss": "^4.1.18", + "@tailwindcss/typography": "^0.5.19", + "@types/node": "^20.19.30", + "@types/react": "19.2.8", + "@types/react-dom": "19.2.3", + "autoprefixer": "^10.4.23", + "eslint": "^9.39.2", + "eslint-config-next": "16.1.3", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", + "typescript": "^5.9.3" + }, + "overrides": { + "@types/react": "19.2.8", + "@types/react-dom": "19.2.3" } } diff --git a/postcss.config.js b/postcss.config.js index 12a703d..b4bee66 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,6 +1,6 @@ module.exports = { plugins: { - tailwindcss: {}, + '@tailwindcss/postcss': {}, autoprefixer: {}, }, }; diff --git a/styles/globals.css b/styles/globals.css index 84669c5..6c012de 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -1,69 +1,189 @@ -@import "@docsearch/css"; -@import "./algolia.css"; +@import 'tailwindcss'; +@plugin '@tailwindcss/typography'; -@tailwind base; -@tailwind components; -@tailwind utilities; -@import url("./syntax.css"); +@custom-variant dark (&:is(.dark *)); + +@utility container { + margin-inline: auto; + padding-inline: 2rem; + + @media (width >=--theme(--breakpoint-sm)) { + max-width: none; + } + + @media (width >=1440px) { + max-width: 1440px; + } +} + +@theme { + --color-border: hsl(var(--border)); + --color-input: hsl(var(--input)); + --color-ring: hsl(var(--ring)); + --color-background: hsl(var(--background)); + --color-foreground: hsl(var(--foreground)); + + --color-primary: hsl(var(--primary)); + --color-primary-foreground: hsl(var(--primary-foreground)); + + --color-secondary: hsl(var(--secondary)); + --color-secondary-foreground: hsl(var(--secondary-foreground)); + + --color-destructive: hsl(var(--destructive)); + --color-destructive-foreground: hsl(var(--destructive-foreground)); + + --color-muted: hsl(var(--muted)); + --color-muted-foreground: hsl(var(--muted-foreground)); + + --color-accent: hsl(var(--accent)); + --color-accent-foreground: hsl(var(--accent-foreground)); + + --color-popover: hsl(var(--popover)); + --color-popover-foreground: hsl(var(--popover-foreground)); + + --color-card: hsl(var(--card)); + --color-card-foreground: hsl(var(--card-foreground)); + + --color-sidebar: hsl(var(--sidebar-background)); + --color-sidebar-foreground: hsl(var(--sidebar-foreground)); + --color-sidebar-primary: hsl(var(--sidebar-primary)); + --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground)); + --color-sidebar-accent: hsl(var(--sidebar-accent)); + --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground)); + --color-sidebar-border: hsl(var(--sidebar-border)); + --color-sidebar-ring: hsl(var(--sidebar-ring)); + + --radius-lg: var(--radius); + --radius-md: calc(var(--radius) - 2px); + --radius-sm: calc(var(--radius) - 4px); + + --font-code: var(--font-geist-mono); + --font-regular: var(--font-geist-sans); + + --animate-accordion-down: accordion-down 0.2s ease-out; + --animate-accordion-up: accordion-up 0.2s ease-out; + --animate-shiny-text: shiny-text 8s infinite; + + @keyframes accordion-down { + from { + height: 0; + } + + to { + height: var(--radix-accordion-content-height); + } + } + + @keyframes accordion-up { + from { + height: var(--radix-accordion-content-height); + } + + to { + height: 0; + } + } + + @keyframes shiny-text { + + 0%, + 90%, + 100% { + background-position: calc(-100% - var(--shiny-width)) 0; + } + + 30%, + 60% { + background-position: calc(100% + var(--shiny-width)) 0; + } + } +} + +/* + The default border color has changed to `currentcolor` in Tailwind CSS v4, + so we've added these compatibility styles to make sure everything still + looks the same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add an explicit border + color utility to any element that depends on these defaults. +*/ +@layer base { + + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentcolor); + } +} + +@utility animate-shine { + --animate-shine: shine var(--duration) infinite linear; + animation: var(--animate-shine); + background-size: 200% 200%; +} + /* Modern Blue Theme */ @layer base { - :root { - --background: 210 40% 98%; - --foreground: 220 30% 15%; - --card: 0 0% 100%; - --card-foreground: 220 30% 15%; - --popover: 0 0% 100%; - --popover-foreground: 220 30% 15%; - --primary: 210 81% 56%; /* #2281E3 */ - --primary-foreground: 0 0% 100%; - --secondary: 210 30% 90%; - --secondary-foreground: 220 30% 15%; - --muted: 210 20% 92%; - --muted-foreground: 220 15% 50%; - --accent: 200 100% 40%; - --accent-foreground: 0 0% 100%; - --destructive: 0 85% 60%; - --destructive-foreground: 0 0% 100%; - --border: 210 20% 85%; - --input: 210 20% 85%; - --ring: 210 81% 56%; - --radius: 0.5rem; - --chart-1: 210 81% 56%; - --chart-2: 200 100% 40%; - --chart-3: 220 76% 60%; - --chart-4: 190 90% 50%; - --chart-5: 230 86% 45%; - --line-number-color: rgba(0, 0, 0, 0.05); - } + :root { + --background: 210 40% 98%; + --foreground: 220 30% 15%; + --card: 0 0% 100%; + --card-foreground: 220 30% 15%; + --popover: 0 0% 100%; + --popover-foreground: 220 30% 15%; + --primary: 210 81% 56%; + /* #2281E3 */ + --primary-foreground: 0 0% 100%; + --secondary: 210 30% 90%; + --secondary-foreground: 220 30% 15%; + --muted: 210 20% 92%; + --muted-foreground: 220 15% 50%; + --accent: 200 100% 40%; + --accent-foreground: 0 0% 100%; + --destructive: 0 85% 60%; + --destructive-foreground: 0 0% 100%; + --border: 210 20% 85%; + --input: 210 20% 85%; + --ring: 210 81% 56%; + --radius: 0.5rem; + --chart-1: 210 81% 56%; + --chart-2: 200 100% 40%; + --chart-3: 220 76% 60%; + --chart-4: 190 90% 50%; + --chart-5: 230 86% 45%; + --line-number-color: rgba(0, 0, 0, 0.05); + } - .dark { - --background: 220 25% 10%; - --foreground: 210 30% 96%; - --card: 220 25% 15%; - --card-foreground: 210 30% 96%; - --popover: 220 25% 15%; - --popover-foreground: 210 30% 96%; - --primary: 210 100% 65%; - --primary-foreground: 220 25% 10%; - --secondary: 215 25% 20%; - --secondary-foreground: 210 30% 96%; - --muted: 215 20% 25%; - --muted-foreground: 215 20% 65%; - --accent: 200 100% 60%; - --accent-foreground: 0 0% 100%; - --destructive: 0 85% 70%; - --destructive-foreground: 0 0% 100%; - --border: 215 20% 25%; - --input: 215 20% 25%; - --ring: 210 100% 65%; - --chart-1: 210 100% 65%; - --chart-2: 200 100% 60%; - --chart-3: 220 90% 70%; - --chart-4: 190 100% 65%; - --chart-5: 230 90% 60%; - --line-number-color: rgba(255, 255, 255, 0.1); - } + .dark { + --background: 220 25% 10%; + --foreground: 210 30% 96%; + --card: 220 25% 15%; + --card-foreground: 210 30% 96%; + --popover: 220 25% 15%; + --popover-foreground: 210 30% 96%; + --primary: 210 100% 65%; + --primary-foreground: 220 25% 10%; + --secondary: 215 25% 20%; + --secondary-foreground: 210 30% 96%; + --muted: 215 20% 25%; + --muted-foreground: 215 20% 65%; + --accent: 200 100% 60%; + --accent-foreground: 0 0% 100%; + --destructive: 0 85% 70%; + --destructive-foreground: 0 0% 100%; + --border: 215 20% 25%; + --input: 215 20% 25%; + --ring: 210 100% 65%; + --chart-1: 210 100% 65%; + --chart-2: 200 100% 60%; + --chart-3: 220 90% 70%; + --chart-4: 190 100% 65%; + --chart-5: 230 90% 60%; + --line-number-color: rgba(255, 255, 255, 0.1); + } } @layer base { @@ -76,67 +196,66 @@ } } -.prose { - margin: 0 !important; -} +@layer utilities { + .prose { + margin: 0 !important; + } -pre { - padding: 2px 0 !important; - width: inherit !important; - overflow-x: auto; -} + pre { + padding: 2px 0 !important; + width: inherit !important; + overflow-x: auto; + } -pre>code { - display: grid; - max-width: inherit !important; - padding: 14px 0 !important; - border: 0 !important; -} + pre>code { + display: grid; + max-width: inherit !important; + padding: 14px 0 !important; + border: 0 !important; + } -.code-line { - padding: 0.75px 16px; - @apply border-l-2 border-transparent -} + .code-line { + padding: 0.75px 16px; + @apply border-l-2 border-transparent; + } -.line-number::before { - display: inline-block; - width: 1rem; - margin-right: 22px; - margin-left: -2px; - color: rgb(110, 110, 110); - content: attr(line); - font-size: 13.5px; - text-align: right; -} + .line-number::before { + display: inline-block; + width: 1rem; + margin-right: 22px; + margin-left: -2px; + color: rgb(110, 110, 110); + content: attr(line); + font-size: 13.5px; + text-align: right; + } -.highlight-line { - @apply bg-primary/5 border-l-2 border-primary/30; -} + .highlight-line { + @apply bg-primary/5 border-l-2 border-primary/30; + } -.rehype-code-title { - @apply px-2 -mb-8 w-full text-sm pb-5 font-medium mt-5 font-code; -} + .rehype-code-title { + @apply px-2 -mb-8 w-full text-sm pb-5 font-medium mt-5 font-code; + } -.highlight-comp>code { - background-color: transparent !important; + .highlight-comp>code { + background-color: transparent !important; + } } @layer utilities { - .animate-shine { - --animate-shine: shine var(--duration) infinite linear; - animation: var(--animate-shine); - background-size: 200% 200%; + + @keyframes shine { + 0% { + background-position: 0% 0%; } - @keyframes shine { - 0% { - background-position: 0% 0%; - } - 50% { - background-position: 100% 100%; - } - 100% { - background-position: 0% 0%; - } + 50% { + background-position: 100% 100%; + } + + 100% { + background-position: 0% 0%; } } +} \ No newline at end of file diff --git a/tailwind.config.ts b/tailwind.config.ts index 56783f3..8e6fa3b 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,111 +1,113 @@ import type { Config } from "tailwindcss"; +import tailwindAnimate from "tailwindcss-animate"; +import typography from "@tailwindcss/typography"; const config = { - darkMode: ["class"], - content: [ - "./pages/**/*.{ts,tsx}", - "./components/**/*.{ts,tsx}", - "./app/**/*.{ts,tsx}", - "./src/**/*.{ts,tsx}", - ], - prefix: "", - theme: { - container: { - center: true, - padding: '2rem', - screens: { - '2xl': '1440px' - } - }, - extend: { - colors: { - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))' - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))' - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))' - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))' - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))' - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))' - }, - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))' - }, - sidebar: { - DEFAULT: 'hsl(var(--sidebar-background))', - foreground: 'hsl(var(--sidebar-foreground))', - primary: 'hsl(var(--sidebar-primary))', - 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', - accent: 'hsl(var(--sidebar-accent))', - 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', - border: 'hsl(var(--sidebar-border))', - ring: 'hsl(var(--sidebar-ring))' - } - }, - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)' - }, - fontFamily: { - code: ["var(--font-geist-mono)"], - regular: ["var(--font-geist-sans)"] - }, - keyframes: { - 'accordion-down': { - from: { - height: '0' - }, - to: { - height: 'var(--radix-accordion-content-height)' - } - }, - 'accordion-up': { - from: { - height: 'var(--radix-accordion-content-height)' - }, - to: { - height: '0' - } - }, - 'shiny-text': { - '0%, 90%, 100%': { - 'background-position': 'calc(-100% - var(--shiny-width)) 0' - }, - '30%, 60%': { - 'background-position': 'calc(100% + var(--shiny-width)) 0' - } - } - }, - animation: { - 'accordion-down': 'accordion-down 0.2s ease-out', - 'accordion-up': 'accordion-up 0.2s ease-out', - 'shiny-text': 'shiny-text 8s infinite' - } - } - }, - plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")], + darkMode: "class", + content: [ + "./pages/**/*.{ts,tsx}", + "./components/**/*.{ts,tsx}", + "./app/**/*.{ts,tsx}", + "./src/**/*.{ts,tsx}", + ], + prefix: "", + theme: { + container: { + center: true, + padding: '2rem', + screens: { + '2xl': '1440px' + } + }, + extend: { + colors: { + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + sidebar: { + DEFAULT: 'hsl(var(--sidebar-background))', + foreground: 'hsl(var(--sidebar-foreground))', + primary: 'hsl(var(--sidebar-primary))', + 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', + accent: 'hsl(var(--sidebar-accent))', + 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', + border: 'hsl(var(--sidebar-border))', + ring: 'hsl(var(--sidebar-ring))' + } + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + fontFamily: { + code: ["var(--font-geist-mono)"], + regular: ["var(--font-geist-sans)"] + }, + keyframes: { + 'accordion-down': { + from: { + height: '0' + }, + to: { + height: 'var(--radix-accordion-content-height)' + } + }, + 'accordion-up': { + from: { + height: 'var(--radix-accordion-content-height)' + }, + to: { + height: '0' + } + }, + 'shiny-text': { + '0%, 90%, 100%': { + 'background-position': 'calc(-100% - var(--shiny-width)) 0' + }, + '30%, 60%': { + 'background-position': 'calc(100% + var(--shiny-width)) 0' + } + } + }, + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out', + 'shiny-text': 'shiny-text 8s infinite' + } + } + }, + plugins: [tailwindAnimate, typography], } satisfies Config; export default config; diff --git a/tsconfig.json b/tsconfig.json index e7ff90f..9e9bbf7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,10 @@ { "compilerOptions": { - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -10,7 +14,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve", + "jsx": "react-jsx", "incremental": true, "plugins": [ { @@ -18,9 +22,20 @@ } ], "paths": { - "@/*": ["./*"] - } + "@/*": [ + "./*" + ] + }, + "target": "ES2017" }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] }