@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,322 @@
1
+ "use client";
2
+
3
+ import { Toast } from "@base-ui/react/toast";
4
+ import {
5
+ CheckCircledIcon,
6
+ CrossCircledIcon,
7
+ ExclamationTriangleIcon,
8
+ InfoCircledIcon,
9
+ ReloadIcon,
10
+ } from "@radix-ui/react-icons";
11
+ import type React from "react";
12
+ import { buttonVariants } from "@/components/ui/button";
13
+ import { cn } from "@/lib/utils";
14
+
15
+ const TOAST_ICONS = {
16
+ error: CrossCircledIcon,
17
+ info: InfoCircledIcon,
18
+ loading: ReloadIcon,
19
+ success: CheckCircledIcon,
20
+ warning: ExclamationTriangleIcon,
21
+ } as const;
22
+
23
+ type SwipeDirection = "up" | "down" | "left" | "right";
24
+
25
+ type ToastData = {
26
+ rootProps?: Omit<
27
+ React.ComponentProps<typeof Toast.Root>,
28
+ "children" | "className" | "swipeDirection" | "toast"
29
+ >;
30
+ tooltipStyle?: boolean;
31
+ };
32
+
33
+ function getSwipeDirection(position: ToastPosition): SwipeDirection[] {
34
+ const verticalDirection: SwipeDirection = position.startsWith("top")
35
+ ? "up"
36
+ : "down";
37
+
38
+ if (position.includes("center")) {
39
+ return [verticalDirection];
40
+ }
41
+
42
+ if (position.includes("left")) {
43
+ return ["left", verticalDirection];
44
+ }
45
+
46
+ return ["right", verticalDirection];
47
+ }
48
+
49
+ function upsertReplayClassName(toast: {
50
+ type?: string;
51
+ updateKey?: number;
52
+ }): string | undefined {
53
+ const k = toast.updateKey ?? 0;
54
+ if (k <= 0) return undefined;
55
+ const isEven = k % 2 === 0;
56
+ if (toast.type === "error") {
57
+ return isEven ? "animate-toast-error-even" : "animate-toast-error-odd";
58
+ }
59
+ return isEven ? "animate-toast-success-even" : "animate-toast-success-odd";
60
+ }
61
+
62
+ function Toasts({
63
+ position,
64
+ portalProps,
65
+ }: {
66
+ position: ToastPosition;
67
+ portalProps?: React.ComponentProps<typeof Toast.Portal>;
68
+ }): React.ReactElement {
69
+ const { toasts } = Toast.useToastManager();
70
+ const swipeDirection = getSwipeDirection(position);
71
+
72
+ return (
73
+ <Toast.Portal data-slot="toast-portal" {...portalProps}>
74
+ <Toast.Viewport
75
+ className={cn(
76
+ "fixed z-60 mx-auto flex w-[calc(100%-var(--toast-inset)*2)] max-w-90 [--toast-inset:--spacing(4)] sm:[--toast-inset:--spacing(8)]",
77
+ // Vertical positioning
78
+ "data-[position*=top]:top-(--toast-inset)",
79
+ "data-[position*=bottom]:bottom-(--toast-inset)",
80
+ // Horizontal positioning
81
+ "data-[position*=left]:left-(--toast-inset)",
82
+ "data-[position*=right]:right-(--toast-inset)",
83
+ "data-[position*=center]:left-1/2 data-[position*=center]:-translate-x-1/2",
84
+ )}
85
+ data-position={position}
86
+ data-slot="toast-viewport"
87
+ >
88
+ {toasts.map((toast) => {
89
+ const Icon = toast.type
90
+ ? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS]
91
+ : null;
92
+ const toastData = toast.data as ToastData | undefined;
93
+
94
+ return (
95
+ <Toast.Root
96
+ key={toast.id}
97
+ className={cn(
98
+ "absolute z-[calc(9999-var(--toast-index))] h-(--toast-calc-height) w-full select-none rounded-lg border bg-popover text-popover-foreground [transition:transform_.5s_cubic-bezier(.22,1,.36,1),opacity_.5s,height_.15s,background-color_.5s]",
99
+ // Base positioning using data-position
100
+ "data-[position*=right]:right-0 data-[position*=right]:left-auto",
101
+ "data-[position*=left]:right-auto data-[position*=left]:left-0",
102
+ "data-[position*=center]:right-0 data-[position*=center]:left-0",
103
+ "data-[position*=top]:top-0 data-[position*=top]:bottom-auto data-[position*=top]:origin-[50%_calc(50%-50%*min(var(--toast-index,0),1))]",
104
+ "data-[position*=bottom]:top-auto data-[position*=bottom]:bottom-0 data-[position*=bottom]:origin-[50%_calc(50%+50%*min(var(--toast-index,0),1))]",
105
+ // Gap fill for hover
106
+ "after:absolute after:left-0 after:h-[calc(var(--toast-gap)+1px)] after:w-full",
107
+ "data-[position*=top]:after:top-full",
108
+ "data-[position*=bottom]:after:bottom-full",
109
+ // Define some variables
110
+ "[--toast-calc-height:var(--toast-frontmost-height,var(--toast-height))] [--toast-gap:--spacing(3)] [--toast-peek:--spacing(3)] [--toast-scale:calc(max(0,1-(var(--toast-index)*.1)))] [--toast-shrink:calc(1-var(--toast-scale))]",
111
+ // Define offset-y variable
112
+ "data-[position*=top]:[--toast-calc-offset-y:calc(var(--toast-offset-y)+var(--toast-index)*var(--toast-gap)+var(--toast-swipe-movement-y))]",
113
+ "data-[position*=bottom]:[--toast-calc-offset-y:calc(var(--toast-offset-y)*-1+var(--toast-index)*var(--toast-gap)*-1+var(--toast-swipe-movement-y))]",
114
+ // Default state transform
115
+ "data-[position*=top]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--toast-peek))+(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]",
116
+ "data-[position*=bottom]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--toast-peek))-(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]",
117
+ // Limited state
118
+ "data-limited:opacity-0",
119
+ // Expanded state
120
+ "data-expanded:h-(--toast-height)",
121
+ "data-position:data-expanded:transform-[translateX(var(--toast-swipe-movement-x))_translateY(var(--toast-calc-offset-y))]",
122
+ // Starting and ending animations
123
+ "data-[position*=top]:data-starting-style:transform-[translateY(calc(-100%-var(--toast-inset)))]",
124
+ "data-[position*=bottom]:data-starting-style:transform-[translateY(calc(100%+var(--toast-inset)))]",
125
+ "data-ending-style:opacity-0",
126
+ // Ending animations (direction-aware)
127
+ "data-ending-style:not-data-limited:not-data-swipe-direction:transform-[translateY(calc(100%+var(--toast-inset)))]",
128
+ "data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]",
129
+ "data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]",
130
+ "data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]",
131
+ "data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]",
132
+ // Ending animations (expanded)
133
+ "data-expanded:data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]",
134
+ "data-expanded:data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]",
135
+ "data-expanded:data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]",
136
+ "data-expanded:data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]",
137
+ upsertReplayClassName(toast),
138
+ )}
139
+ {...toastData?.rootProps}
140
+ data-position={position}
141
+ swipeDirection={swipeDirection}
142
+ toast={toast}
143
+ >
144
+ <Toast.Content className="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm transition-opacity duration-250 data-behind:not-data-expanded:pointer-events-none data-behind:opacity-0 data-expanded:opacity-100">
145
+ <div className="flex gap-2">
146
+ {Icon && (
147
+ <div
148
+ className="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
149
+ data-slot="toast-icon"
150
+ >
151
+ <Icon className="in-data-[type=loading]:animate-spin in-data-[type=error]:text-destructive in-data-[type=info]:text-info in-data-[type=success]:text-success in-data-[type=warning]:text-warning in-data-[type=loading]:opacity-80" />
152
+ </div>
153
+ )}
154
+
155
+ <div className="flex flex-col gap-0.5">
156
+ <Toast.Title
157
+ className="font-medium"
158
+ data-slot="toast-title"
159
+ />
160
+ <Toast.Description
161
+ className="text-muted-foreground"
162
+ data-slot="toast-description"
163
+ />
164
+ </div>
165
+ </div>
166
+ {toast.actionProps && (
167
+ <Toast.Action
168
+ className={buttonVariants({ size: "xs" })}
169
+ data-slot="toast-action"
170
+ >
171
+ {toast.actionProps.children}
172
+ </Toast.Action>
173
+ )}
174
+ </Toast.Content>
175
+ </Toast.Root>
176
+ );
177
+ })}
178
+ </Toast.Viewport>
179
+ </Toast.Portal>
180
+ );
181
+ }
182
+
183
+ function AnchoredToasts({
184
+ portalProps,
185
+ }: {
186
+ portalProps?: React.ComponentProps<typeof Toast.Portal>;
187
+ }): React.ReactElement {
188
+ const { toasts } = Toast.useToastManager();
189
+
190
+ return (
191
+ <Toast.Portal data-slot="toast-portal-anchored" {...portalProps}>
192
+ <Toast.Viewport
193
+ className="outline-none"
194
+ data-slot="toast-viewport-anchored"
195
+ >
196
+ {toasts.map((toast) => {
197
+ const Icon = toast.type
198
+ ? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS]
199
+ : null;
200
+ const toastData = toast.data as ToastData | undefined;
201
+ const tooltipStyle = toastData?.tooltipStyle ?? false;
202
+ const positionerProps = toast.positionerProps;
203
+
204
+ if (!positionerProps?.anchor) {
205
+ return null;
206
+ }
207
+
208
+ return (
209
+ <Toast.Positioner
210
+ key={toast.id}
211
+ className="z-50 max-w-[min(--spacing(64),var(--available-width))]"
212
+ data-slot="toast-positioner"
213
+ sideOffset={positionerProps.sideOffset ?? 4}
214
+ toast={toast}
215
+ >
216
+ <Toast.Root
217
+ className={cn(
218
+ "relative text-balance border bg-popover text-popover-foreground text-xs transition-[scale,opacity] data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0",
219
+ tooltipStyle ? "rounded-md" : "rounded-lg",
220
+ upsertReplayClassName(toast),
221
+ )}
222
+ {...toastData?.rootProps}
223
+ data-slot="toast-popup"
224
+ toast={toast}
225
+ >
226
+ {tooltipStyle ? (
227
+ <Toast.Content className="pointer-events-auto px-2 py-1">
228
+ <Toast.Title data-slot="toast-title" />
229
+ </Toast.Content>
230
+ ) : (
231
+ <Toast.Content className="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm">
232
+ <div className="flex gap-2">
233
+ {Icon && (
234
+ <div
235
+ className="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
236
+ data-slot="toast-icon"
237
+ >
238
+ <Icon className="in-data-[type=loading]:animate-spin in-data-[type=error]:text-destructive in-data-[type=info]:text-info in-data-[type=success]:text-success in-data-[type=warning]:text-warning in-data-[type=loading]:opacity-80" />
239
+ </div>
240
+ )}
241
+
242
+ <div className="flex flex-col gap-0.5">
243
+ <Toast.Title
244
+ className="font-medium"
245
+ data-slot="toast-title"
246
+ />
247
+ <Toast.Description
248
+ className="text-muted-foreground"
249
+ data-slot="toast-description"
250
+ />
251
+ </div>
252
+ </div>
253
+ {toast.actionProps && (
254
+ <Toast.Action
255
+ className={buttonVariants({ size: "xs" })}
256
+ data-slot="toast-action"
257
+ >
258
+ {toast.actionProps.children}
259
+ </Toast.Action>
260
+ )}
261
+ </Toast.Content>
262
+ )}
263
+ </Toast.Root>
264
+ </Toast.Positioner>
265
+ );
266
+ })}
267
+ </Toast.Viewport>
268
+ </Toast.Portal>
269
+ );
270
+ }
271
+
272
+ export const toastManager: ReturnType<typeof Toast.createToastManager> =
273
+ Toast.createToastManager();
274
+
275
+ export const anchoredToastManager: ReturnType<typeof Toast.createToastManager> =
276
+ Toast.createToastManager();
277
+
278
+ export type ToastPosition =
279
+ | "top-left"
280
+ | "top-center"
281
+ | "top-right"
282
+ | "bottom-left"
283
+ | "bottom-center"
284
+ | "bottom-right";
285
+
286
+ export interface ToastProviderProps extends Toast.Provider.Props {
287
+ position?: ToastPosition;
288
+ portalProps?: React.ComponentProps<typeof Toast.Portal>;
289
+ }
290
+
291
+ export function ToastProvider({
292
+ children,
293
+ position = "bottom-right",
294
+ portalProps,
295
+ ...props
296
+ }: ToastProviderProps): React.ReactElement {
297
+ return (
298
+ <Toast.Provider toastManager={toastManager} {...props}>
299
+ {children}
300
+ <Toasts portalProps={portalProps} position={position} />
301
+ </Toast.Provider>
302
+ );
303
+ }
304
+
305
+ export interface AnchoredToastProviderProps extends Toast.Provider.Props {
306
+ portalProps?: React.ComponentProps<typeof Toast.Portal>;
307
+ }
308
+
309
+ export function AnchoredToastProvider({
310
+ children,
311
+ portalProps,
312
+ ...props
313
+ }: AnchoredToastProviderProps): React.ReactElement {
314
+ return (
315
+ <Toast.Provider toastManager={anchoredToastManager} {...props}>
316
+ {children}
317
+ <AnchoredToasts portalProps={portalProps} />
318
+ </Toast.Provider>
319
+ );
320
+ }
321
+
322
+ export { Toast as ToastPrimitive };
@@ -0,0 +1,66 @@
1
+ "use client";
2
+
3
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ function TooltipProvider({
8
+ delay = 0,
9
+ ...props
10
+ }: TooltipPrimitive.Provider.Props) {
11
+ return (
12
+ <TooltipPrimitive.Provider
13
+ data-slot="tooltip-provider"
14
+ delay={delay}
15
+ {...props}
16
+ />
17
+ );
18
+ }
19
+
20
+ function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
21
+ return <TooltipPrimitive.Root data-slot="tooltip" {...props} />;
22
+ }
23
+
24
+ function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
25
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
26
+ }
27
+
28
+ function TooltipContent({
29
+ className,
30
+ side = "top",
31
+ sideOffset = 4,
32
+ align = "center",
33
+ alignOffset = 0,
34
+ children,
35
+ ...props
36
+ }: TooltipPrimitive.Popup.Props &
37
+ Pick<
38
+ TooltipPrimitive.Positioner.Props,
39
+ "align" | "alignOffset" | "side" | "sideOffset"
40
+ >) {
41
+ return (
42
+ <TooltipPrimitive.Portal>
43
+ <TooltipPrimitive.Positioner
44
+ align={align}
45
+ alignOffset={alignOffset}
46
+ side={side}
47
+ sideOffset={sideOffset}
48
+ className="isolate z-50"
49
+ >
50
+ <TooltipPrimitive.Popup
51
+ data-slot="tooltip-content"
52
+ className={cn(
53
+ "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 duration-150 ease-out data-instant:duration-0 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
54
+ className,
55
+ )}
56
+ {...props}
57
+ >
58
+ {children}
59
+ <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
60
+ </TooltipPrimitive.Popup>
61
+ </TooltipPrimitive.Positioner>
62
+ </TooltipPrimitive.Portal>
63
+ );
64
+ }
65
+
66
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
@@ -0,0 +1,54 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+
3
+ export interface UseCopyToClipboardOptions {
4
+ timeout?: number;
5
+ onCopy?: () => void;
6
+ }
7
+
8
+ export function useCopyToClipboard(options: UseCopyToClipboardOptions = {}) {
9
+ const { timeout = 2000, onCopy } = options;
10
+ const [isCopied, setIsCopied] = useState(false);
11
+ const timeoutRef = useRef<number | null>(null);
12
+
13
+ const copyToClipboard = useCallback(
14
+ async (value: string) => {
15
+ if (!value || typeof window === "undefined" || !navigator.clipboard) {
16
+ return false;
17
+ }
18
+
19
+ try {
20
+ if (timeoutRef.current) {
21
+ window.clearTimeout(timeoutRef.current);
22
+ }
23
+
24
+ await navigator.clipboard.writeText(value);
25
+ setIsCopied(true);
26
+ onCopy?.();
27
+
28
+ if (timeout > 0) {
29
+ timeoutRef.current = window.setTimeout(() => {
30
+ setIsCopied(false);
31
+ }, timeout);
32
+ }
33
+ return true;
34
+ } catch (error) {
35
+ console.error("Failed to copy to clipboard", error);
36
+ return false;
37
+ }
38
+ },
39
+ [timeout, onCopy],
40
+ );
41
+
42
+ useEffect(() => {
43
+ return () => {
44
+ if (timeoutRef.current) {
45
+ window.clearTimeout(timeoutRef.current);
46
+ }
47
+ };
48
+ }, []);
49
+
50
+ return {
51
+ copyToClipboard,
52
+ isCopied,
53
+ };
54
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ export * from "./components/content/changelog-content";
4
+ export * from "./components/content/code-block";
5
+ export * from "./components/content/docs-content";
6
+ export * from "./components/layout/docs-layout";
7
+ export * from "./components/layout/site-header";
8
+ export * from "./components/layout/site-layout";
9
+ export * from "./components/layout/site-provider";
10
+ export * from "./components/layout/site-template";
11
+ export * from "./components/layout/theme-provider";
12
+ export * from "./components/layout/theme-switcher";
13
+ export * from "./components/motion-primitives/sliding-number";
14
+ export * from "./components/motion-primitives/text-effect";
15
+ export * from "./components/motion-primitives/text-morph";
16
+ export * from "./components/shared/command-block";
17
+ export * from "./components/shared/scroll-fade";
18
+ export * from "./components/ui/badge";
19
+ export * from "./components/ui/brand-logo";
20
+ export * from "./components/ui/button";
21
+ export * from "./components/ui/button-group";
22
+ export * from "./components/ui/card";
23
+ export * from "./components/ui/direction";
24
+ export * from "./components/ui/hover-card";
25
+ export * from "./components/ui/item";
26
+ export * from "./components/ui/separator";
27
+ export * from "./components/ui/toast";
28
+ export * from "./components/ui/tooltip";
29
+
30
+ export * from "./lib/motions";
@@ -0,0 +1,63 @@
1
+ export type ChangeKind = "new" | "improved" | "fixed";
2
+
3
+ export const NAV = [
4
+ { href: "/", label: "Home" },
5
+ { href: "/docs", label: "Docs" },
6
+ { href: "/changelog", label: "Changelog" },
7
+ ] as const;
8
+
9
+ export const CHANGELOG = [
10
+ {
11
+ version: "1.1.0",
12
+ title: "The Components Update",
13
+ date: "2024-03-15",
14
+ summary:
15
+ "Introduced a new suite of reusable components to accelerate your workflow.",
16
+ changes: [
17
+ {
18
+ kind: "new" as ChangeKind,
19
+ text: "Added generic button component with variants.",
20
+ },
21
+ {
22
+ kind: "new" as ChangeKind,
23
+ text: "Added customizable dialog and modal components.",
24
+ },
25
+ {
26
+ kind: "improved" as ChangeKind,
27
+ text: "Enhanced accessibility across all interactive elements.",
28
+ },
29
+ ],
30
+ },
31
+ {
32
+ version: "1.0.1",
33
+ title: "Bug Fixes",
34
+ date: "2024-01-10",
35
+ summary: "Minor fixes and performance improvements.",
36
+ changes: [
37
+ {
38
+ kind: "fixed" as ChangeKind,
39
+ text: "Fixed hydration error on initial load.",
40
+ },
41
+ {
42
+ kind: "improved" as ChangeKind,
43
+ text: "Optimized font loading for better Core Web Vitals.",
44
+ },
45
+ ],
46
+ },
47
+ {
48
+ version: "1.0.0",
49
+ title: "Initial Release",
50
+ date: "2024-01-01",
51
+ summary: "First version of the project.",
52
+ changes: [
53
+ {
54
+ kind: "new" as ChangeKind,
55
+ text: "Added initial template architecture.",
56
+ },
57
+ {
58
+ kind: "new" as ChangeKind,
59
+ text: "Established monochromatic Liquid Glass design tokens.",
60
+ },
61
+ ],
62
+ },
63
+ ];
@@ -0,0 +1,21 @@
1
+ export const EASE_OUT = [0.22, 1, 0.36, 1] as const;
2
+ export const REVEAL_DURATION = 0.28;
3
+ export const HERO_STAGGER = 0.06;
4
+
5
+ export const heroChild = {
6
+ hidden: { opacity: 0, y: 12 },
7
+ show: {
8
+ opacity: 1,
9
+ y: 0,
10
+ transition: { duration: REVEAL_DURATION, ease: EASE_OUT },
11
+ },
12
+ };
13
+
14
+ export const colReveal = {
15
+ hidden: { opacity: 0, y: 12 },
16
+ show: {
17
+ opacity: 1,
18
+ y: 0,
19
+ transition: { duration: REVEAL_DURATION, ease: EASE_OUT },
20
+ },
21
+ };
@@ -0,0 +1,56 @@
1
+ "use client";
2
+
3
+ function playBeep(
4
+ frequency: number,
5
+ duration: number,
6
+ type: OscillatorType = "sine",
7
+ ) {
8
+ if (
9
+ typeof window === "undefined" ||
10
+ // biome-ignore lint/suspicious/noExplicitAny: window may not have webkitAudioContext
11
+ (!window.AudioContext && !(window as any).webkitAudioContext)
12
+ )
13
+ return;
14
+ try {
15
+ const AudioContext =
16
+ // biome-ignore lint/suspicious/noExplicitAny: window may not have webkitAudioContext
17
+ window.AudioContext || (window as any).webkitAudioContext;
18
+ const ctx = new AudioContext();
19
+ const osc = ctx.createOscillator();
20
+ const gain = ctx.createGain();
21
+
22
+ osc.type = type;
23
+ osc.frequency.setValueAtTime(frequency, ctx.currentTime);
24
+
25
+ gain.gain.setValueAtTime(0.1, ctx.currentTime);
26
+ gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);
27
+
28
+ osc.connect(gain);
29
+ gain.connect(ctx.destination);
30
+
31
+ osc.start();
32
+ osc.stop(ctx.currentTime + duration);
33
+ } catch (_e) {
34
+ // Ignore audio errors
35
+ }
36
+ }
37
+
38
+ export function tap() {
39
+ playBeep(400, 0.1);
40
+ }
41
+
42
+ export function turn(direction: "back" | "forward") {
43
+ if (direction === "forward") {
44
+ playBeep(600, 0.1);
45
+ } else {
46
+ playBeep(300, 0.1);
47
+ }
48
+ }
49
+
50
+ export function copy() {
51
+ playBeep(800, 0.15, "triangle");
52
+ }
53
+
54
+ export function deny() {
55
+ playBeep(150, 0.2, "sawtooth");
56
+ }
@@ -0,0 +1,14 @@
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ export function formatChangelogDate(date: string) {
9
+ return new Date(date).toLocaleDateString("en-US", {
10
+ month: "long",
11
+ day: "numeric",
12
+ year: "numeric",
13
+ });
14
+ }
package/src/shiki.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { codeToHtml } from "shiki";
2
+
3
+ export type SnippetInput = Record<string, { code: string; lang: string }>;
4
+
5
+ export async function highlightSnippets<T extends SnippetInput>(
6
+ snippets: T,
7
+ options?: { lightTheme?: string; darkTheme?: string },
8
+ ): Promise<Record<keyof T, string>> {
9
+ const light = options?.lightTheme || "github-light";
10
+ const dark = options?.darkTheme || "github-dark";
11
+
12
+ const entries = await Promise.all(
13
+ Object.entries(snippets).map(
14
+ async ([key, s]) =>
15
+ [
16
+ key,
17
+ await codeToHtml(s.code, {
18
+ lang: s.lang,
19
+ themes: { light, dark },
20
+ }),
21
+ ] as const,
22
+ ),
23
+ );
24
+ return Object.fromEntries(entries) as Record<keyof T, string>;
25
+ }