version 1.13.5 : add theme schema

This commit is contained in:
gitfromwildan
2025-05-31 02:11:57 +07:00
parent bcafa9b5d9
commit d967120677
24 changed files with 788 additions and 165 deletions

View File

@@ -68,7 +68,7 @@ export default function ContextPopover({ className }: ContextPopoverProps) {
>
<div className="flex items-center gap-2">
{activeRoute?.context?.icon && (
<span className="text-primary bg-primary/10 border border-primary rounded p-0.5">
<span className="text-primary bg-primary/10 border border-primary dark:border dark:border-accent dark:bg-accent/10 dark:text-accent rounded p-0.5">
{getIcon(activeRoute.context.icon)}
</span>
)}
@@ -98,14 +98,14 @@ export default function ContextPopover({ className }: ContextPopoverProps) {
"relative flex w-full items-center gap-2 rounded px-2 py-1.5 text-sm",
"text-left outline-none transition-colors",
isActive
? "bg-primary/20 text-primary"
: "text-foreground/80 hover:bg-primary/20"
? "bg-primary/20 text-primary dark:bg-accent/20 dark:text-accent"
: "text-foreground/80 hover:bg-primary/20 dark:text-foreground/60 dark:hover:bg-accent/20"
)}
>
{route.context?.icon && (
<span className={cn(
"flex h-4 w-4 items-center justify-center",
isActive ? "text-primary" : "text-foreground/60"
isActive ? "text-primary dark:text-accent" : "text-foreground/60"
)}>
{getIcon(route.context.icon)}
</span>

View File

@@ -63,8 +63,9 @@ export function FooterButtons() {
target="_blank"
rel="noopener noreferrer"
aria-label={item.name}
className="text-muted-foreground hover:text-foreground transition-colors"
>
<IconComponent className="w-4 h-4 text-gray-800 transition-colors dark:text-gray-400 hover:text-primary" />
<IconComponent className="w-4 h-4" />
</Link>
);
})}
@@ -79,7 +80,8 @@ export function MadeWith() {
<span className="text-primary">
<Link href="https://www.docubook.pro" target="_blank" rel="noopener noreferrer" className="underline underline-offset-2 text-muted-foreground">
DocuBook
</Link></span>
</Link>
</span>
</>
);
}

View File

