@inklu/docs 0.2.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/components.json +25 -0
  3. package/dist/index.d.mts +271 -0
  4. package/dist/index.d.ts +271 -0
  5. package/dist/index.js +2455 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +2368 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/dist/shiki.d.mts +10 -0
  10. package/dist/shiki.d.ts +10 -0
  11. package/dist/shiki.js +47 -0
  12. package/dist/shiki.js.map +1 -0
  13. package/dist/shiki.mjs +22 -0
  14. package/dist/shiki.mjs.map +1 -0
  15. package/dist/styles.css +2556 -0
  16. package/package.json +55 -0
  17. package/src/components/content/changelog-content.tsx +119 -0
  18. package/src/components/content/code-block.tsx +111 -0
  19. package/src/components/content/docs-content.tsx +113 -0
  20. package/src/components/layout/docs-layout.tsx +192 -0
  21. package/src/components/layout/site-header.tsx +152 -0
  22. package/src/components/layout/site-layout.tsx +28 -0
  23. package/src/components/layout/site-provider.tsx +23 -0
  24. package/src/components/layout/site-template.tsx +20 -0
  25. package/src/components/layout/theme-provider.tsx +10 -0
  26. package/src/components/layout/theme-switcher.tsx +42 -0
  27. package/src/components/motion-primitives/morph-icon.tsx +97 -0
  28. package/src/components/motion-primitives/progressive-blur.tsx +64 -0
  29. package/src/components/motion-primitives/sliding-number.tsx +131 -0
  30. package/src/components/motion-primitives/text-effect.tsx +290 -0
  31. package/src/components/motion-primitives/text-morph.tsx +77 -0
  32. package/src/components/motion-primitives/text-scramble.tsx +87 -0
  33. package/src/components/shared/command-block.tsx +193 -0
  34. package/src/components/shared/scroll-fade.tsx +22 -0
  35. package/src/components/ui/badge.tsx +50 -0
  36. package/src/components/ui/brand-logo.tsx +68 -0
  37. package/src/components/ui/button-group.tsx +87 -0
  38. package/src/components/ui/button.tsx +104 -0
  39. package/src/components/ui/card.tsx +97 -0
  40. package/src/components/ui/direction.tsx +6 -0
  41. package/src/components/ui/hover-card.tsx +51 -0
  42. package/src/components/ui/item.tsx +201 -0
  43. package/src/components/ui/separator.tsx +25 -0
  44. package/src/components/ui/toast.tsx +322 -0
  45. package/src/components/ui/tooltip.tsx +66 -0
  46. package/src/hooks/use-copy-to-clipboard.ts +54 -0
  47. package/src/index.ts +30 -0
  48. package/src/lib/constants.ts +63 -0
  49. package/src/lib/motions.ts +21 -0
  50. package/src/lib/utils/audio.ts +56 -0
  51. package/src/lib/utils.ts +14 -0
  52. package/src/shiki.ts +25 -0
  53. package/src/styles.css +112 -0
  54. package/src/typeset.css +513 -0
  55. package/tsconfig.json +15 -0
  56. package/tsup.config.ts +11 -0
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@inklu/docs",
3
+ "version": "0.2.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "main": "dist/index.js",
8
+ "module": "dist/index.mjs",
9
+ "types": "dist/index.d.ts",
10
+ "style": "dist/styles.css",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js"
16
+ },
17
+ "./shiki": {
18
+ "types": "./dist/shiki.d.ts",
19
+ "import": "./dist/shiki.mjs",
20
+ "require": "./dist/shiki.js"
21
+ },
22
+ "./styles.css": "./dist/styles.css"
23
+ },
24
+ "dependencies": {
25
+ "@base-ui/react": "^1.6.0",
26
+ "@lisse/react": "^0.5.2",
27
+ "@radix-ui/react-icons": "^1.3.2",
28
+ "class-variance-authority": "^0.7.1",
29
+ "clsx": "^2.1.1",
30
+ "motion": "^12.42.2",
31
+ "next-themes": "^0.4.6",
32
+ "react-use-measure": "^2.1.7",
33
+ "shiki": "^4.3.1",
34
+ "tailwind-merge": "^3.6.0"
35
+ },
36
+ "peerDependencies": {
37
+ "next": "*",
38
+ "react": "*",
39
+ "react-dom": "*"
40
+ },
41
+ "devDependencies": {
42
+ "@tailwindcss/cli": "^4",
43
+ "@types/node": "^20.19.43",
44
+ "@types/react": "^19",
45
+ "@types/react-dom": "^19",
46
+ "tailwindcss": "^4",
47
+ "tsup": "^8.0.0",
48
+ "typescript": "^5"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup && pnpm run build:css",
52
+ "build:css": "tailwindcss -i ./src/styles.css -o ./dist/styles.css",
53
+ "dev": "tsup --watch & pnpm run build:css --watch"
54
+ }
55
+ }
@@ -0,0 +1,119 @@
1
+ "use client";
2
+
3
+ import { motion, useReducedMotion } from "motion/react";
4
+ import type { ReactNode } from "react";
5
+ import { Badge } from "@/components/ui/badge";
6
+ import { CHANGELOG, type ChangeKind } from "@/lib/constants";
7
+ import { colReveal } from "@/lib/motions";
8
+ import { cn, formatChangelogDate } from "@/lib/utils";
9
+
10
+ function Col({ children }: { children: ReactNode }) {
11
+ const reduced = useReducedMotion() ?? false;
12
+ return (
13
+ <motion.div
14
+ className="mx-auto w-full"
15
+ initial={reduced ? false : "hidden"}
16
+ whileInView="show"
17
+ viewport={{ once: true, margin: "0px 0px -64px 0px" }}
18
+ variants={colReveal}
19
+ >
20
+ {children}
21
+ </motion.div>
22
+ );
23
+ }
24
+
25
+ const KIND_LABEL: Record<ChangeKind, string> = {
26
+ new: "New",
27
+ improved: "Improved",
28
+ fixed: "Fixed",
29
+ };
30
+
31
+ function KindTag({ kind }: { kind: ChangeKind }) {
32
+ return (
33
+ <Badge variant="secondary" className="align-top mt-0.5">
34
+ {KIND_LABEL[kind]}
35
+ </Badge>
36
+ );
37
+ }
38
+
39
+ export function ChangelogContent() {
40
+ return (
41
+ <div className="w-full">
42
+ <div className="relative mx-auto w-full">
43
+ <main className="typeset typeset-docs mx-auto max-w-(--layout-content-width) w-full">
44
+ {/* Page Header (Title & Date) */}
45
+ <section id="changelog-header" className="scroll-mt-24">
46
+ <Col>
47
+ <h1 className="tracking-tight mb-2">Changelog</h1>
48
+ <p className="text-muted-foreground/60 mb-8 mt-0">
49
+ 16 February, 2026
50
+ </p>
51
+ </Col>
52
+ </section>
53
+
54
+ {CHANGELOG.map((entry: (typeof CHANGELOG)[number]) => (
55
+ <section
56
+ key={entry.version}
57
+ id={`v${entry.version}`}
58
+ className="scroll-mt-24"
59
+ >
60
+ <Col>
61
+ <div className="not-typeset mt-16 flex items-center gap-3">
62
+ <Badge
63
+ variant="secondary"
64
+ className="text-foreground align-middle"
65
+ >
66
+ {entry.version === "next" ? "Up next" : `v${entry.version}`}
67
+ </Badge>
68
+ <span className="text-muted-foreground">
69
+ {entry.date
70
+ ? formatChangelogDate(entry.date)
71
+ : "In progress"}
72
+ </span>
73
+ </div>
74
+ <h2>{entry.title}</h2>
75
+ <p>{entry.summary}</p>
76
+
77
+ <div className="not-typeset mt-4 overflow-hidden rounded-2xl bg-card">
78
+ {entry.changes.map(
79
+ (
80
+ change: (typeof CHANGELOG)[number]["changes"][number],
81
+ i: number,
82
+ ) => (
83
+ <div
84
+ key={change.text}
85
+ className={cn(
86
+ "flex gap-3 px-4 py-3",
87
+ i > 0 && "border-t border-border",
88
+ )}
89
+ >
90
+ <KindTag kind={change.kind} />
91
+ <span className="text-muted-foreground">
92
+ {change.text}
93
+ </span>
94
+ </div>
95
+ ),
96
+ )}
97
+ </div>
98
+ </Col>
99
+ </section>
100
+ ))}
101
+
102
+ <Col>
103
+ <p className="mt-16">
104
+ Full commit history lives on{" "}
105
+ <a
106
+ href="https://github.com/your-username/your-repo"
107
+ target="_blank"
108
+ rel="noopener noreferrer"
109
+ >
110
+ GitHub
111
+ </a>
112
+ .
113
+ </p>
114
+ </Col>
115
+ </main>
116
+ </div>
117
+ </div>
118
+ );
119
+ }
@@ -0,0 +1,111 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { CopyMorphIcon } from "@/components/shared/command-block";
5
+ import { Button } from "@/components/ui/button";
6
+ import { anchoredToastManager } from "@/components/ui/toast";
7
+ import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard";
8
+ import { cn } from "@/lib/utils";
9
+ import { copy } from "@/lib/utils/audio";
10
+
11
+ export interface CodeBlockProps {
12
+ html: string;
13
+ code: string;
14
+ className?: string;
15
+ }
16
+
17
+ export function CodeBlock({ html, code, className }: CodeBlockProps) {
18
+ const buttonRef = React.useRef<HTMLButtonElement>(null);
19
+ const { copyToClipboard, isCopied } = useCopyToClipboard({ timeout: 1800 });
20
+
21
+ async function handleCopy() {
22
+ const success = await copyToClipboard(code);
23
+ if (success) {
24
+ copy();
25
+ anchoredToastManager.add({
26
+ title: "Copied!",
27
+ positionerProps: {
28
+ anchor: buttonRef.current,
29
+ sideOffset: 6,
30
+ },
31
+ data: {
32
+ tooltipStyle: true,
33
+ },
34
+ timeout: 2000,
35
+ });
36
+ } else {
37
+ anchoredToastManager.add({
38
+ title: "Failed to copy",
39
+ positionerProps: {
40
+ anchor: buttonRef.current,
41
+ sideOffset: 6,
42
+ },
43
+ data: {
44
+ tooltipStyle: true,
45
+ },
46
+ timeout: 2000,
47
+ });
48
+ }
49
+ }
50
+
51
+ return (
52
+ <div
53
+ className={cn(
54
+ "group relative mt-6 overflow-hidden rounded-xl not-typeset",
55
+ "bg-code",
56
+ "transition-colors",
57
+ className,
58
+ )}
59
+ >
60
+ <Button
61
+ ref={buttonRef}
62
+ variant="ghost"
63
+ size="icon-xs"
64
+ onClick={handleCopy}
65
+ className={cn(
66
+ "absolute right-3 top-3 z-20 rounded-lg",
67
+ "bg-transparent",
68
+ "text-muted-foreground",
69
+ "opacity-0",
70
+ "translate-y-1",
71
+ "transition-all duration-200",
72
+ "group-hover:translate-y-0",
73
+ "group-hover:opacity-100",
74
+ "hover:bg-code-foreground/10",
75
+ "hover:text-code-foreground",
76
+ "focus-visible:translate-y-0",
77
+ "focus-visible:opacity-100",
78
+ )}
79
+ >
80
+ <CopyMorphIcon copied={isCopied} />
81
+ </Button>
82
+
83
+ <div
84
+ className={cn(
85
+ "[&_pre]:m-0",
86
+ "[&_pre]:overflow-x-auto",
87
+ "[&_pre]:bg-transparent!",
88
+ "[&_pre]:px-5",
89
+ "[&_pre]:py-4",
90
+ "[&_pre]:font-mono",
91
+ "[&_pre]:leading-relaxed",
92
+
93
+ "[&_code]:bg-transparent!",
94
+ "[&_code]:p-0",
95
+ "[&_code]:font-inherit",
96
+
97
+ "[&_.line]:min-h-6",
98
+
99
+ "[&_pre::-webkit-scrollbar]:h-2",
100
+ "[&_pre::-webkit-scrollbar-track]:bg-transparent",
101
+ "[&_pre::-webkit-scrollbar-thumb]:rounded-full",
102
+ "[&_pre::-webkit-scrollbar-thumb]:bg-code-foreground/10",
103
+ "[&_pre::-webkit-scrollbar-thumb:hover]:bg-code-foreground/20",
104
+ )}
105
+ dangerouslySetInnerHTML={{
106
+ __html: html,
107
+ }}
108
+ />
109
+ </div>
110
+ );
111
+ }
@@ -0,0 +1,113 @@
1
+ "use client";
2
+
3
+ import { motion, useReducedMotion } from "motion/react";
4
+ import type { ReactNode } from "react";
5
+ import { CodeBlock } from "@/components/content/code-block";
6
+ import { CommandBlock } from "@/components/shared/command-block";
7
+ import { ScrollFade } from "@/components/shared/scroll-fade";
8
+ import { colReveal } from "@/lib/motions";
9
+
10
+ export function DocsCol({ children }: { children: ReactNode }) {
11
+ const reduced = useReducedMotion() ?? false;
12
+ return (
13
+ <motion.div
14
+ className="mx-auto w-full"
15
+ initial={reduced ? false : "hidden"}
16
+ whileInView="show"
17
+ viewport={{ once: true, margin: "0px 0px -64px 0px" }}
18
+ variants={colReveal}
19
+ >
20
+ {children}
21
+ </motion.div>
22
+ );
23
+ }
24
+
25
+ export function DocsFrame({ children }: { children: ReactNode }) {
26
+ return (
27
+ <div className="relative min-h-screen overflow-x-clip pb-24">
28
+ {/* Top scroll fade */}
29
+ <ScrollFade />
30
+
31
+ {/* Shiki code block styles */}
32
+ <style>{`.docs-code .shiki{margin:0;padding:16px 18px;overflow-x:auto;line-height:1.65;background:transparent !important;font-size:var(--typeset-size)}.docs-code .shiki code{background:transparent;padding:0}`}</style>
33
+
34
+ <div className="w-full">
35
+ <div className="relative mx-auto w-full">
36
+ <main className="typeset typeset-docs mx-auto max-w-(--layout-content-width) w-full">
37
+ {children}
38
+ </main>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ );
43
+ }
44
+
45
+ import { TextEffect } from "@/components/motion-primitives/text-effect";
46
+
47
+ export function DocsHeader({
48
+ title,
49
+ date,
50
+ children,
51
+ }: {
52
+ title: string;
53
+ date: string;
54
+ children?: ReactNode;
55
+ }) {
56
+ return (
57
+ <section id="docs-header" className="scroll-mt-24">
58
+ <DocsCol>
59
+ <h1 className="tracking-tight mb-2">
60
+ <TextEffect preset="fade-in-blur" per="word">
61
+ {title}
62
+ </TextEffect>
63
+ </h1>
64
+ <p className="text-muted-foreground/60 mb-8 mt-0">{date}</p>
65
+ {children}
66
+ </DocsCol>
67
+ </section>
68
+ );
69
+ }
70
+
71
+ export function DocsOverview({ children }: { children: ReactNode }) {
72
+ return (
73
+ <section id="overview" className="scroll-mt-24">
74
+ <DocsCol>{children}</DocsCol>
75
+ </section>
76
+ );
77
+ }
78
+
79
+ export function DocsSection({
80
+ id,
81
+ title,
82
+ children,
83
+ }: {
84
+ id: string;
85
+ title?: string;
86
+ children: ReactNode;
87
+ }) {
88
+ return (
89
+ <section id={id} className="scroll-mt-24 mt-12">
90
+ <DocsCol>
91
+ {title && <h2>{title}</h2>}
92
+ {children}
93
+ </DocsCol>
94
+ </section>
95
+ );
96
+ }
97
+
98
+ export function DocsInstallation({
99
+ install,
100
+ }: {
101
+ install: { id: string; command: string }[];
102
+ }) {
103
+ return (
104
+ <DocsSection id="installation" title="Installation">
105
+ <p>Install the package using your preferred package manager:</p>
106
+ <CommandBlock items={install} />
107
+ </DocsSection>
108
+ );
109
+ }
110
+
111
+ export function DocsCode({ code, html }: { code: string; html: string }) {
112
+ return <CodeBlock code={code} html={html} />;
113
+ }
@@ -0,0 +1,192 @@
1
+ "use client";
2
+
3
+ import { ArrowLeftIcon } from "@radix-ui/react-icons";
4
+ import { motion, useMotionValueEvent, useScroll } from "motion/react";
5
+ import Link from "next/link";
6
+ import { usePathname } from "next/navigation";
7
+ import * as React from "react";
8
+ import { SlidingNumber } from "../motion-primitives/sliding-number";
9
+ import { ScrollFade } from "../shared/scroll-fade";
10
+
11
+ export type DocsLayoutProps = {
12
+ children: React.ReactNode;
13
+ links: {
14
+ group: string;
15
+ items: { title: string; url: string; isAnchor: boolean }[];
16
+ }[];
17
+ indexUrl?: string;
18
+ indexLabel?: string;
19
+ header?: React.ReactNode;
20
+ };
21
+
22
+ export function DocsLayout({
23
+ children,
24
+ links,
25
+ indexUrl = "/",
26
+ indexLabel = "Index",
27
+ header,
28
+ }: DocsLayoutProps) {
29
+ const pathname = usePathname();
30
+ const [activeHash, setActiveHash] = React.useState("");
31
+
32
+ const { scrollYProgress } = useScroll();
33
+ const [progress, setProgress] = React.useState(0);
34
+
35
+ useMotionValueEvent(scrollYProgress, "change", (latest) => {
36
+ setProgress(Math.round(latest * 100));
37
+ });
38
+
39
+ // Scrollspy to detect currently active section
40
+ React.useEffect(() => {
41
+ // Only extract sections for the current page path
42
+ const currentPathLinks = links.flatMap((g) =>
43
+ g.items.filter((i) => i.isAnchor && i.url.startsWith(pathname)),
44
+ );
45
+ const sections = currentPathLinks.map((i) => i.url.split("#")[1]);
46
+ if (!sections.length) {
47
+ setActiveHash("");
48
+ return;
49
+ }
50
+
51
+ const observerOptions = {
52
+ root: null,
53
+ rootMargin: "-20% 0px -60% 0px", // Trigger when the section occupies the main focal area
54
+ threshold: 0,
55
+ };
56
+
57
+ const observer = new IntersectionObserver((entries) => {
58
+ for (const entry of entries) {
59
+ if (entry.isIntersecting) {
60
+ setActiveHash(`#${entry.target.id}`);
61
+ }
62
+ }
63
+ }, observerOptions);
64
+
65
+ for (const id of sections) {
66
+ if (id) {
67
+ const el = document.getElementById(id);
68
+ if (el) observer.observe(el);
69
+ }
70
+ }
71
+
72
+ // Capture initial hash if present
73
+ if (typeof window !== "undefined" && window.location.hash) {
74
+ setActiveHash(window.location.hash);
75
+ } else {
76
+ // default to first
77
+ setActiveHash(`#${sections[0]}`);
78
+ }
79
+
80
+ return () => {
81
+ observer.disconnect();
82
+ };
83
+ }, [links, pathname]);
84
+
85
+ const handleAnchorClick = (
86
+ e: React.MouseEvent<HTMLAnchorElement>,
87
+ url: string,
88
+ ) => {
89
+ if (!url.includes("#")) return;
90
+
91
+ const hash = url.split("#")[1];
92
+ const element = document.getElementById(hash);
93
+
94
+ if (element && url.startsWith(pathname)) {
95
+ e.preventDefault();
96
+ element.scrollIntoView({ behavior: "smooth" });
97
+ window.history.pushState(null, "", `#${hash}`);
98
+ setActiveHash(`#${hash}`);
99
+ }
100
+ };
101
+
102
+ const isLinkActive = (url: string, isAnchor: boolean) => {
103
+ if (isAnchor) {
104
+ if (!url.startsWith(pathname)) return false;
105
+ const hash = url.split("#")[1];
106
+ if (activeHash) {
107
+ return activeHash === `#${hash}`;
108
+ }
109
+ return false;
110
+ }
111
+ return pathname === url;
112
+ };
113
+
114
+ return (
115
+ <>
116
+ <ScrollFade />
117
+ {header}
118
+ <div className="mx-auto w-full max-w-(--layout-max-width) px-6 py-12 md:py-24 flex flex-col md:flex-row gap-10 md:gap-16">
119
+ {/* Sticky Minimalist Sidebar */}
120
+ <aside className="w-full md:w-48 shrink-0 md:sticky md:top-24 md:self-start flex flex-col gap-6 md:gap-8">
121
+ <div className="flex items-center justify-between">
122
+ <Link
123
+ href={indexUrl}
124
+ className="text-sm text-muted-foreground hover:text-foreground flex items-center gap-1.5 transition-opacity duration-200 select-none opacity-60 hover:opacity-100"
125
+ >
126
+ <ArrowLeftIcon className="size-3.5" />
127
+ {indexLabel}
128
+ </Link>
129
+
130
+ <div className="text-xs font-mono text-muted-foreground opacity-50 flex items-center gap-0.5">
131
+ <SlidingNumber value={progress} />%
132
+ </div>
133
+ </div>
134
+ <div className="flex flex-row flex-wrap gap-x-6 gap-y-2 md:flex-col md:gap-y-4">
135
+ {links.map((group) => (
136
+ <div key={group.group} className="flex flex-col gap-1.5 md:gap-2">
137
+ <nav className="flex flex-row flex-wrap gap-x-1 gap-y-1 md:flex-col md:gap-y-1">
138
+ {group.items.map((link) => {
139
+ const active = isLinkActive(link.url, link.isAnchor);
140
+ return (
141
+ <Link
142
+ key={link.title}
143
+ href={link.url}
144
+ onClick={(e) => handleAnchorClick(e, link.url)}
145
+ className={`relative text-sm transition-colors duration-200 px-2 py-1.5 -ml-2 rounded-md ${
146
+ active
147
+ ? "text-foreground font-medium"
148
+ : "text-muted-foreground hover:text-foreground"
149
+ }`}
150
+ >
151
+ {active && (
152
+ <motion.div
153
+ layoutId="activeDocsLinkBg"
154
+ className="absolute inset-0 bg-secondary/50 rounded-md"
155
+ transition={{
156
+ type: "spring",
157
+ duration: 0.3,
158
+ bounce: 0,
159
+ }}
160
+ />
161
+ )}
162
+ <span className="relative z-10">{link.title}</span>
163
+ </Link>
164
+ );
165
+ })}
166
+ </nav>
167
+ </div>
168
+ ))}
169
+ </div>
170
+ </aside>
171
+
172
+ {/* Main Content */}
173
+ <main className="flex-1 min-w-0">{children}</main>
174
+ </div>
175
+ </>
176
+ );
177
+ }
178
+
179
+ export function DocsNavbar({
180
+ logo,
181
+ right,
182
+ }: {
183
+ logo?: React.ReactNode;
184
+ right?: React.ReactNode;
185
+ }) {
186
+ return (
187
+ <header className="sticky top-4 z-(--z-header) mx-auto flex w-full max-w-(--layout-max-width) items-center justify-between px-(--layout-padding) py-3">
188
+ {logo}
189
+ {right}
190
+ </header>
191
+ );
192
+ }