@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.
Files changed (165) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +68 -2
  3. package/components.json +22 -0
  4. package/package.json +231 -4
  5. package/src/components/audio-player.tsx +549 -0
  6. package/src/components/button.tsx +13 -0
  7. package/src/components/chart.tsx +403 -0
  8. package/src/components/circuit.tsx +276 -0
  9. package/src/components/code-block/code-block.tsx +260 -0
  10. package/src/components/code-block/index.ts +18 -0
  11. package/src/components/code-block/languages.ts +311 -0
  12. package/src/components/code-block/styles.ts +39 -0
  13. package/src/components/code-block/tokenizer.ts +290 -0
  14. package/src/components/code-block/types.ts +38 -0
  15. package/src/components/code-block/utils.ts +39 -0
  16. package/src/components/code.tsx +48 -0
  17. package/src/components/cookie-consent.tsx +137 -0
  18. package/src/components/copy-button.tsx +44 -0
  19. package/src/components/country-dropdown.tsx +188 -0
  20. package/src/components/glow-loader-fast.tsx +32 -0
  21. package/src/components/glow-loader-mono.tsx +33 -0
  22. package/src/components/glow-loader-sm-fast.tsx +32 -0
  23. package/src/components/glow-loader-sm-mono.tsx +34 -0
  24. package/src/components/glow-loader-sm.tsx +32 -0
  25. package/src/components/glow-loader.tsx +32 -0
  26. package/src/components/glow-logo.tsx +32 -0
  27. package/src/components/gradient.tsx +80 -0
  28. package/src/components/index.ts +45 -0
  29. package/src/components/inline-sidebar.tsx +351 -0
  30. package/src/components/input-color.tsx +349 -0
  31. package/src/components/logo-base.tsx +213 -0
  32. package/src/components/logo-color.tsx +23 -0
  33. package/src/components/logo-full.tsx +71 -0
  34. package/src/components/logo-svg.tsx +23 -0
  35. package/src/components/menu-navigation.tsx +359 -0
  36. package/src/components/multi-select.tsx +721 -0
  37. package/src/components/pastel-gradient.tsx +25 -0
  38. package/src/components/pixel-symmetry.tsx +151 -0
  39. package/src/components/progress.tsx +28 -0
  40. package/src/components/scroll-more.tsx +131 -0
  41. package/src/components/select.tsx +85 -0
  42. package/src/components/shimmering-text.tsx +108 -0
  43. package/src/components/sidebar-light.tsx +102 -0
  44. package/src/components/sonner.tsx +38 -0
  45. package/src/components/theme-switcher-sidebar.tsx +56 -0
  46. package/src/components/theme-switcher-simple.tsx +110 -0
  47. package/src/components/theme-switcher.tsx +47 -0
  48. package/src/components/toggle-group.tsx +55 -0
  49. package/src/components/tooltip-button.tsx +91 -0
  50. package/src/components/tooltip.tsx +71 -0
  51. package/src/components/tree-view.tsx +499 -0
  52. package/src/components/unglow-loader.tsx +33 -0
  53. package/src/components/waveform.tsx +1632 -0
  54. package/src/components/zoomable-image.tsx +76 -0
  55. package/src/docs/api-docs.tsx +763 -0
  56. package/src/docs/callout.tsx +160 -0
  57. package/src/docs/code-group.tsx +215 -0
  58. package/src/docs/copy-menu.tsx +143 -0
  59. package/src/docs/doc-tabs.tsx +84 -0
  60. package/src/docs/index.ts +16 -0
  61. package/src/docs/steps.tsx +105 -0
  62. package/src/docs/table-of-contents.tsx +174 -0
  63. package/src/examples/guide-markdown.ts +129 -0
  64. package/src/examples/index.ts +1153 -0
  65. package/src/global.d.ts +1 -0
  66. package/src/hooks/use-copy-to-clipboard.ts +36 -0
  67. package/src/hooks/use-mobile.ts +19 -0
  68. package/src/lib/display-name.ts +15 -0
  69. package/src/lib/example-input.ts +130 -0
  70. package/src/lib/plans.ts +44 -0
  71. package/src/lib/safe-content.ts +137 -0
  72. package/src/lib/schema.ts +320 -0
  73. package/src/lib/storage.ts +108 -0
  74. package/src/markdown/docs-markdown-renderer.tsx +518 -0
  75. package/src/markdown/helpers.ts +95 -0
  76. package/src/markdown/index.ts +24 -0
  77. package/src/markdown/markdown-renderer.tsx +232 -0
  78. package/src/markdown/youtube-embed.tsx +43 -0
  79. package/src/og/image.ts +312 -0
  80. package/src/og/index.tsx +430 -0
  81. package/src/og/query.ts +26 -0
  82. package/src/shadcn/accordion.tsx +71 -0
  83. package/src/shadcn/alert-dialog.tsx +187 -0
  84. package/src/shadcn/alert.tsx +75 -0
  85. package/src/shadcn/aspect-ratio.tsx +22 -0
  86. package/src/shadcn/attachment.tsx +207 -0
  87. package/src/shadcn/avatar.tsx +106 -0
  88. package/src/shadcn/badge.tsx +51 -0
  89. package/src/shadcn/breadcrumb.tsx +124 -0
  90. package/src/shadcn/bubble.tsx +127 -0
  91. package/src/shadcn/button-group.tsx +87 -0
  92. package/src/shadcn/button.tsx +57 -0
  93. package/src/shadcn/calendar.tsx +219 -0
  94. package/src/shadcn/card.tsx +102 -0
  95. package/src/shadcn/carousel.tsx +242 -0
  96. package/src/shadcn/chart.tsx +372 -0
  97. package/src/shadcn/checkbox.tsx +28 -0
  98. package/src/shadcn/collapsible.tsx +19 -0
  99. package/src/shadcn/combobox.tsx +297 -0
  100. package/src/shadcn/command.tsx +194 -0
  101. package/src/shadcn/context-menu.tsx +270 -0
  102. package/src/shadcn/dialog.tsx +158 -0
  103. package/src/shadcn/direction.tsx +4 -0
  104. package/src/shadcn/drawer.tsx +227 -0
  105. package/src/shadcn/dropdown-menu.tsx +265 -0
  106. package/src/shadcn/empty.tsx +103 -0
  107. package/src/shadcn/field.tsx +238 -0
  108. package/src/shadcn/form.tsx +173 -0
  109. package/src/shadcn/hover-card.tsx +50 -0
  110. package/src/shadcn/index.ts +53 -0
  111. package/src/shadcn/input-group.tsx +156 -0
  112. package/src/shadcn/input-otp.tsx +84 -0
  113. package/src/shadcn/input.tsx +19 -0
  114. package/src/shadcn/item.tsx +201 -0
  115. package/src/shadcn/kbd.tsx +26 -0
  116. package/src/shadcn/label.tsx +19 -0
  117. package/src/shadcn/marker.tsx +70 -0
  118. package/src/shadcn/menubar.tsx +278 -0
  119. package/src/shadcn/message-scroller.tsx +131 -0
  120. package/src/shadcn/message.tsx +91 -0
  121. package/src/shadcn/native-select.tsx +60 -0
  122. package/src/shadcn/navigation-menu.tsx +167 -0
  123. package/src/shadcn/pagination.tsx +130 -0
  124. package/src/shadcn/popover.tsx +87 -0
  125. package/src/shadcn/progress.tsx +82 -0
  126. package/src/shadcn/radio-group.tsx +35 -0
  127. package/src/shadcn/resizable.tsx +49 -0
  128. package/src/shadcn/scroll-area.tsx +52 -0
  129. package/src/shadcn/select.tsx +200 -0
  130. package/src/shadcn/separator.tsx +22 -0
  131. package/src/shadcn/sheet.tsx +136 -0
  132. package/src/shadcn/sidebar.tsx +721 -0
  133. package/src/shadcn/skeleton.tsx +13 -0
  134. package/src/shadcn/slider.tsx +51 -0
  135. package/src/shadcn/sonner.tsx +49 -0
  136. package/src/shadcn/spinner.tsx +10 -0
  137. package/src/shadcn/switch.tsx +29 -0
  138. package/src/shadcn/table.tsx +115 -0
  139. package/src/shadcn/tabs.tsx +79 -0
  140. package/src/shadcn/textarea.tsx +17 -0
  141. package/src/shadcn/toggle-group.tsx +89 -0
  142. package/src/shadcn/toggle.tsx +44 -0
  143. package/src/shadcn/tooltip.tsx +63 -0
  144. package/src/theme/config.ts +48 -0
  145. package/src/theme/css/base.css +92 -0
  146. package/src/theme/css/bitboy.css +75 -0
  147. package/src/theme/css/dark.css +47 -0
  148. package/src/theme/css/designer.css +122 -0
  149. package/src/theme/css/fonts.css +33 -0
  150. package/src/theme/css/globals.css +14 -0
  151. package/src/theme/css/hackintosh.css +57 -0
  152. package/src/theme/css/llama.css +103 -0
  153. package/src/theme/css/safelist.css +39 -0
  154. package/src/theme/css/shadcn.css +77 -0
  155. package/src/theme/css/theme.css +216 -0
  156. package/src/theme/embed-theme.ts +146 -0
  157. package/src/theme/fonts/FiraCode-VF.woff2 +0 -0
  158. package/src/theme/fonts/Hack-Regular.woff2 +0 -0
  159. package/src/theme/fonts/LICENSE-FiraCode.txt +93 -0
  160. package/src/theme/fonts/LICENSE-Hack.txt +45 -0
  161. package/src/theme/fonts/LICENSE-MonaSans.txt +93 -0
  162. package/src/theme/fonts/Mona-Sans.woff2 +0 -0
  163. package/src/theme/index.ts +15 -0
  164. package/src/utils/cn.ts +3 -0
  165. 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 }