@@ -13,7 +13,7 @@ const CardGroup: React.FC<CardGroupProps> = ({ children, cols = 2, className })
return (
<div
className={clsx(
"grid gap-4",
"grid gap-4 text-foreground",
`grid-cols-1 sm:grid-cols-${cols}`,
className
)}

View File

@@ -20,8 +20,9 @@ const Card: React.FC<CardProps> = ({ title, icon, href, horizontal, children, cl
const content = (
<div
className={clsx(
"border rounded-lg shadow-sm p-4 transition-all duration-200 bg-white dark:bg-gray-900",
"hover:bg-gray-50 dark:hover:bg-gray-800",
"border rounded-lg shadow-sm p-4 transition-all duration-200",
"bg-card text-card-foreground border-border",
"hover:bg-accent/5 hover:border-accent/30",
"flex gap-2",
horizontal ? "flex-row items-center gap-1" : "flex-col space-y-1",
className
@@ -29,8 +30,8 @@ const Card: React.FC<CardProps> = ({ title, icon, href, horizontal, children, cl
>
{Icon && <Icon className="w-5 h-5 text-primary flex-shrink-0" />}
<div className="flex-1 min-w-0 my-auto h-full">
<span className="text-base font-semibold">{title}</span>
<div className="text-sm text-gray-600 dark:text-gray-400 -mt-3">{children}</div>
<span className="text-base font-semibold text-foreground">{title}</span>
<div className="text-sm text-muted-foreground -mt-3">{children}</div>
</div>
</div>
);

View File

@@ -1,12 +1,7 @@
'use client';
import React, { useState, ReactNode, Children, isValidElement, cloneElement } from 'react';
import { ChevronRight, ChevronDown, File as FileIcon, Folder as FolderIcon, FolderOpen } from 'lucide-react';
interface FileTreeProps {
children: ReactNode;
defaultOpen?: boolean;
}
import { ChevronRight, File as FileIcon, Folder as FolderIcon, FolderOpen } from 'lucide-react';
interface FileProps {
name: string;
@@ -15,24 +10,27 @@ interface FileProps {
const FileComponent = ({ name }: FileProps) => {
const [isHovered, setIsHovered] = useState(false);
const fileExtension = name.split('.').pop()?.toUpperCase();
return (
<div
className={`
flex items-center gap-2 py-1.5 pl-7 pr-3 text-sm
transition-all duration-200 ease-in-out rounded-md
${isHovered
? 'bg-blue-50 dark:bg-blue-900/30'
: 'hover:bg-gray-50 dark:hover:bg-gray-800/50'}
flex items-center gap-2 py-1.5 pl-7 pr-3 text-sm rounded-md
transition-colors duration-150 cursor-default select-none
${isHovered ? 'bg-accent/10' : 'hover:bg-muted/50'}
`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
tabIndex={-1}
>
<FileIcon className={`h-3.5 w-3.5 transition-colors ${isHovered ? 'text-blue-500' : 'text-gray-400 dark:text-gray-500'}`} />
<span className="font-mono text-sm text-gray-700 dark:text-gray-300">{name}</span>
{isHovered && (
<span className="ml-auto text-xs text-gray-400 dark:text-gray-500">
{name.split('.').pop()?.toUpperCase()}
<FileIcon className={`
h-3.5 w-3.5 flex-shrink-0 transition-colors
${isHovered ? 'text-accent' : 'text-muted-foreground'}
`} />
<span className="font-mono text-sm text-foreground truncate">{name}</span>
{isHovered && fileExtension && (
<span className="ml-auto text-xs text-muted-foreground/80">
{fileExtension}
</span>
)}
</div>
@@ -48,31 +46,49 @@ const FolderComponent = ({ name, children }: FileProps) => {
<div className="relative">
<div
className={`
flex items-center gap-2 py-1.5 pl-4 pr-3 rounded-md cursor-pointer
transition-all duration-200 ease-in-out
${isHovered ? 'bg-gray-50 dark:bg-gray-800/50' : ''}
${isOpen ? 'text-blue-600 dark:text-blue-400' : 'text-gray-800 dark:text-gray-200'}
flex items-center gap-2 py-1.5 pl-4 pr-3 rounded-md
transition-colors duration-150 select-none
${isHovered ? 'bg-muted/60' : ''}
${isOpen ? 'text-foreground' : 'text-foreground/80'}
${hasChildren ? 'cursor-pointer' : 'cursor-default'}
`}
onClick={() => setIsOpen(!isOpen)}
onClick={() => hasChildren && setIsOpen(!isOpen)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
tabIndex={-1}
onKeyDown={(e) => e.preventDefault()}
>
{hasChildren ? (
<ChevronRight
className={`h-3.5 w-3.5 transition-transform duration-200 ${isOpen ? 'transform rotate-90' : ''}`}
className={`
h-3.5 w-3.5 flex-shrink-0 transition-transform duration-200
${isOpen ? 'rotate-90' : ''}
${isHovered ? 'text-foreground/70' : 'text-muted-foreground'}
`}
/>
) : (
<div className="w-3.5" />
)}
{isOpen ? (
<FolderOpen className="h-4 w-4 text-blue-500 dark:text-blue-400" />
<FolderOpen className={`
h-4 w-4 flex-shrink-0 transition-colors
${isHovered ? 'text-accent' : 'text-muted-foreground'}
`} />
) : (
<FolderIcon className="h-4 w-4 text-blue-400 dark:text-blue-500" />
<FolderIcon className={`
h-4 w-4 flex-shrink-0 transition-colors
${isHovered ? 'text-accent/80' : 'text-muted-foreground/80'}
`} />
)}
<span className="font-medium">{name}</span>
<span className={`
font-medium transition-colors duration-150
${isHovered ? 'text-accent' : ''}
`}>
{name}
</span>
</div>
{isOpen && hasChildren && (
<div className="ml-5 border-l-2 border-gray-100 dark:border-gray-700/50 pl-2">
<div className="ml-5 border-l-2 border-muted/50 pl-2">
{children}
</div>
)}
@@ -82,13 +98,16 @@ const FolderComponent = ({ name, children }: FileProps) => {
export const Files = ({ children }: { children: ReactNode }) => {
return (
<div className="
rounded-xl border border-gray-100 dark:border-gray-700/50
bg-white/50 dark:bg-gray-800/30 backdrop-blur-sm
shadow-sm overflow-hidden
transition-all duration-200
hover:shadow-md hover:border-gray-200 dark:hover:border-gray-600/50
">
<div
className="
rounded-xl border border-muted/50
bg-card/50 backdrop-blur-sm
shadow-sm overflow-hidden
transition-all duration-200
hover:shadow-md hover:border-muted/60
"
onKeyDown={(e) => e.preventDefault()}
>
<div className="p-2">
{Children.map(children, (child, index) => {
if (isValidElement(child)) {

View File

@@ -88,7 +88,7 @@ const KbdComponent: React.FC<KbdProps> = ({
return (
<kbd
className="inline-flex items-center justify-center px-2 py-1 mx-0.5 text-xs font-mono font-medium text-gray-800 bg-gray-100 border border-gray-300 rounded-md dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
className="inline-flex items-center justify-center px-2 py-1 mx-0.5 text-xs font-mono font-medium text-foreground bg-secondary/70 border rounded-md"
{...props}
>
{renderContent()}

View File

@@ -29,7 +29,7 @@ export default function Note({
"dark:bg-stone-950/25 bg-stone-50": type === "note",
"dark:bg-red-950 bg-red-100 border-red-200 dark:border-red-900":
type === "danger",
"dark:bg-orange-950 bg-orange-100 border-orange-200 dark:border-orange-900":
"bg-orange-50 border-orange-200 dark:border-orange-900 dark:bg-orange-900/50":
type === "warning",
"dark:bg-green-950 bg-green-100 border-green-200 dark:border-green-900":
type === "success",

View File

@@ -11,13 +11,13 @@ export function Stepper({ children }: PropsWithChildren) {
return (
<div
className={cn(
"border-l pl-9 ml-3 relative",
"border-l border-muted pl-9 ml-3 relative",
clsx({
"pb-5 ": index < length - 1,
})
)}
>
<div className="bg-muted w-8 h-8 text-xs font-medium rounded-md border flex items-center justify-center absolute -left-4 font-code">
<div className="bg-muted text-muted-foreground w-8 h-8 text-xs font-medium rounded-md border border-border/50 flex items-center justify-center absolute -left-4 font-code">
{index + 1}
</div>
{child}

View File

@@ -11,14 +11,17 @@ const Tooltip: React.FC<TooltipProps> = ({ text, tip }) => {
return (
<span
className="relative inline-block cursor-pointer underline decoration-dotted text-blue-500"
className="relative inline-flex items-center cursor-help text-primary hover:text-primary/80 transition-colors"
onMouseEnter={() => setVisible(true)}
onMouseLeave={() => setVisible(false)}
>
{text}
<span className="border-b border-dashed border-primary/60 pb-0.5">
{text}
</span>
{visible && (
<span className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 w-max max-w-xs sm:max-w-sm md:max-w-md lg:max-w-lg xl:max-w-xl bg-background text-foreground text-sm p-2 rounded shadow-md break-words text-center outline outline-1 outline-offset-2">
<span className="absolute bottom-full left-1/2 -translate-x-1/2 mb-3 w-64 bg-popover text-popover-foreground text-sm p-3 rounded-md shadow-lg border border-border/50 break-words text-left z-50">
{tip}
<span className="absolute -bottom-1.5 left-1/2 -translate-x-1/2 w-3 h-3 bg-popover rotate-45 border-b border-r border-border/50 -z-10" />
</span>
)}
</span>

View File

@@ -63,15 +63,15 @@ export function NavMenu({ isSheet = false }) {
const Comp = (
<Anchor
key={`${item.title}-${item.href}`}
activeClassName="!text-primary md:font-semibold font-medium"
activeClassName="text-primary dark:text-accent md:font-semibold font-medium"
absolute
className="flex items-center gap-1 dark:text-stone-300/85 text-stone-800"
className="flex items-center gap-1 text-foreground/80 hover:text-foreground transition-colors"
href={item.href}
target={isExternal ? "_blank" : undefined}
rel={isExternal ? "noopener noreferrer" : undefined}
>
{item.title}
{isExternal && <ArrowUpRight className="w-4 h-4 text-muted-foreground" />}
{isExternal && <ArrowUpRight className="w-4 h-4 text-foreground/80" />}
</Anchor>
);
return isSheet ? (

View File

@@ -54,9 +54,10 @@ export default function SubLink({
// Only apply active styles if it's an exact match and not a parent with active children
const Comp = useMemo(() => (
<Anchor
activeClassName={!hasActiveChild ? "text-primary font-medium" : ""}
activeClassName={!hasActiveChild ? "dark:text-accent text-primary font-medium" : ""}
href={fullHref}
className={cn(
"text-foreground/80 hover:text-foreground transition-colors",
hasActiveChild && "font-medium text-foreground"
)}
>
@@ -72,8 +73,8 @@ export default function SubLink({
)
) : (
<h4 className={cn(
"font-medium sm:text-sm",
hasActiveChild ? "text-foreground" : "text-primary"
"font-medium sm:text-sm text-foreground/90 hover:text-foreground transition-colors",
hasActiveChild ? "text-foreground" : "text-foreground/80"
)}>
{title}
</h4>
@@ -93,7 +94,7 @@ export default function SubLink({
>
<div className="flex items-center justify-between w-full">
{titleOrLink}
<span className="ml-2">
<span className="ml-2 text-muted-foreground">
{!isOpen ? (
<ChevronRight className="h-[0.9rem] w-[0.9rem]" aria-hidden="true" />
) : (
@@ -111,8 +112,8 @@ export default function SubLink({
>
<div
className={cn(
"flex flex-col items-start sm:text-sm dark:text-stone-300/85 text-stone-800 ml-0.5 mt-2.5 gap-3",
level > 0 && "pl-4 border-l ml-1.5"
"flex flex-col items-start sm:text-sm text-foreground/80 ml-0.5 mt-2.5 gap-3 hover:[&_a]:text-foreground transition-colors",
level > 0 && "pl-4 border-l border-border ml-1.5"
)}
>
{items?.map((innerLink) => (

View File

@@ -28,7 +28,7 @@ export function ModeToggle() {
setSelectedTheme(value);
}
}}
className="flex items-center gap-1 rounded-full border border-gray-300 dark:border-gray-700 p-1 transition-all"
className="flex items-center gap-1 rounded-full border border-border bg-background/50 p-1 transition-all"
>
<ToggleGroupItem
value="light"
@@ -36,11 +36,11 @@ export function ModeToggle() {
aria-label="Light Mode"
className={`rounded-full p-1 transition-all ${
selectedTheme === "light"
? "bg-blue-500 text-white"
: "bg-transparent"
? "bg-primary text-primary-foreground"
: "bg-transparent hover:bg-muted/50"
}`}
>
<Sun className={`h-4 w-4 ${selectedTheme === "light" ? "text-white" : "text-gray-600 dark:text-gray-300"}`} />
<Sun className="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem
value="system"
@@ -48,11 +48,11 @@ export function ModeToggle() {
aria-label="System Mode"
className={`rounded-full p-1 transition-all ${
selectedTheme === "system"
? "bg-blue-500 text-white"
: "bg-transparent"
? "bg-primary text-primary-foreground"
: "bg-transparent hover:bg-muted/50"
}`}
>
<Monitor className={`h-4 w-4 ${selectedTheme === "system" ? "text-white" : "text-gray-600 dark:text-gray-300"}`} />
<Monitor className="h-4 w-4" />
</ToggleGroupItem>
<ToggleGroupItem
value="dark"
@@ -60,11 +60,11 @@ export function ModeToggle() {
aria-label="Dark Mode"
className={`rounded-full p-1 transition-all ${
selectedTheme === "dark"
? "bg-blue-500 text-white"
: "bg-transparent"
? "bg-primary text-primary-foreground"
: "bg-transparent hover:bg-muted/50"
}`}
>
<Moon className={`h-4 w-4 ${selectedTheme === "dark" ? "text-white" : "text-gray-600 dark:text-gray-300"}`} />
<Moon className="h-4 w-4" />
</ToggleGroupItem>
</ToggleGroup>
);

View File

@@ -149,7 +149,7 @@ export default function TocObserver({
return (
<div className="relative">
<div className="relative text-sm text-stone-600 dark:text-stone-400">
<div className="relative text-sm text-foreground/70 hover:text-foreground transition-colors">
<div className="flex flex-col gap-0">
{data.map(({ href, level, text }, index) => {
const id = href.slice(1);
@@ -172,7 +172,7 @@ export default function TocObserver({
{/* Vertical line */}
<div className={clsx(
"absolute left-0 top-0 h-full w-px",
isActive ? "bg-primary/20" : "bg-stone-300 dark:bg-stone-600"
isActive ? "bg-primary/20 dark:bg-primary/30" : "bg-border/50 dark:bg-border/50"
)}>
{isActive && (
<motion.div
@@ -187,11 +187,11 @@ export default function TocObserver({
{/* Horizontal line */}
<div className={clsx(
"absolute left-0 top-1/2 h-px w-6",
isActive ? "bg-primary/20" : "bg-stone-300 dark:bg-stone-600"
isActive ? "bg-primary/20 dark:bg-primary/30" : "bg-border/50 dark:bg-border/50"
)}>
{isActive && (
<motion.div
className="absolute left-0 top-0 h-full w-full bg-primary origin-left"
className="absolute left-0 top-0 h-full w-full bg-primary dark:bg-accent origin-left"
initial={{ scaleX: 0 }}
animate={{ scaleX: scrollProgress }}
transition={{ duration: 0.3, delay: 0.1 }}
@@ -207,8 +207,8 @@ export default function TocObserver({
className={clsx(
"relative flex items-center py-2 transition-colors",
{
"text-primary dark:text-primary-400 font-medium": isActive,
"text-stone-600 dark:text-stone-400 hover:text-stone-900 dark:hover:text-stone-200": !isActive,
"text-primary dark:text-primary font-medium": isActive,
"text-muted-foreground hover:text-foreground dark:hover:text-foreground/90": !isActive,
}
)}
style={{
@@ -229,13 +229,13 @@ export default function TocObserver({
<div className={clsx(
"w-1.5 h-1.5 rounded-full transition-all duration-300 relative z-10",
{
"bg-primary scale-100": isActive,
"bg-stone-300 dark:bg-stone-600 scale-75 group-hover:scale-100 group-hover:bg-primary/50": !isActive,
"bg-primary scale-100 dark:bg-primary/90": isActive,
"bg-muted-foreground/30 dark:bg-muted-foreground/30 scale-75 group-hover:scale-100 group-hover:bg-primary/50 dark:group-hover:bg-primary/50": !isActive,
}
)}>
{isActive && (
<motion.div
className="absolute inset-0 rounded-full bg-primary/20"
className="absolute inset-0 rounded-full bg-primary/20 dark:bg-primary/30"
initial={{ scale: 1 }}
animate={{ scale: 1.8 }}
transition={{