@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.
- package/CHANGELOG.md +7 -0
- package/components.json +25 -0
- package/dist/index.d.mts +271 -0
- package/dist/index.d.ts +271 -0
- package/dist/index.js +2455 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2368 -0
- package/dist/index.mjs.map +1 -0
- package/dist/shiki.d.mts +10 -0
- package/dist/shiki.d.ts +10 -0
- package/dist/shiki.js +47 -0
- package/dist/shiki.js.map +1 -0
- package/dist/shiki.mjs +22 -0
- package/dist/shiki.mjs.map +1 -0
- package/dist/styles.css +2556 -0
- package/package.json +55 -0
- package/src/components/content/changelog-content.tsx +119 -0
- package/src/components/content/code-block.tsx +111 -0
- package/src/components/content/docs-content.tsx +113 -0
- package/src/components/layout/docs-layout.tsx +192 -0
- package/src/components/layout/site-header.tsx +152 -0
- package/src/components/layout/site-layout.tsx +28 -0
- package/src/components/layout/site-provider.tsx +23 -0
- package/src/components/layout/site-template.tsx +20 -0
- package/src/components/layout/theme-provider.tsx +10 -0
- package/src/components/layout/theme-switcher.tsx +42 -0
- package/src/components/motion-primitives/morph-icon.tsx +97 -0
- package/src/components/motion-primitives/progressive-blur.tsx +64 -0
- package/src/components/motion-primitives/sliding-number.tsx +131 -0
- package/src/components/motion-primitives/text-effect.tsx +290 -0
- package/src/components/motion-primitives/text-morph.tsx +77 -0
- package/src/components/motion-primitives/text-scramble.tsx +87 -0
- package/src/components/shared/command-block.tsx +193 -0
- package/src/components/shared/scroll-fade.tsx +22 -0
- package/src/components/ui/badge.tsx +50 -0
- package/src/components/ui/brand-logo.tsx +68 -0
- package/src/components/ui/button-group.tsx +87 -0
- package/src/components/ui/button.tsx +104 -0
- package/src/components/ui/card.tsx +97 -0
- package/src/components/ui/direction.tsx +6 -0
- package/src/components/ui/hover-card.tsx +51 -0
- package/src/components/ui/item.tsx +201 -0
- package/src/components/ui/separator.tsx +25 -0
- package/src/components/ui/toast.tsx +322 -0
- package/src/components/ui/tooltip.tsx +66 -0
- package/src/hooks/use-copy-to-clipboard.ts +54 -0
- package/src/index.ts +30 -0
- package/src/lib/constants.ts +63 -0
- package/src/lib/motions.ts +21 -0
- package/src/lib/utils/audio.ts +56 -0
- package/src/lib/utils.ts +14 -0
- package/src/shiki.ts +25 -0
- package/src/styles.css +112 -0
- package/src/typeset.css +513 -0
- package/tsconfig.json +15 -0
- 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
|
+
}
|
package/src/lib/utils.ts
ADDED
|
@@ -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
|
+
}
|