@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
@@ -0,0 +1,152 @@
1
+ "use client";
2
+
3
+ import { GitHubLogoIcon } from "@radix-ui/react-icons";
4
+ import { motion } from "motion/react";
5
+ import Link from "next/link";
6
+ import { usePathname } from "next/navigation";
7
+ import { ThemeSwitcher } from "@/components/layout/theme-switcher";
8
+ import { Button } from "@/components/ui/button";
9
+ import { cn } from "@/lib/utils";
10
+ import { tap as tapSound, turn as turnSound } from "@/lib/utils/audio";
11
+ import { Badge } from "../ui/badge";
12
+ import { BrandLogo } from "../ui/brand-logo";
13
+ import {
14
+ HoverCard,
15
+ HoverCardContent,
16
+ HoverCardTrigger,
17
+ } from "../ui/hover-card";
18
+ import {
19
+ Tooltip,
20
+ TooltipContent,
21
+ TooltipProvider,
22
+ TooltipTrigger,
23
+ } from "../ui/tooltip";
24
+
25
+ export type SiteHeaderProps = {
26
+ navItems?: { label: string; href: string }[];
27
+ logo?: React.ReactNode;
28
+ version?: string;
29
+ githubUrl?: string;
30
+ right?: React.ReactNode;
31
+ };
32
+
33
+ export function SiteHeader({
34
+ navItems = [],
35
+ logo = <BrandLogo />,
36
+ version = "v0.1",
37
+ githubUrl = "https://github.com/your-username/your-repo",
38
+ right,
39
+ }: SiteHeaderProps) {
40
+ const pathname = usePathname();
41
+ const isActive = (href: string) =>
42
+ href === "/" ? pathname === "/" : (pathname?.startsWith(href) ?? false);
43
+
44
+ return (
45
+ <header className="sticky top-4 z-(--z-header) flex w-full items-center justify-between rounded-xl">
46
+ {/* Brand mark — doubles as the home link. */}
47
+ <Link
48
+ href="/"
49
+ aria-label="Home"
50
+ className="flex items-center text-foreground transition-opacity hover:opacity-80"
51
+ >
52
+ {logo}
53
+ </Link>
54
+
55
+ {/* Nav links, centered. */}
56
+ <nav className="absolute left-1/2 flex -translate-x-1/2 items-center gap-4 md:gap-5">
57
+ {navItems.map((item) => {
58
+ const active = isActive(item.href);
59
+ return (
60
+ <Link
61
+ key={item.href}
62
+ href={item.href}
63
+ aria-current={active ? "page" : undefined}
64
+ onClick={() => {
65
+ if (!active) turnSound(item.href === "/" ? "back" : "forward");
66
+ }}
67
+ className={cn(
68
+ "relative font-medium text-sm leading-5 transition-colors px-3 py-1.5 rounded-full",
69
+ active
70
+ ? "text-foreground"
71
+ : "text-muted-foreground hover:text-foreground",
72
+ )}
73
+ >
74
+ {active && (
75
+ <motion.div
76
+ layoutId="activeHeaderNavBg"
77
+ className="absolute inset-0 bg-secondary/60 rounded-full"
78
+ transition={{ type: "spring", duration: 0.3, bounce: 0 }}
79
+ />
80
+ )}
81
+ <span className="relative z-10">{item.label}</span>
82
+ </Link>
83
+ );
84
+ })}
85
+ </nav>
86
+
87
+ <div className="flex items-center gap-1">
88
+ {version && (
89
+ <HoverCard>
90
+ <HoverCardTrigger
91
+ render={
92
+ <Badge
93
+ variant={"secondary"}
94
+ className="align-middle cursor-default hover:bg-secondary/80 transition-colors"
95
+ >
96
+ {version}
97
+ </Badge>
98
+ }
99
+ />
100
+ <HoverCardContent
101
+ side="bottom"
102
+ sideOffset={8}
103
+ align="center"
104
+ className="w-64 p-2"
105
+ >
106
+ <div className="space-y-1.5">
107
+ <h1 className=" font-medium">Release {version}</h1>
108
+ <p className="text-muted-foreground">
109
+ You are viewing the documentation for the latest release.
110
+ </p>
111
+ </div>
112
+ </HoverCardContent>
113
+ </HoverCard>
114
+ )}
115
+ {right}
116
+ {githubUrl && (
117
+ <TooltipProvider delay={200}>
118
+ <Tooltip>
119
+ <TooltipTrigger
120
+ render={
121
+ <Button
122
+ variant="ghost"
123
+ size="sm"
124
+ render={(props: React.ComponentProps<"a">) => (
125
+ <a
126
+ {...props}
127
+ href={githubUrl}
128
+ target="_blank"
129
+ rel="noopener noreferrer"
130
+ onClick={(e) => {
131
+ props.onClick?.(e);
132
+ tapSound();
133
+ }}
134
+ />
135
+ )}
136
+ >
137
+ <GitHubLogoIcon />
138
+ <span className="hidden sm:inline">GitHub</span>
139
+ </Button>
140
+ }
141
+ />
142
+ <TooltipContent side="bottom" sideOffset={6}>
143
+ View source on GitHub
144
+ </TooltipContent>
145
+ </Tooltip>
146
+ </TooltipProvider>
147
+ )}
148
+ <ThemeSwitcher />
149
+ </div>
150
+ </header>
151
+ );
152
+ }
@@ -0,0 +1,28 @@
1
+ import type * as React from "react";
2
+ import { ScrollFade } from "../shared/scroll-fade";
3
+
4
+ export type SiteLayoutProps = {
5
+ children: React.ReactNode;
6
+ header?: React.ReactNode;
7
+ footer?: React.ReactNode;
8
+ };
9
+
10
+ export function SiteLayout({ children, header, footer }: SiteLayoutProps) {
11
+ return (
12
+ <div className="relative flex flex-col items-center min-h-screen pb-24 overflow-x-clip">
13
+ {/* Top scroll fade */}
14
+ <ScrollFade />
15
+
16
+ <section className="w-full px-6 flex flex-col gap-3 pt-3">
17
+ {header}
18
+ {children}
19
+ </section>
20
+
21
+ {footer && (
22
+ <div className="flex flex-col gap-10 items-center text-center mt-32 px-4">
23
+ {footer}
24
+ </div>
25
+ )}
26
+ </div>
27
+ );
28
+ }
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import type * as React from "react";
4
+ import { DirectionProvider } from "../ui/direction";
5
+ import { AnchoredToastProvider, ToastProvider } from "../ui/toast";
6
+ import { ThemeProvider } from "./theme-provider";
7
+
8
+ export function SiteProvider({ children }: { children: React.ReactNode }) {
9
+ return (
10
+ <ThemeProvider
11
+ attribute="class"
12
+ defaultTheme="dark"
13
+ enableSystem
14
+ disableTransitionOnChange
15
+ >
16
+ <DirectionProvider direction="ltr">
17
+ <ToastProvider position="bottom-right">
18
+ <AnchoredToastProvider>{children}</AnchoredToastProvider>
19
+ </ToastProvider>
20
+ </DirectionProvider>
21
+ </ThemeProvider>
22
+ );
23
+ }
@@ -0,0 +1,20 @@
1
+ "use client";
2
+
3
+ import { motion, useReducedMotion } from "motion/react";
4
+ import type * as React from "react";
5
+
6
+ export function SiteTemplate({ children }: { children: React.ReactNode }) {
7
+ const reduced = useReducedMotion();
8
+
9
+ return (
10
+ <motion.div
11
+ initial={reduced ? false : { opacity: 0 }}
12
+ animate={{ opacity: 1 }}
13
+ transition={
14
+ reduced ? { duration: 0 } : { duration: 0.24, ease: [0.22, 1, 0.36, 1] }
15
+ }
16
+ >
17
+ {children}
18
+ </motion.div>
19
+ );
20
+ }
@@ -0,0 +1,10 @@
1
+ "use client";
2
+
3
+ import { ThemeProvider as NextThemesProvider } from "next-themes";
4
+ import type { ComponentProps } from "react";
5
+
6
+ export function ThemeProvider(
7
+ props: ComponentProps<typeof NextThemesProvider>,
8
+ ) {
9
+ return <NextThemesProvider {...props} />;
10
+ }
@@ -0,0 +1,42 @@
1
+ "use client";
2
+
3
+ import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
4
+ import { useTheme } from "next-themes";
5
+ import { useEffect, useState } from "react";
6
+ import { Button } from "@/components/ui/button";
7
+ import { MorphIcon } from "../motion-primitives/morph-icon";
8
+
9
+ export function ThemeSwitcher() {
10
+ const { theme, setTheme, systemTheme } = useTheme();
11
+ const [mounted, setMounted] = useState(false);
12
+
13
+ useEffect(() => {
14
+ setMounted(true);
15
+ }, []);
16
+
17
+ if (!mounted) {
18
+ return (
19
+ <Button variant="ghost" size="icon-sm" aria-label="Toggle theme">
20
+ <SunIcon aria-hidden="true" />
21
+ </Button>
22
+ );
23
+ }
24
+
25
+ const currentTheme = theme === "system" ? systemTheme : theme;
26
+
27
+ return (
28
+ <Button
29
+ variant="ghost"
30
+ size="icon-sm"
31
+ onClick={() => setTheme(currentTheme === "dark" ? "light" : "dark")}
32
+ aria-label="Toggle theme"
33
+ >
34
+ <MorphIcon
35
+ isActive={currentTheme === "dark"}
36
+ activeIcon={<MoonIcon aria-hidden="true" />}
37
+ inactiveIcon={<SunIcon aria-hidden="true" />}
38
+ variant="rotate"
39
+ />
40
+ </Button>
41
+ );
42
+ }
@@ -0,0 +1,97 @@
1
+ "use client";
2
+
3
+ import { AnimatePresence, motion } from "motion/react";
4
+ import type * as React from "react";
5
+
6
+ const CARD_DELAY_STEP = 0.067;
7
+
8
+ // Active permutation: TR BR / TL BL (indices in a 2×2 grid 0-3)
9
+ const ACTIVE_ORDER = [1, 3, 0, 2];
10
+ const DEFAULT_ORDER = [0, 1, 2, 3];
11
+
12
+ export interface CardsViewIconProps {
13
+ /** Whether the icon is in its active (animated) state. */
14
+ isActive: boolean;
15
+ /** Extra classes applied to the root element. */
16
+ className?: string;
17
+ }
18
+
19
+ export function CardsViewIcon({ isActive, className }: CardsViewIconProps) {
20
+ const order = isActive ? ACTIVE_ORDER : DEFAULT_ORDER;
21
+
22
+ return (
23
+ <motion.span
24
+ className={`inline-grid h-3 w-3 place-content-center grid-cols-2 gap-[2.5px] ${className ?? ""}`}
25
+ aria-hidden="true"
26
+ >
27
+ {order.map((id, i) => (
28
+ <motion.span
29
+ key={id}
30
+ layout
31
+ className="size-[4px] rounded-[1px] bg-current"
32
+ animate={isActive ? { opacity: [0.65, 1, 1] } : { opacity: 1 }}
33
+ transition={{
34
+ duration: 0.42,
35
+ delay: i === 0 ? 0 : i * CARD_DELAY_STEP,
36
+ ease: "easeOut",
37
+ layout: {
38
+ duration: 0.45,
39
+ delay: i === 0 ? 0 : i * CARD_DELAY_STEP,
40
+ ease: "easeInOut",
41
+ },
42
+ }}
43
+ />
44
+ ))}
45
+ </motion.span>
46
+ );
47
+ }
48
+
49
+ export interface MorphIconProps {
50
+ isActive: boolean;
51
+ activeIcon: React.ReactNode;
52
+ inactiveIcon: React.ReactNode;
53
+ className?: string;
54
+ variant?: "scale" | "rotate" | "slide";
55
+ }
56
+
57
+ export function MorphIcon({
58
+ isActive,
59
+ activeIcon,
60
+ inactiveIcon,
61
+ className,
62
+ variant = "rotate",
63
+ }: MorphIconProps) {
64
+ const variants = {
65
+ scale: {
66
+ initial: { opacity: 0, scale: 0.95 },
67
+ animate: { opacity: 1, scale: 1 },
68
+ exit: { opacity: 0, scale: 0.95 },
69
+ },
70
+ rotate: {
71
+ initial: { opacity: 0, rotate: -90, scale: 0.95 },
72
+ animate: { opacity: 1, rotate: 0, scale: 1 },
73
+ exit: { opacity: 0, rotate: 90, scale: 0.95 },
74
+ },
75
+ slide: {
76
+ initial: { opacity: 0, y: -10 },
77
+ animate: { opacity: 1, y: 0 },
78
+ exit: { opacity: 0, y: 10 },
79
+ },
80
+ };
81
+
82
+ return (
83
+ <AnimatePresence mode="popLayout" initial={false}>
84
+ <motion.span
85
+ key={isActive ? "active" : "inactive"}
86
+ initial="initial"
87
+ animate="animate"
88
+ exit="exit"
89
+ variants={variants[variant]}
90
+ transition={{ duration: 0.15, ease: [0.23, 1, 0.32, 1] }}
91
+ className={`inline-flex items-center justify-center ${className ?? ""}`}
92
+ >
93
+ {isActive ? activeIcon : inactiveIcon}
94
+ </motion.span>
95
+ </AnimatePresence>
96
+ );
97
+ }
@@ -0,0 +1,64 @@
1
+ "use client";
2
+ import { type HTMLMotionProps, motion } from "motion/react";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ export const GRADIENT_ANGLES = {
6
+ top: 0,
7
+ right: 90,
8
+ bottom: 180,
9
+ left: 270,
10
+ };
11
+
12
+ export type ProgressiveBlurProps = {
13
+ direction?: keyof typeof GRADIENT_ANGLES;
14
+ blurLayers?: number;
15
+ className?: string;
16
+ blurIntensity?: number;
17
+ } & HTMLMotionProps<"div">;
18
+
19
+ export function ProgressiveBlur({
20
+ direction = "bottom",
21
+ blurLayers = 8,
22
+ className,
23
+ blurIntensity = 0.25,
24
+ ...props
25
+ }: ProgressiveBlurProps) {
26
+ const layers = Math.max(blurLayers, 2);
27
+ const segmentSize = 1 / (blurLayers + 1);
28
+
29
+ return (
30
+ <div className={cn("relative", className)}>
31
+ {Array.from({ length: layers }).map((_, index) => {
32
+ const angle = GRADIENT_ANGLES[direction];
33
+ const gradientStops = [
34
+ index * segmentSize,
35
+ (index + 1) * segmentSize,
36
+ (index + 2) * segmentSize,
37
+ (index + 3) * segmentSize,
38
+ ].map(
39
+ (pos, posIndex) =>
40
+ `rgba(255, 255, 255, ${posIndex === 1 || posIndex === 2 ? 1 : 0}) ${pos * 100}%`,
41
+ );
42
+
43
+ const gradient = `linear-gradient(${angle}deg, ${gradientStops.join(
44
+ ", ",
45
+ )})`;
46
+
47
+ return (
48
+ <motion.div
49
+ // biome-ignore lint/suspicious/noArrayIndexKey: Layers are static
50
+ key={index}
51
+ className="pointer-events-none absolute inset-0 rounded-[inherit]"
52
+ style={{
53
+ maskImage: gradient,
54
+ WebkitMaskImage: gradient,
55
+ backdropFilter: `blur(${index * blurIntensity}px)`,
56
+ WebkitBackdropFilter: `blur(${index * blurIntensity}px)`,
57
+ }}
58
+ {...props}
59
+ />
60
+ );
61
+ })}
62
+ </div>
63
+ );
64
+ }
@@ -0,0 +1,131 @@
1
+ "use client";
2
+ import {
3
+ type MotionValue,
4
+ motion,
5
+ motionValue,
6
+ useSpring,
7
+ useTransform,
8
+ } from "motion/react";
9
+ import { useEffect, useId } from "react";
10
+ import useMeasure from "react-use-measure";
11
+
12
+ const TRANSITION = {
13
+ type: "spring" as const,
14
+ bounce: 0,
15
+ duration: 0.3,
16
+ };
17
+
18
+ function Digit({ value, place }: { value: number; place: number }) {
19
+ const valueRoundedToPlace = Math.floor(value / place) % 10;
20
+ const initial = motionValue(valueRoundedToPlace);
21
+ const animatedValue = useSpring(initial, TRANSITION);
22
+
23
+ useEffect(() => {
24
+ animatedValue.set(valueRoundedToPlace);
25
+ }, [animatedValue, valueRoundedToPlace]);
26
+
27
+ return (
28
+ <div className="relative inline-block w-[1ch] overflow-x-visible overflow-y-clip leading-none tabular-nums">
29
+ <div className="invisible">0</div>
30
+ {Array.from({ length: 10 }, (_, i) => (
31
+ // biome-ignore lint/suspicious/noArrayIndexKey: order is static
32
+ <SlidingDigitNumber key={`digit-${i}`} mv={animatedValue} number={i} />
33
+ ))}
34
+ </div>
35
+ );
36
+ }
37
+
38
+ function SlidingDigitNumber({
39
+ mv,
40
+ number,
41
+ }: {
42
+ mv: MotionValue<number>;
43
+ number: number;
44
+ }) {
45
+ const uniqueId = useId();
46
+ const [ref, bounds] = useMeasure();
47
+
48
+ const y = useTransform(mv, (latest) => {
49
+ if (!bounds.height) return 0;
50
+ const placeValue = latest % 10;
51
+ const offset = (10 + number - placeValue) % 10;
52
+ let memo = offset * bounds.height;
53
+
54
+ if (offset > 5) {
55
+ memo -= 10 * bounds.height;
56
+ }
57
+
58
+ return memo;
59
+ });
60
+
61
+ // don't render the animated number until we know the height
62
+ if (!bounds.height) {
63
+ return (
64
+ <span ref={ref} className="invisible absolute">
65
+ {number}
66
+ </span>
67
+ );
68
+ }
69
+
70
+ return (
71
+ <motion.span
72
+ style={{ y }}
73
+ layoutId={`${uniqueId}-${number}`}
74
+ className="absolute inset-0 flex items-center justify-center"
75
+ transition={TRANSITION}
76
+ ref={ref}
77
+ >
78
+ {number}
79
+ </motion.span>
80
+ );
81
+ }
82
+
83
+ type SlidingNumberProps = {
84
+ value: number;
85
+ padStart?: boolean;
86
+ decimalSeparator?: string;
87
+ };
88
+
89
+ export function SlidingNumber({
90
+ value,
91
+ padStart = false,
92
+ decimalSeparator = ".",
93
+ }: SlidingNumberProps) {
94
+ const absValue = Math.abs(value);
95
+ const [integerPart, decimalPart] = absValue.toString().split(".");
96
+ const integerValue = parseInt(integerPart, 10);
97
+ const paddedInteger =
98
+ padStart && integerValue < 10 ? `0${integerPart}` : integerPart;
99
+ const integerDigits = paddedInteger.split("");
100
+ const integerPlaces = integerDigits.map(
101
+ (_, i) => 10 ** (integerDigits.length - i - 1),
102
+ );
103
+
104
+ return (
105
+ <div className="flex items-center">
106
+ {value < 0 && "-"}
107
+ {integerDigits.map((_, index) => (
108
+ <Digit
109
+ key={`pos-${integerPlaces[index]}`}
110
+ value={integerValue}
111
+ place={integerPlaces[index]}
112
+ />
113
+ ))}
114
+ {decimalPart && (
115
+ <>
116
+ <span>{decimalSeparator}</span>
117
+ {decimalPart.split("").map((_, index) => {
118
+ const place = 10 ** (decimalPart.length - index - 1);
119
+ return (
120
+ <Digit
121
+ key={`decimal-${place}`}
122
+ value={parseInt(decimalPart, 10)}
123
+ place={place}
124
+ />
125
+ );
126
+ })}
127
+ </>
128
+ )}
129
+ </div>
130
+ );
131
+ }