@docubook/create 2.4.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/dist/app/docs/[[...slug]]/page.tsx +55 -59
- package/src/dist/app/docs/layout.tsx +11 -4
- package/src/dist/app/layout.tsx +10 -7
- package/src/dist/app/page.tsx +1 -1
- package/src/dist/components/{context-popover.tsx → ContextPopover.tsx} +3 -2
- package/src/dist/components/DocSearch.tsx +16 -15
- package/src/dist/components/{docs-breadcrumb.tsx → DocsBreadcrumb.tsx} +1 -1
- package/src/dist/components/DocsNavbar.tsx +46 -0
- package/src/dist/components/DocsSidebar.tsx +207 -0
- package/src/dist/components/Github.tsx +26 -0
- package/src/dist/components/{scroll-to-top.tsx → ScrollToTop.tsx} +16 -9
- package/src/dist/components/SearchBox.tsx +39 -0
- package/src/dist/components/SearchContext.tsx +47 -0
- package/src/dist/components/SearchModal.tsx +77 -79
- package/src/dist/components/SearchTrigger.tsx +20 -15
- package/src/dist/components/Sponsor.tsx +2 -2
- package/src/dist/components/{theme-toggle.tsx → ThemeToggle.tsx} +10 -10
- package/src/dist/components/TocObserver.tsx +197 -0
- package/src/dist/components/footer.tsx +16 -12
- package/src/dist/components/leftbar.tsx +45 -73
- package/src/dist/components/markdown/AccordionContext.tsx +21 -0
- package/src/dist/components/markdown/AccordionGroupMdx.tsx +11 -22
- package/src/dist/components/markdown/AccordionMdx.tsx +58 -59
- package/src/dist/components/markdown/PreMdx.tsx +2 -2
- package/src/dist/components/navbar.tsx +130 -53
- package/src/dist/components/toc.tsx +16 -14
- package/src/dist/components/typography.tsx +1 -1
- package/src/dist/components/ui/icon-cloud.tsx +353 -0
- package/src/dist/components/ui/scroll-area.tsx +2 -2
- package/src/dist/components/ui/sheet.tsx +4 -4
- package/src/dist/components/ui/toggle.tsx +3 -3
- package/src/dist/docs/components/accordion-group.mdx +13 -12
- package/src/dist/docs/components/accordion.mdx +11 -14
- package/src/dist/docu.json +3 -0
- package/src/dist/hooks/useActiveSection.ts +45 -33
- package/src/dist/hooks/useScrollPosition.ts +16 -14
- package/src/dist/lib/search/algolia.ts +5 -0
- package/src/dist/lib/search/built-in.ts +43 -0
- package/src/dist/lib/search/config.ts +7 -0
- package/src/dist/lib/toc.ts +1 -0
- package/src/dist/lib/utils.ts +13 -54
- package/src/dist/package.json +1 -1
- package/src/dist/styles/algolia.css +12 -35
- package/src/dist/styles/{syntax.css → override.css} +82 -39
- package/src/dist/tailwind.config.ts +11 -110
- package/src/dist/components/AccordionContext.ts +0 -4
- package/src/dist/components/GithubStart.tsx +0 -44
- package/src/dist/components/mob-toc.tsx +0 -134
- package/src/dist/components/search.tsx +0 -55
- package/src/dist/components/toc-observer.tsx +0 -254
- /package/src/dist/components/{docs-menu.tsx → DocsMenu.tsx} +0 -0
- /package/src/dist/components/{edit-on-github.tsx → EditWithGithub.tsx} +0 -0
- /package/src/dist/components/{theme-provider.tsx → ThemeProvider.tsx} +0 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx"
|
|
4
|
+
import Link from "next/link"
|
|
5
|
+
import { useState, useRef, useEffect, useCallback } from "react"
|
|
6
|
+
import { motion } from "framer-motion"
|
|
7
|
+
import { ScrollToTop } from "./ScrollToTop"
|
|
8
|
+
import { TocItem } from "@/lib/toc"
|
|
9
|
+
|
|
10
|
+
interface TocObserverProps {
|
|
11
|
+
data: TocItem[]
|
|
12
|
+
activeId?: string | null
|
|
13
|
+
onActiveIdChange?: (id: string | null) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function TocObserver({
|
|
17
|
+
data,
|
|
18
|
+
activeId: externalActiveId,
|
|
19
|
+
onActiveIdChange,
|
|
20
|
+
}: TocObserverProps) {
|
|
21
|
+
const itemRefs = useRef<Map<string, HTMLAnchorElement>>(new Map())
|
|
22
|
+
|
|
23
|
+
const activeId = externalActiveId ?? null
|
|
24
|
+
|
|
25
|
+
const handleLinkClick = useCallback(
|
|
26
|
+
(id: string) => {
|
|
27
|
+
onActiveIdChange?.(id)
|
|
28
|
+
},
|
|
29
|
+
[onActiveIdChange]
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// Function to check if an item has children
|
|
33
|
+
const hasChildren = (currentId: string, currentLevel: number) => {
|
|
34
|
+
const currentIndex = data.findIndex((item) => item.href.slice(1) === currentId)
|
|
35
|
+
if (currentIndex === -1 || currentIndex === data.length - 1) return false
|
|
36
|
+
|
|
37
|
+
const nextItem = data[currentIndex + 1]
|
|
38
|
+
return nextItem.level > currentLevel
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Calculate scroll progress for the active section
|
|
42
|
+
const [scrollProgress, setScrollProgress] = useState(0)
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const handleScroll = () => {
|
|
46
|
+
if (!activeId) return
|
|
47
|
+
|
|
48
|
+
const activeElement = document.getElementById(activeId)
|
|
49
|
+
if (!activeElement) return
|
|
50
|
+
|
|
51
|
+
const rect = activeElement.getBoundingClientRect()
|
|
52
|
+
const windowHeight = window.innerHeight
|
|
53
|
+
const elementTop = rect.top
|
|
54
|
+
const elementHeight = rect.height
|
|
55
|
+
|
|
56
|
+
// Calculate how much of the element is visible
|
|
57
|
+
let progress = 0
|
|
58
|
+
if (elementTop < windowHeight) {
|
|
59
|
+
progress = Math.min(1, (windowHeight - elementTop) / (windowHeight + elementHeight))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setScrollProgress(progress)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const container = document.getElementById("scroll-container") || window
|
|
66
|
+
|
|
67
|
+
container.addEventListener("scroll", handleScroll, { passive: true })
|
|
68
|
+
|
|
69
|
+
// Initial calculation
|
|
70
|
+
handleScroll()
|
|
71
|
+
|
|
72
|
+
return () => container.removeEventListener("scroll", handleScroll)
|
|
73
|
+
}, [activeId])
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className="relative">
|
|
77
|
+
<div className="text-foreground/70 hover:text-foreground relative text-sm transition-colors">
|
|
78
|
+
<div className="flex flex-col gap-0">
|
|
79
|
+
{data.map(({ href, level, text }, index) => {
|
|
80
|
+
const id = href.slice(1)
|
|
81
|
+
const isActive = activeId === id
|
|
82
|
+
const indent = level > 1 ? (level - 1) * 20 : 0
|
|
83
|
+
// Prefix with underscore to indicate intentionally unused
|
|
84
|
+
const _isParent = hasChildren(id, level)
|
|
85
|
+
const _isLastInLevel = index === data.length - 1 || data[index + 1].level <= level
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div key={href} className="relative">
|
|
89
|
+
{/* Simple L-shaped connector */}
|
|
90
|
+
{level > 1 && (
|
|
91
|
+
<div
|
|
92
|
+
className={clsx("absolute top-0 h-full w-6", {
|
|
93
|
+
"left-[6px]": indent === 20, // Level 2
|
|
94
|
+
"left-[22px]": indent === 40, // Level 3
|
|
95
|
+
"left-[38px]": indent === 60, // Level 4
|
|
96
|
+
})}
|
|
97
|
+
>
|
|
98
|
+
{/* Vertical line */}
|
|
99
|
+
<div
|
|
100
|
+
className={clsx(
|
|
101
|
+
"absolute left-0 top-0 h-full w-px",
|
|
102
|
+
isActive
|
|
103
|
+
? "bg-primary/20 dark:bg-primary/30"
|
|
104
|
+
: "bg-border/50 dark:bg-border/50"
|
|
105
|
+
)}
|
|
106
|
+
>
|
|
107
|
+
{isActive && (
|
|
108
|
+
<motion.div
|
|
109
|
+
className="bg-primary absolute left-0 top-0 h-full w-full origin-top"
|
|
110
|
+
initial={{ scaleY: 0 }}
|
|
111
|
+
animate={{ scaleY: scrollProgress }}
|
|
112
|
+
transition={{ duration: 0.3 }}
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
{/* Horizontal line */}
|
|
118
|
+
<div
|
|
119
|
+
className={clsx(
|
|
120
|
+
"absolute left-0 top-1/2 h-px w-6",
|
|
121
|
+
isActive
|
|
122
|
+
? "bg-primary/20 dark:bg-primary/30"
|
|
123
|
+
: "bg-border/50 dark:bg-border/50"
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
126
|
+
{isActive && (
|
|
127
|
+
<motion.div
|
|
128
|
+
className="bg-primary dark:bg-accent absolute left-0 top-0 h-full w-full origin-left"
|
|
129
|
+
initial={{ scaleX: 0 }}
|
|
130
|
+
animate={{ scaleX: scrollProgress }}
|
|
131
|
+
transition={{ duration: 0.3, delay: 0.1 }}
|
|
132
|
+
/>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
|
|
138
|
+
<Link
|
|
139
|
+
href={href}
|
|
140
|
+
onClick={() => handleLinkClick(id)}
|
|
141
|
+
className={clsx("relative flex items-center py-2 transition-colors", {
|
|
142
|
+
"text-primary dark:text-primary font-medium": isActive,
|
|
143
|
+
"text-muted-foreground hover:text-foreground dark:hover:text-foreground/90":
|
|
144
|
+
!isActive,
|
|
145
|
+
})}
|
|
146
|
+
style={{
|
|
147
|
+
paddingLeft: `${indent}px`,
|
|
148
|
+
marginLeft: level > 1 ? "12px" : "0",
|
|
149
|
+
}}
|
|
150
|
+
ref={(el) => {
|
|
151
|
+
const map = itemRefs.current
|
|
152
|
+
if (el) {
|
|
153
|
+
map.set(id, el)
|
|
154
|
+
} else {
|
|
155
|
+
map.delete(id)
|
|
156
|
+
}
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
{/* Circle indicator */}
|
|
160
|
+
<div className="relative flex h-4 w-4 shrink-0 items-center justify-center">
|
|
161
|
+
<div
|
|
162
|
+
className={clsx(
|
|
163
|
+
"relative z-10 h-1.5 w-1.5 rounded-full transition-all duration-300",
|
|
164
|
+
{
|
|
165
|
+
"bg-primary dark:bg-primary/90 scale-100": isActive,
|
|
166
|
+
"bg-muted-foreground/30 dark:bg-muted-foreground/30 group-hover:bg-primary/50 dark:group-hover:bg-primary/50 scale-75 group-hover:scale-100":
|
|
167
|
+
!isActive,
|
|
168
|
+
}
|
|
169
|
+
)}
|
|
170
|
+
>
|
|
171
|
+
{isActive && (
|
|
172
|
+
<motion.div
|
|
173
|
+
className="bg-primary/20 dark:bg-primary/30 absolute inset-0 rounded-full"
|
|
174
|
+
initial={{ scale: 1 }}
|
|
175
|
+
animate={{ scale: 1.8 }}
|
|
176
|
+
transition={{
|
|
177
|
+
duration: 2,
|
|
178
|
+
repeat: Infinity,
|
|
179
|
+
repeatType: "reverse",
|
|
180
|
+
}}
|
|
181
|
+
/>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<span className="truncate text-sm">{text}</span>
|
|
187
|
+
</Link>
|
|
188
|
+
</div>
|
|
189
|
+
)
|
|
190
|
+
})}
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
{/* Add scroll to top link at the bottom of TOC */}
|
|
194
|
+
<ScrollToTop className="mt-6" />
|
|
195
|
+
</div>
|
|
196
|
+
)
|
|
197
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
|
-
import { ModeToggle } from "@/components/
|
|
2
|
+
import { ModeToggle } from "@/components/ThemeToggle";
|
|
3
3
|
import docuData from "@/docu.json";
|
|
4
4
|
import * as LucideIcons from "lucide-react";
|
|
5
5
|
|
|
@@ -20,21 +20,25 @@ const docuConfig = docuData as {
|
|
|
20
20
|
footer: FooterConfig;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
interface FooterProps {
|
|
24
|
+
id?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function Footer({ id }: FooterProps) {
|
|
24
28
|
const { footer } = docuConfig;
|
|
25
29
|
return (
|
|
26
|
-
<footer className="w-full py-8 border-t bg-background">
|
|
30
|
+
<footer id={id} className="w-full py-8 border-t bg-background">
|
|
27
31
|
<div className="container flex flex-col lg:flex-row items-center justify-between text-sm">
|
|
28
32
|
<div className="flex flex-col items-center lg:items-start justify-start gap-4 w-full lg:w-3/5 text-center lg:text-left">
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
<p className="text-muted-foreground">
|
|
34
|
+
Copyright © {new Date().getFullYear()} {footer.copyright} - <MadeWith />
|
|
35
|
+
</p>
|
|
36
|
+
<div className="flex items-center justify-center lg:justify-start gap-6 mt-2 w-full">
|
|
37
|
+
<FooterButtons />
|
|
38
|
+
</div>
|
|
35
39
|
</div>
|
|
36
40
|
<div className="hidden lg:flex items-center justify-end lg:w-2/5">
|
|
37
|
-
|
|
41
|
+
<ModeToggle />
|
|
38
42
|
</div>
|
|
39
43
|
</div>
|
|
40
44
|
</footer>
|
|
@@ -79,9 +83,9 @@ export function MadeWith() {
|
|
|
79
83
|
<span className="text-muted-foreground">Made with </span>
|
|
80
84
|
<span className="text-primary">
|
|
81
85
|
<Link href="https://www.docubook.pro" target="_blank" rel="noopener noreferrer" className="underline underline-offset-2 text-muted-foreground">
|
|
82
|
-
|
|
86
|
+
DocuBook
|
|
83
87
|
</Link>
|
|
84
|
-
|
|
88
|
+
</span>
|
|
85
89
|
</>
|
|
86
90
|
);
|
|
87
91
|
}
|
|
@@ -1,101 +1,73 @@
|
|
|
1
1
|
"use client"
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "@/components/
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import { DialogTitle, DialogDescription } from "@/components/ui/dialog";
|
|
14
|
-
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
15
|
-
import DocsMenu from "@/components/docs-menu";
|
|
16
|
-
import { ModeToggle } from "@/components/theme-toggle";
|
|
17
|
-
import ContextPopover from "@/components/context-popover";
|
|
2
|
+
import { Sheet, SheetClose, SheetContent, SheetHeader, SheetTrigger } from "@/components/ui/sheet"
|
|
3
|
+
import { Logo, NavMenu } from "@/components/navbar"
|
|
4
|
+
import { Button } from "@/components/ui/button"
|
|
5
|
+
import { PanelRight } from "lucide-react"
|
|
6
|
+
import { DialogTitle, DialogDescription } from "@/components/ui/dialog"
|
|
7
|
+
import { ScrollArea } from "@/components/ui/scroll-area"
|
|
8
|
+
import DocsMenu from "@/components/DocsMenu"
|
|
9
|
+
import { ModeToggle } from "@/components/ThemeToggle"
|
|
10
|
+
import ContextPopover from "@/components/ContextPopover"
|
|
11
|
+
import Search from "@/components/SearchBox"
|
|
12
|
+
import GitHubButton from "@/components/Github"
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
export function ToggleButton({
|
|
21
|
-
collapsed,
|
|
22
|
-
onToggle
|
|
23
|
-
}: {
|
|
24
|
-
collapsed: boolean,
|
|
25
|
-
onToggle: () => void
|
|
26
|
-
}) {
|
|
14
|
+
export function Leftbar() {
|
|
27
15
|
return (
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
onClick={onToggle}
|
|
34
|
-
>
|
|
35
|
-
{collapsed ? (
|
|
36
|
-
<PanelLeftOpen size={18} />
|
|
37
|
-
) : (
|
|
38
|
-
<PanelLeftClose size={18} />
|
|
39
|
-
)}
|
|
40
|
-
</Button>
|
|
41
|
-
</div>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
16
|
+
<aside className="sticky top-0 hidden h-screen w-[280px] shrink-0 flex-col lg:flex">
|
|
17
|
+
{/* Logo */}
|
|
18
|
+
<div className="flex h-14 shrink-0 items-center px-5">
|
|
19
|
+
<Logo />
|
|
20
|
+
</div>
|
|
44
21
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
22
|
+
<div className="flex shrink-0 items-center gap-2 px-4 pb-4">
|
|
23
|
+
<Search className="min-w-[250px] max-w-[250px]" />
|
|
24
|
+
</div>
|
|
48
25
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{/* Scrollable Content */}
|
|
56
|
-
<ScrollArea className="flex-1 px-0.5 pb-4">
|
|
57
|
-
{!collapsed && (
|
|
58
|
-
<div className="space-y-2">
|
|
59
|
-
<ContextPopover />
|
|
60
|
-
<DocsMenu />
|
|
61
|
-
</div>
|
|
62
|
-
)}
|
|
26
|
+
{/* Scrollable Navigation */}
|
|
27
|
+
<ScrollArea className="flex-1 px-4">
|
|
28
|
+
<div className="space-y-2">
|
|
29
|
+
<ContextPopover />
|
|
30
|
+
<DocsMenu />
|
|
31
|
+
</div>
|
|
63
32
|
</ScrollArea>
|
|
33
|
+
|
|
34
|
+
{/* Bottom: Theme Toggle */}
|
|
35
|
+
<div className="flex px-4 py-3">
|
|
36
|
+
<ModeToggle />
|
|
37
|
+
</div>
|
|
64
38
|
</aside>
|
|
65
|
-
)
|
|
39
|
+
)
|
|
66
40
|
}
|
|
67
41
|
|
|
68
42
|
export function SheetLeftbar() {
|
|
69
43
|
return (
|
|
70
44
|
<Sheet>
|
|
71
45
|
<SheetTrigger asChild>
|
|
72
|
-
<Button variant="ghost" size="icon" className="max-
|
|
73
|
-
<
|
|
46
|
+
<Button variant="ghost" size="icon" className="hidden max-md:flex">
|
|
47
|
+
<PanelRight className="text-muted-foreground h-6 w-6 shrink-0" />
|
|
74
48
|
</Button>
|
|
75
49
|
</SheetTrigger>
|
|
76
|
-
<SheetContent className="flex flex-col gap-4 px-0" side="
|
|
50
|
+
<SheetContent className="flex flex-col gap-4 px-0" side="right">
|
|
77
51
|
<DialogTitle className="sr-only">Navigation Menu</DialogTitle>
|
|
78
52
|
<DialogDescription className="sr-only">
|
|
79
53
|
Main navigation menu with links to different sections
|
|
80
54
|
</DialogDescription>
|
|
81
55
|
<SheetHeader>
|
|
82
|
-
<SheetClose className="px-
|
|
83
|
-
<
|
|
56
|
+
<SheetClose className="px-4" asChild>
|
|
57
|
+
<div className="flex items-center justify-between">
|
|
58
|
+
<GitHubButton />
|
|
59
|
+
<div className="mr-8">
|
|
60
|
+
<ModeToggle />
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
84
63
|
</SheetClose>
|
|
85
64
|
</SheetHeader>
|
|
86
65
|
<div className="flex flex-col gap-4 overflow-y-auto">
|
|
87
|
-
<div className="flex flex-col gap-2.5
|
|
88
|
-
<NavMenu
|
|
89
|
-
</div>
|
|
90
|
-
<div className="mx-2 px-5 space-y-2">
|
|
91
|
-
<ContextPopover />
|
|
92
|
-
<DocsMenu isSheet />
|
|
93
|
-
</div>
|
|
94
|
-
<div className="flex w-2/4 px-5">
|
|
95
|
-
<ModeToggle />
|
|
66
|
+
<div className="mx-2 mt-3 flex flex-col gap-2.5 px-5">
|
|
67
|
+
<NavMenu />
|
|
96
68
|
</div>
|
|
97
69
|
</div>
|
|
98
70
|
</SheetContent>
|
|
99
71
|
</Sheet>
|
|
100
|
-
)
|
|
72
|
+
)
|
|
101
73
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createContext, useState, useId } from "react"
|
|
2
|
+
|
|
3
|
+
type AccordionGroupContextType = {
|
|
4
|
+
inGroup: boolean
|
|
5
|
+
groupId: string
|
|
6
|
+
openTitle: string | null
|
|
7
|
+
setOpenTitle: (title: string | null) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const AccordionGroupContext = createContext<AccordionGroupContextType | null>(null)
|
|
11
|
+
|
|
12
|
+
export function AccordionGroupProvider({ children }: { children: React.ReactNode }) {
|
|
13
|
+
const [openTitle, setOpenTitle] = useState<string | null>(null)
|
|
14
|
+
const groupId = useId()
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<AccordionGroupContext.Provider value={{ inGroup: true, groupId, openTitle, setOpenTitle }}>
|
|
18
|
+
{children}
|
|
19
|
+
</AccordionGroupContext.Provider>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -1,31 +1,20 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import React, { ReactNode } from "react"
|
|
4
|
-
import clsx from "clsx"
|
|
5
|
-
import {
|
|
3
|
+
import React, { ReactNode } from "react"
|
|
4
|
+
import clsx from "clsx"
|
|
5
|
+
import { AccordionGroupProvider } from "@/components/markdown/AccordionContext"
|
|
6
6
|
|
|
7
7
|
interface AccordionGroupProps {
|
|
8
|
-
children: ReactNode
|
|
9
|
-
className?: string
|
|
8
|
+
children: ReactNode
|
|
9
|
+
className?: string
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const AccordionGroup: React.FC<AccordionGroupProps> = ({ children, className }) => {
|
|
13
|
-
|
|
14
13
|
return (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
className={clsx(
|
|
21
|
-
"border rounded-lg overflow-hidden",
|
|
22
|
-
className
|
|
23
|
-
)}
|
|
24
|
-
>
|
|
25
|
-
{children}
|
|
26
|
-
</div>
|
|
27
|
-
</AccordionGroupContext.Provider>
|
|
28
|
-
);
|
|
29
|
-
};
|
|
14
|
+
<AccordionGroupProvider>
|
|
15
|
+
<div className={clsx("overflow-hidden rounded-lg border", className)}>{children}</div>
|
|
16
|
+
</AccordionGroupProvider>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
30
19
|
|
|
31
|
-
export default AccordionGroup
|
|
20
|
+
export default AccordionGroup
|
|
@@ -1,62 +1,61 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client"
|
|
2
2
|
|
|
3
|
-
import { ReactNode,
|
|
4
|
-
import { ChevronRight } from
|
|
5
|
-
import * as Icons from "lucide-react"
|
|
6
|
-
import { cn } from
|
|
7
|
-
import { AccordionGroupContext } from
|
|
3
|
+
import { ReactNode, useContext, useState } from "react"
|
|
4
|
+
import { ChevronRight } from "lucide-react"
|
|
5
|
+
import * as Icons from "lucide-react"
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import { AccordionGroupContext } from "@/components/markdown/AccordionContext"
|
|
8
8
|
|
|
9
9
|
type AccordionProps = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
export default Accordion;
|
|
10
|
+
title: string
|
|
11
|
+
children?: ReactNode
|
|
12
|
+
icon?: keyof typeof Icons
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const Accordion: React.FC<AccordionProps> = ({ title, children, icon }: AccordionProps) => {
|
|
16
|
+
const groupContext = useContext(AccordionGroupContext)
|
|
17
|
+
const isInGroup = groupContext?.inGroup === true
|
|
18
|
+
const groupOpen = groupContext?.openTitle === title
|
|
19
|
+
const setGroupOpen = groupContext?.setOpenTitle
|
|
20
|
+
const [localOpen, setLocalOpen] = useState(false)
|
|
21
|
+
|
|
22
|
+
const isOpen = isInGroup ? groupOpen : localOpen
|
|
23
|
+
|
|
24
|
+
const handleToggle = () => {
|
|
25
|
+
if (isInGroup && setGroupOpen) {
|
|
26
|
+
setGroupOpen(groupOpen ? null : title)
|
|
27
|
+
} else {
|
|
28
|
+
setLocalOpen(!localOpen)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const Icon = icon ? (Icons[icon] as React.FC<{ className?: string }>) : null
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
className={cn(
|
|
37
|
+
!isInGroup && "rounded-lg border shadow-sm",
|
|
38
|
+
isInGroup && "border-border border-b last:border-b-0"
|
|
39
|
+
)}
|
|
40
|
+
>
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
onClick={handleToggle}
|
|
44
|
+
className="bg-muted/40 dark:bg-muted/20 hover:bg-muted/70 dark:hover:bg-muted/70 flex w-full cursor-pointer items-center gap-2 px-4 py-3 text-start transition-colors"
|
|
45
|
+
>
|
|
46
|
+
<ChevronRight
|
|
47
|
+
className={cn(
|
|
48
|
+
"text-muted-foreground h-4 w-4 shrink-0 transition-transform duration-200",
|
|
49
|
+
isOpen && "rotate-90"
|
|
50
|
+
)}
|
|
51
|
+
/>
|
|
52
|
+
{Icon && <Icon className="text-foreground h-4 w-4 shrink-0" />}
|
|
53
|
+
<h3 className="text-foreground m-0! text-base font-medium">{title}</h3>
|
|
54
|
+
</button>
|
|
55
|
+
|
|
56
|
+
{isOpen && <div className="dark:bg-muted/10 bg-muted/15 px-4 py-3">{children}</div>}
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default Accordion
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
SiSwift,
|
|
12
12
|
SiKotlin,
|
|
13
13
|
SiHtml5,
|
|
14
|
-
|
|
14
|
+
SiCss,
|
|
15
15
|
SiSass,
|
|
16
16
|
SiPostgresql,
|
|
17
17
|
SiGraphql,
|
|
@@ -68,7 +68,7 @@ const LanguageIcon = ({ lang }: { lang: string }) => {
|
|
|
68
68
|
js: <SiJavascript {...iconProps} />,
|
|
69
69
|
javascript: <SiJavascript {...iconProps} />,
|
|
70
70
|
html: <SiHtml5 {...iconProps} />,
|
|
71
|
-
css: <
|
|
71
|
+
css: <SiCss {...iconProps} />,
|
|
72
72
|
scss: <SiSass {...iconProps} />,
|
|
73
73
|
sass: <SiSass {...iconProps} />,
|
|
74
74
|
};
|