@sntlr/registry-shell 1.0.0
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/LICENSE +21 -0
- package/README.md +200 -0
- package/dist/adapter/custom.d.ts +47 -0
- package/dist/adapter/custom.js +53 -0
- package/dist/adapter/custom.js.map +1 -0
- package/dist/adapter/default.d.ts +40 -0
- package/dist/adapter/default.js +202 -0
- package/dist/adapter/default.js.map +1 -0
- package/dist/cli/build.d.ts +1 -0
- package/dist/cli/build.js +31 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/dev.d.ts +1 -0
- package/dist/cli/dev.js +26 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/index.d.ts +12 -0
- package/dist/cli/index.js +49 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +1 -0
- package/dist/cli/init.js +70 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/shared.d.ts +33 -0
- package/dist/cli/shared.js +278 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/cli/start.d.ts +1 -0
- package/dist/cli/start.js +24 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/config-loader.d.ts +49 -0
- package/dist/config-loader.js +140 -0
- package/dist/define-config.d.ts +188 -0
- package/dist/define-config.js +21 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +9 -0
- package/package.json +124 -0
- package/src/adapter/custom.ts +90 -0
- package/src/adapter/default.ts +241 -0
- package/src/cli/build.ts +38 -0
- package/src/cli/dev.ts +38 -0
- package/src/cli/index.ts +52 -0
- package/src/cli/init.ts +76 -0
- package/src/cli/shared.ts +306 -0
- package/src/cli/start.ts +28 -0
- package/src/config-loader.ts +190 -0
- package/src/define-config.ts +206 -0
- package/src/index.ts +17 -0
- package/src/next-app/app/[...asset]/route.ts +81 -0
- package/src/next-app/app/_user-global.css +6 -0
- package/src/next-app/app/_user-sources.css +9 -0
- package/src/next-app/app/a11y/[name]/route.ts +19 -0
- package/src/next-app/app/api/search-index/route.ts +19 -0
- package/src/next-app/app/components/[name]/page.tsx +61 -0
- package/src/next-app/app/components/layout.tsx +18 -0
- package/src/next-app/app/docs/[slug]/page.tsx +53 -0
- package/src/next-app/app/docs/layout.tsx +18 -0
- package/src/next-app/app/globals.css +329 -0
- package/src/next-app/app/layout.tsx +102 -0
- package/src/next-app/app/page.tsx +9 -0
- package/src/next-app/app/preview-snapshot/[name]/page.tsx +20 -0
- package/src/next-app/app/preview-snapshot/layout.tsx +17 -0
- package/src/next-app/app/props/[name]/route.ts +19 -0
- package/src/next-app/app/r/[name]/route.ts +14 -0
- package/src/next-app/app/tests/[name]/route.ts +19 -0
- package/src/next-app/components/a11y-info.tsx +287 -0
- package/src/next-app/components/a11y-provider.tsx +39 -0
- package/src/next-app/components/component-breadcrumb.tsx +55 -0
- package/src/next-app/components/component-icon.tsx +140 -0
- package/src/next-app/components/component-preview.tsx +13 -0
- package/src/next-app/components/component-tabs.tsx +209 -0
- package/src/next-app/components/docs-toc.tsx +86 -0
- package/src/next-app/components/global-mobile-sidebar.tsx +35 -0
- package/src/next-app/components/header.tsx +188 -0
- package/src/next-app/components/heading-anchor.tsx +52 -0
- package/src/next-app/components/homepage-demo.tsx +180 -0
- package/src/next-app/components/locale-toggle.tsx +35 -0
- package/src/next-app/components/localized-mdx-client.tsx +14 -0
- package/src/next-app/components/localized-mdx.tsx +27 -0
- package/src/next-app/components/mobile-sidebar.tsx +22 -0
- package/src/next-app/components/nav-data-provider.tsx +37 -0
- package/src/next-app/components/navigation-progress.tsx +62 -0
- package/src/next-app/components/preview-canvas.tsx +368 -0
- package/src/next-app/components/preview-controls.tsx +94 -0
- package/src/next-app/components/preview-layout.tsx +218 -0
- package/src/next-app/components/props-table.tsx +134 -0
- package/src/next-app/components/resizable-preview.tsx +101 -0
- package/src/next-app/components/search.tsx +177 -0
- package/src/next-app/components/settings-modal.tsx +98 -0
- package/src/next-app/components/shell-ui/accordion.tsx +70 -0
- package/src/next-app/components/shell-ui/backdrop.tsx +29 -0
- package/src/next-app/components/shell-ui/badge.tsx +55 -0
- package/src/next-app/components/shell-ui/breadcrumb.tsx +120 -0
- package/src/next-app/components/shell-ui/button.tsx +64 -0
- package/src/next-app/components/shell-ui/card.tsx +127 -0
- package/src/next-app/components/shell-ui/checkbox.tsx +33 -0
- package/src/next-app/components/shell-ui/dialog.tsx +171 -0
- package/src/next-app/components/shell-ui/empty-state.tsx +66 -0
- package/src/next-app/components/shell-ui/input.tsx +27 -0
- package/src/next-app/components/shell-ui/kbd.tsx +30 -0
- package/src/next-app/components/shell-ui/label.tsx +25 -0
- package/src/next-app/components/shell-ui/select.tsx +204 -0
- package/src/next-app/components/shell-ui/separator.tsx +32 -0
- package/src/next-app/components/shell-ui/skeleton.tsx +18 -0
- package/src/next-app/components/shell-ui/table.tsx +124 -0
- package/src/next-app/components/shell-ui/tabs.tsx +102 -0
- package/src/next-app/components/shell-ui/toggle.tsx +56 -0
- package/src/next-app/components/sidebar-layout.tsx +37 -0
- package/src/next-app/components/sidebar-provider.tsx +75 -0
- package/src/next-app/components/sidebar.tsx +222 -0
- package/src/next-app/components/snapshot-preview.tsx +28 -0
- package/src/next-app/components/test-info.tsx +155 -0
- package/src/next-app/components/theme-provider.tsx +16 -0
- package/src/next-app/components/theme-toggle.tsx +21 -0
- package/src/next-app/components/translated-text.tsx +8 -0
- package/src/next-app/fallback/homepage.tsx +112 -0
- package/src/next-app/fallback/previews.ts +17 -0
- package/src/next-app/hooks/use-active-section.ts +23 -0
- package/src/next-app/hooks/use-controls.ts +72 -0
- package/src/next-app/hooks/use-mobile.ts +19 -0
- package/src/next-app/lib/branding.ts +52 -0
- package/src/next-app/lib/components-nav.ts +8 -0
- package/src/next-app/lib/docs.ts +16 -0
- package/src/next-app/lib/github.ts +38 -0
- package/src/next-app/lib/i18n.tsx +630 -0
- package/src/next-app/lib/locales.ts +17 -0
- package/src/next-app/lib/preview-loader.ts +7 -0
- package/src/next-app/lib/registry-adapter.ts +199 -0
- package/src/next-app/lib/utils.ts +6 -0
- package/src/next-app/next-env.d.ts +6 -0
- package/src/next-app/next.config.ts +101 -0
- package/src/next-app/postcss.config.mjs +7 -0
- package/src/next-app/public/favicon.ico +0 -0
- package/src/next-app/public/favicon_dark.svg +3 -0
- package/src/next-app/public/favicon_light.svg +3 -0
- package/src/next-app/registry.config.ts +50 -0
- package/src/next-app/tsconfig.json +29 -0
- package/src/next-app/user-aliases.d.ts +17 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Button } from "@shell/components/shell-ui/button"
|
|
4
|
+
import { Badge } from "@shell/components/shell-ui/badge"
|
|
5
|
+
import { Input } from "@shell/components/shell-ui/input"
|
|
6
|
+
import { Checkbox } from "@shell/components/shell-ui/checkbox"
|
|
7
|
+
import { Label } from "@shell/components/shell-ui/label"
|
|
8
|
+
import { Skeleton } from "@shell/components/shell-ui/skeleton"
|
|
9
|
+
import { Separator } from "@shell/components/shell-ui/separator"
|
|
10
|
+
import { Toggle } from "@shell/components/shell-ui/toggle"
|
|
11
|
+
import {
|
|
12
|
+
Card,
|
|
13
|
+
CardHeader,
|
|
14
|
+
CardTitle,
|
|
15
|
+
CardDescription,
|
|
16
|
+
CardContent,
|
|
17
|
+
} from "@shell/components/shell-ui/card"
|
|
18
|
+
import {
|
|
19
|
+
Bold,
|
|
20
|
+
Italic,
|
|
21
|
+
Underline,
|
|
22
|
+
Star,
|
|
23
|
+
Heart,
|
|
24
|
+
Zap,
|
|
25
|
+
Check,
|
|
26
|
+
} from "lucide-react"
|
|
27
|
+
|
|
28
|
+
function DemoCard({ children }: { children: React.ReactNode }) {
|
|
29
|
+
return (
|
|
30
|
+
<div className="rounded-lg border border-border bg-card p-4 shadow-sm w-56 shrink-0">
|
|
31
|
+
{children}
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const leftColumn = [
|
|
37
|
+
<DemoCard key="buttons">
|
|
38
|
+
<p className="text-xs font-medium text-muted-foreground mb-2">Buttons</p>
|
|
39
|
+
<div className="flex flex-wrap gap-2">
|
|
40
|
+
<Button size="sm">Primary</Button>
|
|
41
|
+
<Button size="sm" variant="outline">Outline</Button>
|
|
42
|
+
<Button size="sm" variant="ghost">Ghost</Button>
|
|
43
|
+
</div>
|
|
44
|
+
</DemoCard>,
|
|
45
|
+
|
|
46
|
+
<DemoCard key="badges">
|
|
47
|
+
<p className="text-xs font-medium text-muted-foreground mb-2">Badges</p>
|
|
48
|
+
<div className="flex flex-wrap gap-2">
|
|
49
|
+
<Badge>Default</Badge>
|
|
50
|
+
<Badge variant="secondary">Secondary</Badge>
|
|
51
|
+
<Badge variant="destructive">Error</Badge>
|
|
52
|
+
<Badge variant="outline">Outline</Badge>
|
|
53
|
+
</div>
|
|
54
|
+
</DemoCard>,
|
|
55
|
+
|
|
56
|
+
<DemoCard key="input">
|
|
57
|
+
<p className="text-xs font-medium text-muted-foreground mb-2">Input</p>
|
|
58
|
+
<Input placeholder="Type something..." className="h-8 text-xs" />
|
|
59
|
+
</DemoCard>,
|
|
60
|
+
|
|
61
|
+
<DemoCard key="skeleton">
|
|
62
|
+
<p className="text-xs font-medium text-muted-foreground mb-2">Skeleton</p>
|
|
63
|
+
<div className="flex items-center gap-3">
|
|
64
|
+
<Skeleton className="h-8 w-8 rounded-full" />
|
|
65
|
+
<div className="space-y-1.5 flex-1">
|
|
66
|
+
<Skeleton className="h-3 w-full" />
|
|
67
|
+
<Skeleton className="h-3 w-3/4" />
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</DemoCard>,
|
|
71
|
+
|
|
72
|
+
<DemoCard key="card">
|
|
73
|
+
<Card className="border-0 shadow-none p-0 gap-2">
|
|
74
|
+
<CardHeader className="p-0">
|
|
75
|
+
<CardTitle className="text-sm">Card Title</CardTitle>
|
|
76
|
+
<CardDescription className="text-xs">A sample card</CardDescription>
|
|
77
|
+
</CardHeader>
|
|
78
|
+
<CardContent className="p-0">
|
|
79
|
+
<p className="text-xs text-muted-foreground">Card content goes here.</p>
|
|
80
|
+
</CardContent>
|
|
81
|
+
</Card>
|
|
82
|
+
</DemoCard>,
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
const rightColumn = [
|
|
86
|
+
<DemoCard key="checkbox">
|
|
87
|
+
<p className="text-xs font-medium text-muted-foreground mb-2">Checkbox</p>
|
|
88
|
+
<div className="space-y-2">
|
|
89
|
+
<div className="flex items-center gap-2">
|
|
90
|
+
<Checkbox id="h-1" defaultChecked />
|
|
91
|
+
<Label htmlFor="h-1" className="text-xs">Completed</Label>
|
|
92
|
+
</div>
|
|
93
|
+
<div className="flex items-center gap-2">
|
|
94
|
+
<Checkbox id="h-2" />
|
|
95
|
+
<Label htmlFor="h-2" className="text-xs">In progress</Label>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</DemoCard>,
|
|
99
|
+
|
|
100
|
+
<DemoCard key="toggle">
|
|
101
|
+
<p className="text-xs font-medium text-muted-foreground mb-2">Toggle</p>
|
|
102
|
+
<div className="flex gap-1">
|
|
103
|
+
<Toggle size="sm" aria-label="Bold"><Bold className="size-3.5" /></Toggle>
|
|
104
|
+
<Toggle size="sm" aria-label="Italic"><Italic className="size-3.5" /></Toggle>
|
|
105
|
+
<Toggle size="sm" aria-label="Underline"><Underline className="size-3.5" /></Toggle>
|
|
106
|
+
</div>
|
|
107
|
+
</DemoCard>,
|
|
108
|
+
|
|
109
|
+
<DemoCard key="separator">
|
|
110
|
+
<p className="text-xs font-medium text-muted-foreground mb-2">Separator</p>
|
|
111
|
+
<div className="space-y-2">
|
|
112
|
+
<p className="text-xs">Above</p>
|
|
113
|
+
<Separator />
|
|
114
|
+
<p className="text-xs">Below</p>
|
|
115
|
+
</div>
|
|
116
|
+
</DemoCard>,
|
|
117
|
+
|
|
118
|
+
<DemoCard key="icons">
|
|
119
|
+
<p className="text-xs font-medium text-muted-foreground mb-2">Icons</p>
|
|
120
|
+
<div className="flex gap-3">
|
|
121
|
+
<Star className="size-5 text-amber-500" />
|
|
122
|
+
<Heart className="size-5 text-red-500" />
|
|
123
|
+
<Zap className="size-5 text-blue-500" />
|
|
124
|
+
<Check className="size-5 text-green-500" />
|
|
125
|
+
</div>
|
|
126
|
+
</DemoCard>,
|
|
127
|
+
|
|
128
|
+
<DemoCard key="buttons2">
|
|
129
|
+
<p className="text-xs font-medium text-muted-foreground mb-2">Sizes</p>
|
|
130
|
+
<div className="flex items-center gap-2">
|
|
131
|
+
<Button size="xs">XS</Button>
|
|
132
|
+
<Button size="sm">SM</Button>
|
|
133
|
+
<Button size="default">MD</Button>
|
|
134
|
+
</div>
|
|
135
|
+
</DemoCard>,
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
export function HomepageDemo() {
|
|
139
|
+
// Duplicate items for seamless infinite scroll
|
|
140
|
+
const left = [...leftColumn, ...leftColumn]
|
|
141
|
+
const right = [...rightColumn, ...rightColumn]
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<div className="absolute inset-0 overflow-hidden pointer-events-none opacity-30 hidden md:flex">
|
|
145
|
+
{/* Left column — scrolling up */}
|
|
146
|
+
<div
|
|
147
|
+
className="scroll-up absolute left-[8%] top-0 flex flex-col gap-4 -rotate-12"
|
|
148
|
+
style={{ animation: "scroll-up 30s linear infinite" }}
|
|
149
|
+
>
|
|
150
|
+
{left.map((item, i) => (
|
|
151
|
+
<div key={i}>{item}</div>
|
|
152
|
+
))}
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* Right column — scrolling down */}
|
|
156
|
+
<div
|
|
157
|
+
className="scroll-down absolute right-[8%] top-0 flex flex-col gap-4 rotate-12"
|
|
158
|
+
style={{ animation: "scroll-down 25s linear infinite" }}
|
|
159
|
+
>
|
|
160
|
+
{right.map((item, i) => (
|
|
161
|
+
<div key={i}>{item}</div>
|
|
162
|
+
))}
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<style jsx>{`
|
|
166
|
+
@keyframes scroll-up {
|
|
167
|
+
0% { transform: rotate(-12deg) translateY(0); }
|
|
168
|
+
100% { transform: rotate(-12deg) translateY(-50%); }
|
|
169
|
+
}
|
|
170
|
+
@keyframes scroll-down {
|
|
171
|
+
0% { transform: rotate(12deg) translateY(-50%); }
|
|
172
|
+
100% { transform: rotate(12deg) translateY(0); }
|
|
173
|
+
}
|
|
174
|
+
@media (prefers-reduced-motion: reduce) {
|
|
175
|
+
.scroll-up, .scroll-down { animation: none !important; }
|
|
176
|
+
}
|
|
177
|
+
`}</style>
|
|
178
|
+
</div>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useLocale } from "@shell/lib/i18n"
|
|
4
|
+
import { Button } from "@shell/components/shell-ui/button"
|
|
5
|
+
import { getShellLocales } from "@shell/lib/locales"
|
|
6
|
+
|
|
7
|
+
const LOCALES = getShellLocales()
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Cycles the UI locale through the configured set. Hidden entirely when the
|
|
11
|
+
* shell is in single-locale mode (the toggle would be a no-op).
|
|
12
|
+
*/
|
|
13
|
+
export function LocaleToggle() {
|
|
14
|
+
const { locale, setLocale } = useLocale()
|
|
15
|
+
|
|
16
|
+
if (LOCALES.length < 2) return null
|
|
17
|
+
|
|
18
|
+
const next = () => {
|
|
19
|
+
const i = LOCALES.indexOf(locale)
|
|
20
|
+
const nextIdx = (i + 1) % LOCALES.length
|
|
21
|
+
setLocale(LOCALES[nextIdx])
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Button
|
|
26
|
+
variant="ghost"
|
|
27
|
+
size="icon"
|
|
28
|
+
onClick={next}
|
|
29
|
+
aria-label="Cycle language"
|
|
30
|
+
className="text-xs font-semibold"
|
|
31
|
+
>
|
|
32
|
+
{locale.toUpperCase()}
|
|
33
|
+
</Button>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react"
|
|
4
|
+
import { useLocale } from "@shell/lib/i18n"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Client wrapper that picks one of the pre-rendered locale trees produced by
|
|
8
|
+
* the server-side `LocalizedMdx`. The trees are React nodes, not strings, so
|
|
9
|
+
* locale switching is a tree swap with no MDX recompilation.
|
|
10
|
+
*/
|
|
11
|
+
export function LocalizedMdxClient({ rendered }: { rendered: Record<string, ReactNode> }) {
|
|
12
|
+
const { locale } = useLocale()
|
|
13
|
+
return <>{rendered[locale] ?? rendered.en ?? Object.values(rendered)[0] ?? null}</>
|
|
14
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server component: pre-renders each locale's MDX into a React tree using
|
|
3
|
+
* `next-mdx-remote/rsc`, then hands the keyed map to a client wrapper that
|
|
4
|
+
* picks the active locale. Lets us keep client-side locale switching without
|
|
5
|
+
* the "MDXRemote is an async Client Component" error that fires when /rsc
|
|
6
|
+
* runs inside a "use client" boundary.
|
|
7
|
+
*/
|
|
8
|
+
import { MDXRemote } from "next-mdx-remote/rsc"
|
|
9
|
+
import remarkGfm from "remark-gfm"
|
|
10
|
+
import { mdxHeadings } from "@shell/components/heading-anchor"
|
|
11
|
+
import { LocalizedMdxClient } from "@shell/components/localized-mdx-client"
|
|
12
|
+
|
|
13
|
+
const mdxOptions = {
|
|
14
|
+
mdxOptions: {
|
|
15
|
+
remarkPlugins: [remarkGfm],
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function LocalizedMdx({ locales }: { locales: Record<string, string> }) {
|
|
20
|
+
const rendered: Record<string, React.ReactNode> = {}
|
|
21
|
+
for (const [loc, source] of Object.entries(locales)) {
|
|
22
|
+
rendered[loc] = (
|
|
23
|
+
<MDXRemote source={source} options={mdxOptions} components={mdxHeadings} />
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
return <LocalizedMdxClient rendered={rendered} />
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Sidebar } from "@shell/components/sidebar"
|
|
4
|
+
import { useMobileSidebar } from "@shell/components/sidebar-provider"
|
|
5
|
+
import type { DocMeta } from "@shell/lib/docs"
|
|
6
|
+
import type { ComponentMeta } from "@shell/lib/components-nav"
|
|
7
|
+
|
|
8
|
+
export function MobileSidebar({
|
|
9
|
+
docs,
|
|
10
|
+
components,
|
|
11
|
+
}: {
|
|
12
|
+
docs: DocMeta[]
|
|
13
|
+
components: ComponentMeta[]
|
|
14
|
+
}) {
|
|
15
|
+
const { open, close } = useMobileSidebar()
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="md:hidden">
|
|
19
|
+
<Sidebar docs={docs} components={components} open={open} onClose={close} />
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, type ReactNode } from "react"
|
|
4
|
+
import type { DocMeta } from "@shell/lib/docs"
|
|
5
|
+
import type { ComponentMeta } from "@shell/lib/components-nav"
|
|
6
|
+
|
|
7
|
+
interface NavData {
|
|
8
|
+
docs: DocMeta[]
|
|
9
|
+
components: ComponentMeta[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const NavDataContext = createContext<NavData | null>(null)
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Provides the full navigation data (docs + components + blocks) to any
|
|
16
|
+
* descendant client component. Used by the Header to render section tabs
|
|
17
|
+
* without having to load the data itself.
|
|
18
|
+
*/
|
|
19
|
+
export function NavDataProvider({
|
|
20
|
+
docs,
|
|
21
|
+
components,
|
|
22
|
+
children,
|
|
23
|
+
}: {
|
|
24
|
+
docs: DocMeta[]
|
|
25
|
+
components: ComponentMeta[]
|
|
26
|
+
children: ReactNode
|
|
27
|
+
}) {
|
|
28
|
+
return (
|
|
29
|
+
<NavDataContext.Provider value={{ docs, components }}>
|
|
30
|
+
{children}
|
|
31
|
+
</NavDataContext.Provider>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useNavData(): NavData | null {
|
|
36
|
+
return useContext(NavDataContext)
|
|
37
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useSyncExternalStore } from "react"
|
|
4
|
+
import { usePathname } from "next/navigation"
|
|
5
|
+
|
|
6
|
+
// Simple external store for navigation state
|
|
7
|
+
let listeners: Array<() => void> = []
|
|
8
|
+
let navProgress = { loading: false, progress: 0 }
|
|
9
|
+
|
|
10
|
+
function setNav(updates: Partial<typeof navProgress>) {
|
|
11
|
+
navProgress = { ...navProgress, ...updates }
|
|
12
|
+
listeners.forEach((l) => l())
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function subscribe(listener: () => void) {
|
|
16
|
+
listeners.push(listener)
|
|
17
|
+
return () => { listeners = listeners.filter((l) => l !== listener) }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getSnapshot() { return navProgress }
|
|
21
|
+
|
|
22
|
+
export function NavigationProgress() {
|
|
23
|
+
const pathname = usePathname()
|
|
24
|
+
const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
|
|
25
|
+
const prevPathname = useRef(pathname)
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (prevPathname.current !== pathname) {
|
|
29
|
+
prevPathname.current = pathname
|
|
30
|
+
setNav({ progress: 100 })
|
|
31
|
+
const timer = setTimeout(() => setNav({ loading: false, progress: 0 }), 200)
|
|
32
|
+
return () => clearTimeout(timer)
|
|
33
|
+
}
|
|
34
|
+
}, [pathname])
|
|
35
|
+
|
|
36
|
+
// Intercept link clicks to show progress
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
function handleClick(e: MouseEvent) {
|
|
39
|
+
const link = (e.target as HTMLElement).closest("a")
|
|
40
|
+
if (!link) return
|
|
41
|
+
const href = link.getAttribute("href")
|
|
42
|
+
if (!href || href.startsWith("#") || href.startsWith("http") || href.startsWith("mailto:")) return
|
|
43
|
+
if (href === pathname) return
|
|
44
|
+
setNav({ loading: true, progress: 30 })
|
|
45
|
+
setTimeout(() => setNav({ progress: 70 }), 100)
|
|
46
|
+
}
|
|
47
|
+
document.addEventListener("click", handleClick)
|
|
48
|
+
return () => document.removeEventListener("click", handleClick)
|
|
49
|
+
}, [pathname])
|
|
50
|
+
|
|
51
|
+
if (!state.loading && state.progress === 0) return null
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className="fixed top-0 left-0 z-[100] h-0.5 bg-primary transition-all duration-300 ease-out"
|
|
56
|
+
style={{
|
|
57
|
+
width: `${state.progress}%`,
|
|
58
|
+
opacity: state.progress >= 100 ? 0 : 1,
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|