@inferencesh/ui 0.0.0-stage → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +68 -2
- package/components.json +22 -0
- package/package.json +231 -4
- package/src/components/audio-player.tsx +549 -0
- package/src/components/button.tsx +13 -0
- package/src/components/chart.tsx +403 -0
- package/src/components/circuit.tsx +276 -0
- package/src/components/code-block/code-block.tsx +260 -0
- package/src/components/code-block/index.ts +18 -0
- package/src/components/code-block/languages.ts +311 -0
- package/src/components/code-block/styles.ts +39 -0
- package/src/components/code-block/tokenizer.ts +290 -0
- package/src/components/code-block/types.ts +38 -0
- package/src/components/code-block/utils.ts +39 -0
- package/src/components/code.tsx +48 -0
- package/src/components/cookie-consent.tsx +137 -0
- package/src/components/copy-button.tsx +44 -0
- package/src/components/country-dropdown.tsx +188 -0
- package/src/components/glow-loader-fast.tsx +32 -0
- package/src/components/glow-loader-mono.tsx +33 -0
- package/src/components/glow-loader-sm-fast.tsx +32 -0
- package/src/components/glow-loader-sm-mono.tsx +34 -0
- package/src/components/glow-loader-sm.tsx +32 -0
- package/src/components/glow-loader.tsx +32 -0
- package/src/components/glow-logo.tsx +32 -0
- package/src/components/gradient.tsx +80 -0
- package/src/components/index.ts +45 -0
- package/src/components/inline-sidebar.tsx +351 -0
- package/src/components/input-color.tsx +349 -0
- package/src/components/logo-base.tsx +213 -0
- package/src/components/logo-color.tsx +23 -0
- package/src/components/logo-full.tsx +71 -0
- package/src/components/logo-svg.tsx +23 -0
- package/src/components/menu-navigation.tsx +359 -0
- package/src/components/multi-select.tsx +721 -0
- package/src/components/pastel-gradient.tsx +25 -0
- package/src/components/pixel-symmetry.tsx +151 -0
- package/src/components/progress.tsx +28 -0
- package/src/components/scroll-more.tsx +131 -0
- package/src/components/select.tsx +85 -0
- package/src/components/shimmering-text.tsx +108 -0
- package/src/components/sidebar-light.tsx +102 -0
- package/src/components/sonner.tsx +38 -0
- package/src/components/theme-switcher-sidebar.tsx +56 -0
- package/src/components/theme-switcher-simple.tsx +110 -0
- package/src/components/theme-switcher.tsx +47 -0
- package/src/components/toggle-group.tsx +55 -0
- package/src/components/tooltip-button.tsx +91 -0
- package/src/components/tooltip.tsx +71 -0
- package/src/components/tree-view.tsx +499 -0
- package/src/components/unglow-loader.tsx +33 -0
- package/src/components/waveform.tsx +1632 -0
- package/src/components/zoomable-image.tsx +76 -0
- package/src/docs/api-docs.tsx +763 -0
- package/src/docs/callout.tsx +160 -0
- package/src/docs/code-group.tsx +215 -0
- package/src/docs/copy-menu.tsx +143 -0
- package/src/docs/doc-tabs.tsx +84 -0
- package/src/docs/index.ts +16 -0
- package/src/docs/steps.tsx +105 -0
- package/src/docs/table-of-contents.tsx +174 -0
- package/src/examples/guide-markdown.ts +129 -0
- package/src/examples/index.ts +1153 -0
- package/src/global.d.ts +1 -0
- package/src/hooks/use-copy-to-clipboard.ts +36 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/lib/display-name.ts +15 -0
- package/src/lib/example-input.ts +130 -0
- package/src/lib/plans.ts +44 -0
- package/src/lib/safe-content.ts +137 -0
- package/src/lib/schema.ts +320 -0
- package/src/lib/storage.ts +108 -0
- package/src/markdown/docs-markdown-renderer.tsx +518 -0
- package/src/markdown/helpers.ts +95 -0
- package/src/markdown/index.ts +24 -0
- package/src/markdown/markdown-renderer.tsx +232 -0
- package/src/markdown/youtube-embed.tsx +43 -0
- package/src/og/image.ts +312 -0
- package/src/og/index.tsx +430 -0
- package/src/og/query.ts +26 -0
- package/src/shadcn/accordion.tsx +71 -0
- package/src/shadcn/alert-dialog.tsx +187 -0
- package/src/shadcn/alert.tsx +75 -0
- package/src/shadcn/aspect-ratio.tsx +22 -0
- package/src/shadcn/attachment.tsx +207 -0
- package/src/shadcn/avatar.tsx +106 -0
- package/src/shadcn/badge.tsx +51 -0
- package/src/shadcn/breadcrumb.tsx +124 -0
- package/src/shadcn/bubble.tsx +127 -0
- package/src/shadcn/button-group.tsx +87 -0
- package/src/shadcn/button.tsx +57 -0
- package/src/shadcn/calendar.tsx +219 -0
- package/src/shadcn/card.tsx +102 -0
- package/src/shadcn/carousel.tsx +242 -0
- package/src/shadcn/chart.tsx +372 -0
- package/src/shadcn/checkbox.tsx +28 -0
- package/src/shadcn/collapsible.tsx +19 -0
- package/src/shadcn/combobox.tsx +297 -0
- package/src/shadcn/command.tsx +194 -0
- package/src/shadcn/context-menu.tsx +270 -0
- package/src/shadcn/dialog.tsx +158 -0
- package/src/shadcn/direction.tsx +4 -0
- package/src/shadcn/drawer.tsx +227 -0
- package/src/shadcn/dropdown-menu.tsx +265 -0
- package/src/shadcn/empty.tsx +103 -0
- package/src/shadcn/field.tsx +238 -0
- package/src/shadcn/form.tsx +173 -0
- package/src/shadcn/hover-card.tsx +50 -0
- package/src/shadcn/index.ts +53 -0
- package/src/shadcn/input-group.tsx +156 -0
- package/src/shadcn/input-otp.tsx +84 -0
- package/src/shadcn/input.tsx +19 -0
- package/src/shadcn/item.tsx +201 -0
- package/src/shadcn/kbd.tsx +26 -0
- package/src/shadcn/label.tsx +19 -0
- package/src/shadcn/marker.tsx +70 -0
- package/src/shadcn/menubar.tsx +278 -0
- package/src/shadcn/message-scroller.tsx +131 -0
- package/src/shadcn/message.tsx +91 -0
- package/src/shadcn/native-select.tsx +60 -0
- package/src/shadcn/navigation-menu.tsx +167 -0
- package/src/shadcn/pagination.tsx +130 -0
- package/src/shadcn/popover.tsx +87 -0
- package/src/shadcn/progress.tsx +82 -0
- package/src/shadcn/radio-group.tsx +35 -0
- package/src/shadcn/resizable.tsx +49 -0
- package/src/shadcn/scroll-area.tsx +52 -0
- package/src/shadcn/select.tsx +200 -0
- package/src/shadcn/separator.tsx +22 -0
- package/src/shadcn/sheet.tsx +136 -0
- package/src/shadcn/sidebar.tsx +721 -0
- package/src/shadcn/skeleton.tsx +13 -0
- package/src/shadcn/slider.tsx +51 -0
- package/src/shadcn/sonner.tsx +49 -0
- package/src/shadcn/spinner.tsx +10 -0
- package/src/shadcn/switch.tsx +29 -0
- package/src/shadcn/table.tsx +115 -0
- package/src/shadcn/tabs.tsx +79 -0
- package/src/shadcn/textarea.tsx +17 -0
- package/src/shadcn/toggle-group.tsx +89 -0
- package/src/shadcn/toggle.tsx +44 -0
- package/src/shadcn/tooltip.tsx +63 -0
- package/src/theme/config.ts +48 -0
- package/src/theme/css/base.css +92 -0
- package/src/theme/css/bitboy.css +75 -0
- package/src/theme/css/dark.css +47 -0
- package/src/theme/css/designer.css +122 -0
- package/src/theme/css/fonts.css +33 -0
- package/src/theme/css/globals.css +14 -0
- package/src/theme/css/hackintosh.css +57 -0
- package/src/theme/css/llama.css +103 -0
- package/src/theme/css/safelist.css +39 -0
- package/src/theme/css/shadcn.css +77 -0
- package/src/theme/css/theme.css +216 -0
- package/src/theme/embed-theme.ts +146 -0
- package/src/theme/fonts/FiraCode-VF.woff2 +0 -0
- package/src/theme/fonts/Hack-Regular.woff2 +0 -0
- package/src/theme/fonts/LICENSE-FiraCode.txt +93 -0
- package/src/theme/fonts/LICENSE-Hack.txt +45 -0
- package/src/theme/fonts/LICENSE-MonaSans.txt +93 -0
- package/src/theme/fonts/Mona-Sans.woff2 +0 -0
- package/src/theme/index.ts +15 -0
- package/src/utils/cn.ts +3 -0
- package/src/utils/index.ts +2 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
|
|
3
|
+
// FNV-1a, so the same seed always paints the same cover.
|
|
4
|
+
function hash(seed: string) {
|
|
5
|
+
let h = 0x811c9dc5;
|
|
6
|
+
for (let i = 0; i < seed.length; i++) {
|
|
7
|
+
h ^= seed.charCodeAt(i);
|
|
8
|
+
h = Math.imul(h, 0x01000193);
|
|
9
|
+
}
|
|
10
|
+
return h >>> 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// A soft two-hue gradient picked from `seed` (e.g. a resource id): pale in
|
|
14
|
+
// light mode, deep and muted in dark mode.
|
|
15
|
+
export function PastelGradient({ seed, className = "" }: { seed: string; className?: string }) {
|
|
16
|
+
const h = hash(seed);
|
|
17
|
+
const a = h % 360;
|
|
18
|
+
const b = (a + 40 + ((h >>> 9) % 80)) % 360;
|
|
19
|
+
const angle = 90 + ((h >>> 17) % 180);
|
|
20
|
+
const style = {
|
|
21
|
+
"--pg-light": `linear-gradient(${angle}deg, hsl(${a} 75% 88%), hsl(${b} 70% 80%))`,
|
|
22
|
+
"--pg-dark": `linear-gradient(${angle}deg, hsl(${a} 30% 24%), hsl(${b} 35% 16%))`,
|
|
23
|
+
} as CSSProperties;
|
|
24
|
+
return <div aria-hidden style={style} className={`bg-(image:--pg-light) dark:bg-(image:--pg-dark) ${className}`} />;
|
|
25
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
const PALETTES: Record<string, [number, number, number][]> = {
|
|
4
|
+
brand: [
|
|
5
|
+
[255, 86, 157], // pink
|
|
6
|
+
[84, 21, 255], // purple
|
|
7
|
+
[255, 241, 76], // yellow
|
|
8
|
+
[0, 208, 138], // green
|
|
9
|
+
[240, 138, 255], // lavender
|
|
10
|
+
[28, 174, 255], // blue
|
|
11
|
+
[227, 255, 0], // lime
|
|
12
|
+
],
|
|
13
|
+
cool: [
|
|
14
|
+
[84, 21, 255],
|
|
15
|
+
[28, 174, 255],
|
|
16
|
+
[0, 208, 138],
|
|
17
|
+
[240, 138, 255],
|
|
18
|
+
[227, 255, 0],
|
|
19
|
+
],
|
|
20
|
+
warm: [
|
|
21
|
+
[255, 86, 157],
|
|
22
|
+
[255, 241, 76],
|
|
23
|
+
[255, 153, 0],
|
|
24
|
+
[255, 51, 51],
|
|
25
|
+
[240, 138, 255],
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function PixelSymmetry({
|
|
30
|
+
pixelSize = 8,
|
|
31
|
+
palette = "brand",
|
|
32
|
+
symmetry = "quad",
|
|
33
|
+
density = 0.35,
|
|
34
|
+
className = "",
|
|
35
|
+
}: {
|
|
36
|
+
pixelSize?: number;
|
|
37
|
+
palette?: "brand" | "cool" | "warm";
|
|
38
|
+
symmetry?: "horizontal" | "vertical" | "quad";
|
|
39
|
+
density?: number;
|
|
40
|
+
className?: string;
|
|
41
|
+
}) {
|
|
42
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
43
|
+
const drawn = useRef(false);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const canvas = canvasRef.current;
|
|
47
|
+
if (!canvas || drawn.current) return;
|
|
48
|
+
|
|
49
|
+
const parent = canvas.parentElement;
|
|
50
|
+
if (!parent) return;
|
|
51
|
+
const w = parent.offsetWidth;
|
|
52
|
+
const h = parent.offsetHeight;
|
|
53
|
+
if (w === 0 || h === 0) return;
|
|
54
|
+
|
|
55
|
+
drawn.current = true;
|
|
56
|
+
canvas.width = w;
|
|
57
|
+
canvas.height = h;
|
|
58
|
+
|
|
59
|
+
const ctx = canvas.getContext("2d")!;
|
|
60
|
+
const colors = PALETTES[palette] || PALETTES.brand;
|
|
61
|
+
|
|
62
|
+
const fullCols = Math.ceil(w / pixelSize);
|
|
63
|
+
const fullRows = Math.ceil(h / pixelSize);
|
|
64
|
+
const srcCols = symmetry === "vertical" || symmetry === "quad" ? Math.ceil(fullCols / 2) : fullCols;
|
|
65
|
+
const srcRows = symmetry === "horizontal" || symmetry === "quad" ? Math.ceil(fullRows / 2) : fullRows;
|
|
66
|
+
|
|
67
|
+
const random = Math.random;
|
|
68
|
+
const pick = (arr: [number, number, number][]) => arr[(random() * arr.length) | 0];
|
|
69
|
+
|
|
70
|
+
const grid: ([number, number, number] | null)[] = new Array(srcCols * srcRows).fill(null);
|
|
71
|
+
const idx = (x: number, y: number) => y * srcCols + x;
|
|
72
|
+
|
|
73
|
+
const bright = (rgb: [number, number, number], f: number): [number, number, number] => [
|
|
74
|
+
Math.min(255, (rgb[0] * f) | 0),
|
|
75
|
+
Math.min(255, (rgb[1] * f) | 0),
|
|
76
|
+
Math.min(255, (rgb[2] * f) | 0),
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
// pass 1: clusters
|
|
80
|
+
const clusterCount = (srcCols * srcRows * density * 0.15) | 0;
|
|
81
|
+
for (let i = 0; i < clusterCount; i++) {
|
|
82
|
+
const cx = (random() * srcCols) | 0;
|
|
83
|
+
const cy = (random() * srcRows) | 0;
|
|
84
|
+
const color = pick(colors);
|
|
85
|
+
const radius = 1 + ((random() * 3) | 0);
|
|
86
|
+
|
|
87
|
+
for (let dy = -radius; dy <= radius; dy++) {
|
|
88
|
+
for (let dx = -radius; dx <= radius; dx++) {
|
|
89
|
+
const nx = cx + dx;
|
|
90
|
+
const ny = cy + dy;
|
|
91
|
+
if (nx < 0 || nx >= srcCols || ny < 0 || ny >= srcRows) continue;
|
|
92
|
+
const dist = Math.abs(dx) + Math.abs(dy);
|
|
93
|
+
if (random() < density * (1 - dist / (radius * 2 + 1))) {
|
|
94
|
+
grid[idx(nx, ny)] = bright(color, 0.5 + random() * 0.5);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// pass 2: scatter
|
|
101
|
+
for (let i = 0; i < grid.length; i++) {
|
|
102
|
+
if (!grid[i] && random() < density * 0.12) {
|
|
103
|
+
grid[i] = bright(pick(colors), 0.3 + random() * 0.4);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// pass 3: structural bars
|
|
108
|
+
const barCount = 1 + ((random() * 3) | 0);
|
|
109
|
+
for (let b = 0; b < barCount; b++) {
|
|
110
|
+
const horiz = random() > 0.5;
|
|
111
|
+
const color = pick(colors);
|
|
112
|
+
const pos = (random() * (horiz ? srcRows : srcCols)) | 0;
|
|
113
|
+
const len = 3 + ((random() * 6) | 0);
|
|
114
|
+
const start = (random() * (horiz ? srcCols : srcRows)) | 0;
|
|
115
|
+
for (let i = 0; i < len; i++) {
|
|
116
|
+
const x = horiz ? start + i : pos;
|
|
117
|
+
const y = horiz ? pos : start + i;
|
|
118
|
+
if (x >= 0 && x < srcCols && y >= 0 && y < srcRows) {
|
|
119
|
+
grid[idx(x, y)] = bright(color, 0.6 + random() * 0.4);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// draw
|
|
125
|
+
ctx.fillStyle = "hsl(0 0% 5.9%)";
|
|
126
|
+
ctx.fillRect(0, 0, w, h);
|
|
127
|
+
|
|
128
|
+
for (let y = 0; y < srcRows; y++) {
|
|
129
|
+
for (let x = 0; x < srcCols; x++) {
|
|
130
|
+
const c = grid[idx(x, y)];
|
|
131
|
+
if (!c) continue;
|
|
132
|
+
ctx.fillStyle = `rgb(${c[0]},${c[1]},${c[2]})`;
|
|
133
|
+
const px = x * pixelSize;
|
|
134
|
+
const py = y * pixelSize;
|
|
135
|
+
ctx.fillRect(px, py, pixelSize, pixelSize);
|
|
136
|
+
if (symmetry === "vertical" || symmetry === "quad") ctx.fillRect((fullCols - 1 - x) * pixelSize, py, pixelSize, pixelSize);
|
|
137
|
+
if (symmetry === "horizontal" || symmetry === "quad") ctx.fillRect(px, (fullRows - 1 - y) * pixelSize, pixelSize, pixelSize);
|
|
138
|
+
if (symmetry === "quad") ctx.fillRect((fullCols - 1 - x) * pixelSize, (fullRows - 1 - y) * pixelSize, pixelSize, pixelSize);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}, [pixelSize, palette, symmetry, density]);
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<div className={className} style={{ position: "relative" }}>
|
|
145
|
+
<canvas
|
|
146
|
+
ref={canvasRef}
|
|
147
|
+
style={{ position: "absolute", inset: 0, width: "100%", height: "100%" }}
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Progress as ProgressPrimitive } from "@base-ui/react/progress"
|
|
2
|
+
import { cn } from "cn"
|
|
3
|
+
|
|
4
|
+
import { ProgressIndicator, ProgressTrack } from "../shadcn/progress"
|
|
5
|
+
|
|
6
|
+
export { ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue } from "../shadcn/progress"
|
|
7
|
+
|
|
8
|
+
// The shadcn progress, with a class for its track and its indicator. The
|
|
9
|
+
// shadcn one renders both itself with no way in, so the meters that colour
|
|
10
|
+
// the bar by value (engines) compose the parts here instead.
|
|
11
|
+
// ui/shadcn/progress.tsx stays as the CLI writes it.
|
|
12
|
+
export function Progress({
|
|
13
|
+
className,
|
|
14
|
+
trackClassName,
|
|
15
|
+
indicatorClassName,
|
|
16
|
+
children,
|
|
17
|
+
value,
|
|
18
|
+
...props
|
|
19
|
+
}: ProgressPrimitive.Root.Props & { trackClassName?: string; indicatorClassName?: string }) {
|
|
20
|
+
return (
|
|
21
|
+
<ProgressPrimitive.Root value={value} data-slot="progress" className={cn("flex flex-wrap gap-3", className)} {...props}>
|
|
22
|
+
{children}
|
|
23
|
+
<ProgressTrack className={trackClassName}>
|
|
24
|
+
<ProgressIndicator className={indicatorClassName} />
|
|
25
|
+
</ProgressTrack>
|
|
26
|
+
</ProgressPrimitive.Root>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useRef, useState, useEffect, useCallback, type ReactNode } from 'react'
|
|
4
|
+
import { cn } from '../utils'
|
|
5
|
+
import { ChevronDown, ChevronsUpDown } from 'lucide-react'
|
|
6
|
+
|
|
7
|
+
interface ScrollMoreProps {
|
|
8
|
+
children: ReactNode
|
|
9
|
+
className?: string
|
|
10
|
+
/** 'fade' for gradient edges, 'badge' for a clickable pill, 'both' for both (default: 'both') */
|
|
11
|
+
indicator?: 'fade' | 'badge' | 'both'
|
|
12
|
+
/** fade height in pixels (default: 32) */
|
|
13
|
+
fadeHeight?: number
|
|
14
|
+
/** badge label (default: 'scroll for more') */
|
|
15
|
+
badgeLabel?: string
|
|
16
|
+
/** when true, content is unclamped and a collapse button is shown */
|
|
17
|
+
expanded?: boolean
|
|
18
|
+
/** called to toggle expand/collapse — enables the expand button on the bottom fade */
|
|
19
|
+
onExpandToggle?: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ScrollMore({
|
|
23
|
+
children,
|
|
24
|
+
className,
|
|
25
|
+
indicator = 'both',
|
|
26
|
+
fadeHeight = 32,
|
|
27
|
+
badgeLabel = 'scroll for more',
|
|
28
|
+
expanded,
|
|
29
|
+
onExpandToggle,
|
|
30
|
+
}: ScrollMoreProps) {
|
|
31
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
32
|
+
const [canScrollUp, setCanScrollUp] = useState(false)
|
|
33
|
+
const [canScrollDown, setCanScrollDown] = useState(false)
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const el = ref.current
|
|
37
|
+
if (!el) return
|
|
38
|
+
|
|
39
|
+
const check = () => {
|
|
40
|
+
setCanScrollUp(el.scrollTop > 2)
|
|
41
|
+
setCanScrollDown(el.scrollHeight - el.scrollTop - el.clientHeight > 2)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
check()
|
|
45
|
+
el.addEventListener('scroll', check, { passive: true })
|
|
46
|
+
|
|
47
|
+
const ro = new ResizeObserver(check)
|
|
48
|
+
ro.observe(el)
|
|
49
|
+
|
|
50
|
+
return () => {
|
|
51
|
+
el.removeEventListener('scroll', check)
|
|
52
|
+
ro.disconnect()
|
|
53
|
+
}
|
|
54
|
+
}, [])
|
|
55
|
+
|
|
56
|
+
const scrollDown = useCallback(() => {
|
|
57
|
+
const el = ref.current
|
|
58
|
+
if (!el) return
|
|
59
|
+
el.scrollBy({ top: el.clientHeight * 0.75, behavior: 'smooth' })
|
|
60
|
+
}, [])
|
|
61
|
+
|
|
62
|
+
const showFade = indicator === 'fade' || indicator === 'both'
|
|
63
|
+
const showBadge = indicator === 'badge' || indicator === 'both'
|
|
64
|
+
const canExpand = !!onExpandToggle
|
|
65
|
+
const isOverflowing = canScrollDown || canScrollUp
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className="min-h-0">
|
|
69
|
+
<div className={cn('relative overflow-hidden', expanded ? undefined : className)}>
|
|
70
|
+
{showFade && !expanded && (
|
|
71
|
+
<div
|
|
72
|
+
className={cn(
|
|
73
|
+
'absolute inset-x-0 top-0 z-10 pointer-events-none transition-opacity duration-200',
|
|
74
|
+
'bg-gradient-to-b from-background to-transparent',
|
|
75
|
+
canScrollUp ? 'opacity-100' : 'opacity-0',
|
|
76
|
+
)}
|
|
77
|
+
style={{ height: fadeHeight }}
|
|
78
|
+
/>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
<div ref={ref} className={cn('overflow-y-auto', expanded ? undefined : className)}>
|
|
82
|
+
{children}
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
{showFade && !expanded && (
|
|
86
|
+
<div
|
|
87
|
+
className={cn(
|
|
88
|
+
'absolute inset-x-0 bottom-0 z-10 pointer-events-none transition-opacity duration-200',
|
|
89
|
+
'bg-gradient-to-t from-background to-transparent',
|
|
90
|
+
canScrollDown ? 'opacity-100' : 'opacity-0',
|
|
91
|
+
)}
|
|
92
|
+
style={{ height: fadeHeight }}
|
|
93
|
+
/>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
{showBadge && !expanded && canScrollDown && (
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
onClick={scrollDown}
|
|
100
|
+
className={cn(
|
|
101
|
+
'absolute bottom-2 left-1/2 -translate-x-1/2 z-20',
|
|
102
|
+
'flex items-center gap-1 px-2.5 py-1 rounded-full',
|
|
103
|
+
'bg-background border shadow-sm',
|
|
104
|
+
'text-xs text-muted-foreground hover:text-foreground',
|
|
105
|
+
'transition-all duration-200 animate-in fade-in-0 slide-in-from-bottom-1',
|
|
106
|
+
'cursor-pointer',
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
<ChevronDown className="h-3 w-3" />
|
|
110
|
+
<span>{badgeLabel}</span>
|
|
111
|
+
</button>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{canExpand && (isOverflowing || expanded) && (
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
onClick={onExpandToggle}
|
|
119
|
+
className={cn(
|
|
120
|
+
'w-full flex items-center justify-center gap-1 py-0.5',
|
|
121
|
+
'text-xs text-muted-foreground/60 hover:text-muted-foreground',
|
|
122
|
+
'transition-colors cursor-pointer',
|
|
123
|
+
)}
|
|
124
|
+
>
|
|
125
|
+
<ChevronsUpDown className="h-3 w-3" />
|
|
126
|
+
{expanded ? 'collapse' : 'expand'}
|
|
127
|
+
</button>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "cn"
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
Select as SelectRoot,
|
|
6
|
+
SelectContent as SelectContentBase,
|
|
7
|
+
SelectItem,
|
|
8
|
+
} from "../shadcn/select"
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
SelectGroup,
|
|
12
|
+
SelectItem,
|
|
13
|
+
SelectLabel,
|
|
14
|
+
SelectScrollDownButton,
|
|
15
|
+
SelectScrollUpButton,
|
|
16
|
+
SelectSeparator,
|
|
17
|
+
SelectTrigger,
|
|
18
|
+
SelectValue,
|
|
19
|
+
} from "../shadcn/select"
|
|
20
|
+
|
|
21
|
+
// The shadcn select, with what every select here wants: the closed trigger
|
|
22
|
+
// shows the selected item's label, onValueChange never sees null, and the
|
|
23
|
+
// list grows to its widest option. ui/shadcn/select.tsx stays as the CLI
|
|
24
|
+
// writes it.
|
|
25
|
+
|
|
26
|
+
type RootProps = React.ComponentProps<typeof SelectRoot>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Base UI's Value shows the selected item's label only from `items`;
|
|
30
|
+
* without it a closed select shows the raw value (an id, "version"). The
|
|
31
|
+
* labels are read off the SelectItems in the tree unless given.
|
|
32
|
+
*/
|
|
33
|
+
export function Select({
|
|
34
|
+
onValueChange,
|
|
35
|
+
items,
|
|
36
|
+
children,
|
|
37
|
+
...props
|
|
38
|
+
}: Omit<RootProps, "onValueChange" | "items"> & {
|
|
39
|
+
items?: Record<string, React.ReactNode>
|
|
40
|
+
onValueChange?: (value: string) => void
|
|
41
|
+
}) {
|
|
42
|
+
return (
|
|
43
|
+
<SelectRoot
|
|
44
|
+
onValueChange={onValueChange ? (v) => typeof v === "string" && onValueChange(v) : undefined}
|
|
45
|
+
items={items ?? itemLabels(children)}
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</SelectRoot>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The value-to-label map of the SelectItems among children, at any depth
|
|
55
|
+
* (content, groups, arrays from a map). An item rendered inside another
|
|
56
|
+
* component is not seen; its select shows the raw value, as before.
|
|
57
|
+
*/
|
|
58
|
+
function itemLabels(children: React.ReactNode): Record<string, React.ReactNode> | undefined {
|
|
59
|
+
const labels: Record<string, React.ReactNode> = {}
|
|
60
|
+
let found = false
|
|
61
|
+
const walk = (node: React.ReactNode) => {
|
|
62
|
+
React.Children.forEach(node, (child) => {
|
|
63
|
+
if (!React.isValidElement(child)) return
|
|
64
|
+
const props = child.props as { value?: unknown; label?: React.ReactNode; children?: React.ReactNode }
|
|
65
|
+
if (child.type === SelectItem && typeof props.value === "string") {
|
|
66
|
+
labels[props.value] = props.label ?? props.children
|
|
67
|
+
found = true
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
if (props.children != null) walk(props.children)
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
walk(children)
|
|
74
|
+
return found ? labels : undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The list is at least the trigger's width and grows to its widest option, within the space left. */
|
|
78
|
+
export function SelectContent({ className, ...props }: React.ComponentProps<typeof SelectContentBase>) {
|
|
79
|
+
return (
|
|
80
|
+
<SelectContentBase
|
|
81
|
+
className={cn("w-max max-w-(--available-width) min-w-[max(var(--anchor-width),9rem)]", className)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { motion, useInView, UseInViewOptions } from "motion/react"
|
|
4
|
+
import React, { useMemo, useRef } from "react"
|
|
5
|
+
|
|
6
|
+
import { cn } from "cn"
|
|
7
|
+
|
|
8
|
+
interface ShimmeringTextProps {
|
|
9
|
+
/** Text to display with shimmer effect */
|
|
10
|
+
text: string
|
|
11
|
+
/** Animation duration in seconds */
|
|
12
|
+
duration?: number
|
|
13
|
+
/** Delay before starting animation */
|
|
14
|
+
delay?: number
|
|
15
|
+
/** Whether to repeat the animation */
|
|
16
|
+
repeat?: boolean
|
|
17
|
+
/** Pause duration between repeats in seconds */
|
|
18
|
+
repeatDelay?: number
|
|
19
|
+
/** Custom className */
|
|
20
|
+
className?: string
|
|
21
|
+
/** Whether to start animation when component enters viewport */
|
|
22
|
+
startOnView?: boolean
|
|
23
|
+
/** Whether to animate only once */
|
|
24
|
+
once?: boolean
|
|
25
|
+
/** Margin for in-view detection (rootMargin) */
|
|
26
|
+
inViewMargin?: UseInViewOptions["margin"]
|
|
27
|
+
/** Shimmer spread multiplier */
|
|
28
|
+
spread?: number
|
|
29
|
+
/** Base text color */
|
|
30
|
+
color?: string
|
|
31
|
+
/** Shimmer gradient color */
|
|
32
|
+
shimmerColor?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function ShimmeringText({
|
|
36
|
+
text,
|
|
37
|
+
duration = 2,
|
|
38
|
+
delay = 0,
|
|
39
|
+
repeat = true,
|
|
40
|
+
repeatDelay = 0.5,
|
|
41
|
+
className,
|
|
42
|
+
startOnView = true,
|
|
43
|
+
once = false,
|
|
44
|
+
inViewMargin,
|
|
45
|
+
spread = 2,
|
|
46
|
+
color,
|
|
47
|
+
shimmerColor,
|
|
48
|
+
}: ShimmeringTextProps) {
|
|
49
|
+
const ref = useRef<HTMLSpanElement>(null)
|
|
50
|
+
const isInView = useInView(ref, { once, margin: inViewMargin })
|
|
51
|
+
|
|
52
|
+
// Calculate dynamic spread based on text length
|
|
53
|
+
const dynamicSpread = useMemo(() => {
|
|
54
|
+
return text.length * spread
|
|
55
|
+
}, [text, spread])
|
|
56
|
+
|
|
57
|
+
// Determine if we should start animation
|
|
58
|
+
const shouldAnimate = !startOnView || isInView
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<motion.span
|
|
62
|
+
ref={ref}
|
|
63
|
+
className={cn(
|
|
64
|
+
"relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
|
|
65
|
+
"[--base-color:var(--muted-foreground)] [--shimmer-color:var(--foreground)]",
|
|
66
|
+
"[background-repeat:no-repeat,padding-box]",
|
|
67
|
+
"[--shimmer-bg:linear-gradient(90deg,transparent_calc(50%-var(--spread)),var(--shimmer-color),transparent_calc(50%+var(--spread)))]",
|
|
68
|
+
"dark:[--base-color:var(--muted-foreground)] dark:[--shimmer-color:var(--foreground)]",
|
|
69
|
+
className
|
|
70
|
+
)}
|
|
71
|
+
style={
|
|
72
|
+
{
|
|
73
|
+
"--spread": `${dynamicSpread}px`,
|
|
74
|
+
...(color && { "--base-color": color }),
|
|
75
|
+
...(shimmerColor && { "--shimmer-color": shimmerColor }),
|
|
76
|
+
backgroundImage: `var(--shimmer-bg), linear-gradient(var(--base-color), var(--base-color))`,
|
|
77
|
+
} as React.CSSProperties
|
|
78
|
+
}
|
|
79
|
+
initial={{
|
|
80
|
+
backgroundPosition: "100% center",
|
|
81
|
+
opacity: 0,
|
|
82
|
+
}}
|
|
83
|
+
animate={
|
|
84
|
+
shouldAnimate
|
|
85
|
+
? {
|
|
86
|
+
backgroundPosition: "0% center",
|
|
87
|
+
opacity: 1,
|
|
88
|
+
}
|
|
89
|
+
: {}
|
|
90
|
+
}
|
|
91
|
+
transition={{
|
|
92
|
+
backgroundPosition: {
|
|
93
|
+
repeat: repeat ? Infinity : 0,
|
|
94
|
+
duration,
|
|
95
|
+
delay,
|
|
96
|
+
repeatDelay,
|
|
97
|
+
ease: "linear",
|
|
98
|
+
},
|
|
99
|
+
opacity: {
|
|
100
|
+
duration: 0.3,
|
|
101
|
+
delay,
|
|
102
|
+
},
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
{text}
|
|
106
|
+
</motion.span>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "cn"
|
|
3
|
+
|
|
4
|
+
export interface NavItem {
|
|
5
|
+
title: string
|
|
6
|
+
href: string
|
|
7
|
+
icon?: React.ComponentType<{ className?: string }>
|
|
8
|
+
items?: NavItem[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface SidebarLightProps {
|
|
12
|
+
items: NavItem[]
|
|
13
|
+
pathname: string
|
|
14
|
+
className?: string
|
|
15
|
+
/** Link component to use - defaults to <a> tag. Pass your router's Link component. */
|
|
16
|
+
LinkComponent?: React.ComponentType<{ href: string; className?: string; children: React.ReactNode }>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface NavItemRendererProps {
|
|
20
|
+
item: NavItem
|
|
21
|
+
pathname: string
|
|
22
|
+
depth: number
|
|
23
|
+
LinkComponent: React.ComponentType<{ href: string; className?: string; children: React.ReactNode }>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function NavItemRenderer({ item, pathname, depth, LinkComponent }: NavItemRendererProps) {
|
|
27
|
+
const hasChildren = item.items && item.items.length > 0
|
|
28
|
+
const isActive = pathname === item.href
|
|
29
|
+
|
|
30
|
+
// Parent item with children (section header)
|
|
31
|
+
if (hasChildren) {
|
|
32
|
+
return (
|
|
33
|
+
<div className="space-y-1">
|
|
34
|
+
<div
|
|
35
|
+
className={cn(
|
|
36
|
+
"flex items-center gap-2 text-sm",
|
|
37
|
+
depth === 0 && "px-3 py-2 font-semibold text-foreground",
|
|
38
|
+
depth > 0 && "px-3 py-1.5 font-medium text-muted-foreground"
|
|
39
|
+
)}
|
|
40
|
+
>
|
|
41
|
+
{item.icon && <item.icon className={cn(depth === 0 ? "h-4 w-4" : "h-3.5 w-3.5")} />}
|
|
42
|
+
{item.title}
|
|
43
|
+
</div>
|
|
44
|
+
<div className={cn("ml-4 space-y-1 border-l border-border pl-2")}>
|
|
45
|
+
{item.items!.map((subItem, index) => (
|
|
46
|
+
<NavItemRenderer
|
|
47
|
+
key={subItem.href !== "#" ? subItem.href : `${subItem.title}-${index}`}
|
|
48
|
+
item={subItem}
|
|
49
|
+
pathname={pathname}
|
|
50
|
+
depth={depth + 1}
|
|
51
|
+
LinkComponent={LinkComponent}
|
|
52
|
+
/>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Leaf item (link)
|
|
60
|
+
return (
|
|
61
|
+
<LinkComponent
|
|
62
|
+
href={item.href}
|
|
63
|
+
className={cn(
|
|
64
|
+
"flex items-center gap-2 text-sm rounded-md transition-colors",
|
|
65
|
+
depth === 0 && "px-3 py-2",
|
|
66
|
+
depth > 0 && "px-3 py-1.5",
|
|
67
|
+
isActive
|
|
68
|
+
? "bg-muted font-medium text-foreground"
|
|
69
|
+
: "text-muted-foreground hover:text-foreground hover:bg-muted/50"
|
|
70
|
+
)}
|
|
71
|
+
>
|
|
72
|
+
{item.icon && <item.icon className={cn(depth === 0 ? "h-4 w-4" : "h-3.5 w-3.5")} />}
|
|
73
|
+
{item.title}
|
|
74
|
+
</LinkComponent>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Default link component (plain <a> tag)
|
|
79
|
+
const DefaultLink: React.FC<{ href: string; className?: string; children: React.ReactNode }> = ({ href, className, children }) => (
|
|
80
|
+
<a href={href} className={className}>{children}</a>
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
function SidebarLight({ items, pathname, className, LinkComponent = DefaultLink }: SidebarLightProps) {
|
|
84
|
+
return (
|
|
85
|
+
<aside className={cn("w-full", className)}>
|
|
86
|
+
<nav className="space-y-1">
|
|
87
|
+
{items.map((item, index) => (
|
|
88
|
+
<NavItemRenderer
|
|
89
|
+
key={item.href !== "#" ? item.href : `${item.title}-${index}`}
|
|
90
|
+
item={item}
|
|
91
|
+
pathname={pathname}
|
|
92
|
+
depth={0}
|
|
93
|
+
LinkComponent={LinkComponent}
|
|
94
|
+
/>
|
|
95
|
+
))}
|
|
96
|
+
</nav>
|
|
97
|
+
</aside>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export { SidebarLight }
|
|
102
|
+
export type { SidebarLightProps }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CircleCheckIcon,
|
|
3
|
+
InfoIcon,
|
|
4
|
+
Loader2Icon,
|
|
5
|
+
OctagonXIcon,
|
|
6
|
+
TriangleAlertIcon,
|
|
7
|
+
} from "lucide-react"
|
|
8
|
+
import { useTheme } from "next-themes"
|
|
9
|
+
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
|
10
|
+
|
|
11
|
+
const Toaster = ({ ...props }: ToasterProps) => {
|
|
12
|
+
const { theme = "system" } = useTheme()
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Sonner
|
|
16
|
+
theme={theme as ToasterProps["theme"]}
|
|
17
|
+
className="toaster group"
|
|
18
|
+
icons={{
|
|
19
|
+
success: <CircleCheckIcon className="size-4" />,
|
|
20
|
+
info: <InfoIcon className="size-4" />,
|
|
21
|
+
warning: <TriangleAlertIcon className="size-4" />,
|
|
22
|
+
error: <OctagonXIcon className="size-4" />,
|
|
23
|
+
loading: <Loader2Icon className="size-4 animate-spin" />,
|
|
24
|
+
}}
|
|
25
|
+
style={
|
|
26
|
+
{
|
|
27
|
+
"--normal-bg": "hsl(var(--popover))",
|
|
28
|
+
"--normal-text": "hsl(var(--popover-foreground))",
|
|
29
|
+
"--normal-border": "hsl(var(--border))",
|
|
30
|
+
"--border-radius": "var(--radius)",
|
|
31
|
+
} as React.CSSProperties
|
|
32
|
+
}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { Toaster }
|