@stampui/blocks 1.1.1 → 2.0.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/dist/manifests.js +917 -170
- package/package.json +15 -10
- package/src/components/blocks/animated-counter.tsx +70 -0
- package/src/components/blocks/gradient-text.tsx +39 -0
- package/src/components/blocks/grid-wave.tsx +40 -0
- package/src/components/blocks/loading-card.tsx +48 -0
- package/src/components/blocks/loading-dots.tsx +68 -0
- package/src/components/blocks/orbit-trail.tsx +30 -0
- package/src/components/blocks/progress-ring.tsx +72 -0
- package/src/components/blocks/registry-card.tsx +6 -7
- package/src/components/blocks/signal-arc.tsx +32 -0
- package/src/components/blocks/typewriter-text.tsx +62 -0
- package/src/components/core/alert-dialog.tsx +2 -2
- package/src/components/core/avatar.tsx +8 -4
- package/src/components/core/button.tsx +1 -1
- package/src/components/core/checkbox.tsx +1 -1
- package/src/components/core/combobox.tsx +1 -1
- package/src/components/core/command.tsx +7 -4
- package/src/components/core/date-picker.tsx +1 -1
- package/src/components/core/dialog.tsx +1 -1
- package/src/components/core/drawer.tsx +1 -1
- package/src/components/core/input.tsx +2 -0
- package/src/components/core/label.tsx +1 -1
- package/src/components/core/multi-select.tsx +1 -1
- package/src/components/core/native-select.tsx +1 -1
- package/src/components/core/password-input.tsx +3 -0
- package/src/components/core/radio-group.tsx +1 -1
- package/src/components/core/resizable.tsx +1 -1
- package/src/components/core/select.tsx +1 -1
- package/src/components/core/sheet.tsx +1 -1
- package/src/components/core/slider.tsx +1 -1
- package/src/components/core/status-pulse.tsx +6 -0
- package/src/components/core/switch.tsx +1 -1
- package/src/components/core/table.tsx +7 -2
- package/src/components/core/tabs.tsx +1 -1
- package/src/components/core/toggle.tsx +1 -1
- package/src/components/core/typing-indicator.tsx +41 -27
- package/src/manifests.ts +932 -183
- package/src/components/blocks/ai-chat-shell.tsx +0 -97
- package/src/components/blocks/auth-panel.tsx +0 -203
- package/src/components/blocks/dashboard-shell.tsx +0 -135
- package/src/components/blocks/notification-center.tsx +0 -185
- package/src/components/blocks/onboarding-flow.tsx +0 -230
- package/src/components/blocks/project-command-center.tsx +0 -188
- package/src/components/blocks/prompt-input.tsx +0 -81
- package/src/components/blocks/settings-layout.tsx +0 -178
- package/src/components/blocks/token-stream.tsx +0 -42
- package/src/components/core/carousel.tsx +0 -170
- package/src/components/core/chart.tsx +0 -377
- package/src/components/core/data-table.tsx +0 -173
- package/src/components/core/file-upload.tsx +0 -143
- package/src/components/core/input-otp.tsx +0 -108
- package/src/components/core/stepper.tsx +0 -111
- package/src/components/core/timeline.tsx +0 -81
package/package.json
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stampui/blocks",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "StampUI blocks, registry, and source files",
|
|
5
|
-
"files": [
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "StampUI blocks, registry, and free block source files. Pro block sources are delivered by the licensed registry API, not this package.",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"src"
|
|
8
|
+
],
|
|
6
9
|
"main": "./dist/index.js",
|
|
7
10
|
"types": "./dist/index.d.ts",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"build": "tsc",
|
|
10
|
-
"dev": "tsc -w",
|
|
11
|
-
"prepublishOnly": "tsc"
|
|
12
|
-
},
|
|
13
11
|
"peerDependencies": {
|
|
14
12
|
"react": "^18.2.0 || ^19.0.0",
|
|
15
|
-
"react-dom": "^18.2.0 || ^19.0.0"
|
|
13
|
+
"react-dom": "^18.2.0 || ^19.0.0",
|
|
14
|
+
"next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
|
|
16
15
|
},
|
|
17
16
|
"dependencies": {
|
|
18
17
|
"lucide-react": "^0.475.0"
|
|
@@ -20,9 +19,15 @@
|
|
|
20
19
|
"devDependencies": {
|
|
21
20
|
"@types/node": "^20.0.0",
|
|
22
21
|
"@types/react": "^19.2.14",
|
|
22
|
+
"next": "16.2.5",
|
|
23
23
|
"typescript": "^5.0.0"
|
|
24
24
|
},
|
|
25
25
|
"publishConfig": {
|
|
26
26
|
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc",
|
|
30
|
+
"dev": "tsc -w",
|
|
31
|
+
"sync:pro": "node ../../scripts/sync-pro-sources.mjs"
|
|
27
32
|
}
|
|
28
|
-
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cx } from "@/lib/cx"
|
|
5
|
+
|
|
6
|
+
interface AnimatedCounterProps {
|
|
7
|
+
from?: number
|
|
8
|
+
to: number
|
|
9
|
+
duration?: number
|
|
10
|
+
decimals?: number
|
|
11
|
+
prefix?: string
|
|
12
|
+
suffix?: string
|
|
13
|
+
className?: string
|
|
14
|
+
onComplete?: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function AnimatedCounter({
|
|
18
|
+
from = 0,
|
|
19
|
+
to,
|
|
20
|
+
duration = 1500,
|
|
21
|
+
decimals = 0,
|
|
22
|
+
prefix = "",
|
|
23
|
+
suffix = "",
|
|
24
|
+
className,
|
|
25
|
+
onComplete,
|
|
26
|
+
}: AnimatedCounterProps) {
|
|
27
|
+
const [value, setValue] = React.useState(from)
|
|
28
|
+
const startTimeRef = React.useRef<number | null>(null)
|
|
29
|
+
const rafRef = React.useRef<number | null>(null)
|
|
30
|
+
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
startTimeRef.current = null
|
|
33
|
+
const startValue = from
|
|
34
|
+
|
|
35
|
+
function easeOutExpo(t: number): number {
|
|
36
|
+
return t === 1 ? 1 : 1 - Math.pow(2, -10 * t)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function step(timestamp: number) {
|
|
40
|
+
if (startTimeRef.current === null) startTimeRef.current = timestamp
|
|
41
|
+
const elapsed = timestamp - startTimeRef.current
|
|
42
|
+
const progress = Math.min(elapsed / duration, 1)
|
|
43
|
+
const eased = easeOutExpo(progress)
|
|
44
|
+
const current = startValue + (to - startValue) * eased
|
|
45
|
+
setValue(current)
|
|
46
|
+
if (progress < 1) {
|
|
47
|
+
rafRef.current = requestAnimationFrame(step)
|
|
48
|
+
} else {
|
|
49
|
+
setValue(to)
|
|
50
|
+
onComplete?.()
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
rafRef.current = requestAnimationFrame(step)
|
|
55
|
+
return () => {
|
|
56
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current)
|
|
57
|
+
}
|
|
58
|
+
}, [from, to, duration, decimals, onComplete])
|
|
59
|
+
|
|
60
|
+
const formatted = value.toLocaleString("en-US", {
|
|
61
|
+
minimumFractionDigits: decimals,
|
|
62
|
+
maximumFractionDigits: decimals,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<span className={cx("tabular-nums", className)}>
|
|
67
|
+
{prefix}{formatted}{suffix}
|
|
68
|
+
</span>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cx } from "@/lib/cx"
|
|
5
|
+
|
|
6
|
+
interface GradientTextProps {
|
|
7
|
+
children: React.ReactNode
|
|
8
|
+
colors?: string[]
|
|
9
|
+
speed?: number
|
|
10
|
+
animate?: boolean
|
|
11
|
+
as?: keyof React.JSX.IntrinsicElements
|
|
12
|
+
className?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function GradientText({
|
|
16
|
+
children,
|
|
17
|
+
colors = ["#FAFAFA", "#71717A", "#FAFAFA"],
|
|
18
|
+
speed = 4,
|
|
19
|
+
animate = true,
|
|
20
|
+
as: Tag = "span" as keyof React.JSX.IntrinsicElements,
|
|
21
|
+
className,
|
|
22
|
+
}: GradientTextProps) {
|
|
23
|
+
const style: React.CSSProperties = {
|
|
24
|
+
backgroundImage: `linear-gradient(90deg, ${colors.join(", ")}, ${colors[0]})`,
|
|
25
|
+
backgroundSize: "300% 100%",
|
|
26
|
+
...(animate ? { animation: `gradient-pan ${speed}s linear infinite` } : {}),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const Component = Tag as React.ElementType
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Component
|
|
33
|
+
className={cx("bg-clip-text text-transparent", className)}
|
|
34
|
+
style={style}
|
|
35
|
+
>
|
|
36
|
+
{children}
|
|
37
|
+
</Component>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cx } from "@/lib/cx"
|
|
5
|
+
|
|
6
|
+
export interface GridWaveProps {
|
|
7
|
+
size?: number
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function GridWave({ size = 64, className }: GridWaveProps) {
|
|
12
|
+
const count = 4
|
|
13
|
+
const spacing = 14
|
|
14
|
+
const r = 3
|
|
15
|
+
const start = (64 - (count - 1) * spacing) / 2
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className={cx("inline-flex items-center justify-center", className)}>
|
|
19
|
+
<svg width={size} height={size} viewBox="0 0 64 64" fill="none">
|
|
20
|
+
{Array.from({ length: count }, (_, row) =>
|
|
21
|
+
Array.from({ length: count }, (_, col) => {
|
|
22
|
+
const delay = ((row + col) * 0.14).toFixed(2)
|
|
23
|
+
return (
|
|
24
|
+
<circle
|
|
25
|
+
key={`${row}-${col}`}
|
|
26
|
+
cx={start + col * spacing}
|
|
27
|
+
cy={start + row * spacing}
|
|
28
|
+
r={r}
|
|
29
|
+
fill="currentColor"
|
|
30
|
+
>
|
|
31
|
+
<animate attributeName="opacity" values="0.12;1;0.12" dur="1.3s" begin={`${delay}s`} repeatCount="indefinite" />
|
|
32
|
+
<animate attributeName="r" values={`${r * 0.6};${r};${r * 0.6}`} dur="1.3s" begin={`${delay}s`} repeatCount="indefinite" />
|
|
33
|
+
</circle>
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
)}
|
|
37
|
+
</svg>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cx } from "@/lib/cx"
|
|
5
|
+
|
|
6
|
+
interface LoadingCardProps {
|
|
7
|
+
showAvatar?: boolean
|
|
8
|
+
lines?: number
|
|
9
|
+
showAction?: boolean
|
|
10
|
+
className?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const shimmerStyle: React.CSSProperties = {
|
|
14
|
+
background: "linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 50%, var(--surface-2) 75%)",
|
|
15
|
+
backgroundSize: "200% 100%",
|
|
16
|
+
animation: "shimmer 1.8s ease-in-out infinite",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function Bone({ className }: { className?: string }) {
|
|
20
|
+
return <div className={cx("rounded-md", className)} style={shimmerStyle} />
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function LoadingCard({
|
|
24
|
+
showAvatar = true,
|
|
25
|
+
lines = 3,
|
|
26
|
+
showAction = false,
|
|
27
|
+
className,
|
|
28
|
+
}: LoadingCardProps) {
|
|
29
|
+
return (
|
|
30
|
+
<div className={cx("rounded-xl border border-border bg-card p-5 space-y-4", className)}>
|
|
31
|
+
{showAvatar && (
|
|
32
|
+
<div className="flex items-center gap-3">
|
|
33
|
+
<Bone className="h-10 w-10 rounded-full shrink-0" />
|
|
34
|
+
<div className="flex-1 space-y-2">
|
|
35
|
+
<Bone className="h-3 w-2/5" />
|
|
36
|
+
<Bone className="h-2.5 w-1/3" />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
40
|
+
<div className="space-y-2">
|
|
41
|
+
{Array.from({ length: lines }).map((_, i) => (
|
|
42
|
+
<Bone key={i} className={cx("h-2.5", i === lines - 1 ? "w-3/4" : "w-full")} />
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
{showAction && <Bone className="h-8 w-20 rounded-lg" />}
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cx } from "@/lib/cx"
|
|
5
|
+
|
|
6
|
+
interface LoadingDotsProps {
|
|
7
|
+
variant?: "bounce" | "pulse" | "bars"
|
|
8
|
+
size?: "sm" | "md" | "lg"
|
|
9
|
+
className?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const dotSizes = {
|
|
13
|
+
sm: "h-1.5 w-1.5",
|
|
14
|
+
md: "h-2 w-2",
|
|
15
|
+
lg: "h-3 w-3",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const barHeights = {
|
|
19
|
+
sm: "h-4",
|
|
20
|
+
md: "h-5",
|
|
21
|
+
lg: "h-7",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function LoadingDots({
|
|
25
|
+
variant = "bounce",
|
|
26
|
+
size = "md",
|
|
27
|
+
className,
|
|
28
|
+
}: LoadingDotsProps) {
|
|
29
|
+
if (variant === "bounce") {
|
|
30
|
+
return (
|
|
31
|
+
<div className={cx("flex items-center gap-1", className)}>
|
|
32
|
+
{[0, 1, 2].map((i) => (
|
|
33
|
+
<div
|
|
34
|
+
key={i}
|
|
35
|
+
className={cx("rounded-full bg-foreground animate-bounce", dotSizes[size])}
|
|
36
|
+
style={{ animationDelay: `${i * 0.15}s`, animationDuration: "0.8s" }}
|
|
37
|
+
/>
|
|
38
|
+
))}
|
|
39
|
+
</div>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (variant === "pulse") {
|
|
44
|
+
return (
|
|
45
|
+
<div className={cx("flex items-center gap-1", className)}>
|
|
46
|
+
{[0, 1, 2].map((i) => (
|
|
47
|
+
<div
|
|
48
|
+
key={i}
|
|
49
|
+
className={cx("rounded-full bg-foreground animate-pulse", dotSizes[size])}
|
|
50
|
+
style={{ animationDelay: `${i * 0.2}s`, animationDuration: "1.2s" }}
|
|
51
|
+
/>
|
|
52
|
+
))}
|
|
53
|
+
</div>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className={cx("flex items-center gap-1", className)}>
|
|
59
|
+
{[0, 1, 2, 3].map((i) => (
|
|
60
|
+
<div
|
|
61
|
+
key={i}
|
|
62
|
+
className={cx("w-1 rounded-sm bg-foreground origin-bottom animate-[bars_1s_ease-in-out_infinite]", barHeights[size])}
|
|
63
|
+
style={{ animationDelay: `${i * 0.12}s` }}
|
|
64
|
+
/>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cx } from "@/lib/cx"
|
|
5
|
+
|
|
6
|
+
export interface OrbitTrailProps {
|
|
7
|
+
size?: number
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function OrbitTrail({ size = 64, className }: OrbitTrailProps) {
|
|
12
|
+
return (
|
|
13
|
+
<div className={cx("inline-flex items-center justify-center", className)}>
|
|
14
|
+
<svg width={size} height={size} viewBox="0 0 64 64" fill="none">
|
|
15
|
+
{/* inner orbit — fast, radius 10 */}
|
|
16
|
+
<circle cx="42" cy="32" r="3.5" fill="currentColor">
|
|
17
|
+
<animateTransform attributeName="transform" type="rotate" from="0 32 32" to="360 32 32" dur="0.85s" repeatCount="indefinite" />
|
|
18
|
+
</circle>
|
|
19
|
+
{/* mid orbit — medium, radius 16 */}
|
|
20
|
+
<circle cx="48" cy="32" r="2.8" fill="currentColor" opacity="0.7">
|
|
21
|
+
<animateTransform attributeName="transform" type="rotate" from="120 32 32" to="480 32 32" dur="1.4s" repeatCount="indefinite" />
|
|
22
|
+
</circle>
|
|
23
|
+
{/* outer orbit — slow, radius 22 */}
|
|
24
|
+
<circle cx="54" cy="32" r="2" fill="currentColor" opacity="0.45">
|
|
25
|
+
<animateTransform attributeName="transform" type="rotate" from="240 32 32" to="600 32 32" dur="2.1s" repeatCount="indefinite" />
|
|
26
|
+
</circle>
|
|
27
|
+
</svg>
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cx } from "@/lib/cx"
|
|
5
|
+
|
|
6
|
+
interface ProgressRingProps {
|
|
7
|
+
value?: number
|
|
8
|
+
max?: number
|
|
9
|
+
size?: number
|
|
10
|
+
strokeWidth?: number
|
|
11
|
+
showValue?: boolean
|
|
12
|
+
label?: string
|
|
13
|
+
className?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ProgressRing({
|
|
17
|
+
value = 0,
|
|
18
|
+
max = 100,
|
|
19
|
+
size = 64,
|
|
20
|
+
strokeWidth = 6,
|
|
21
|
+
showValue = true,
|
|
22
|
+
label,
|
|
23
|
+
className,
|
|
24
|
+
}: ProgressRingProps) {
|
|
25
|
+
const [current, setCurrent] = React.useState(0)
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
const id = setTimeout(() => setCurrent(value), 60)
|
|
28
|
+
return () => clearTimeout(id)
|
|
29
|
+
}, [value])
|
|
30
|
+
|
|
31
|
+
const radius = (size - strokeWidth) / 2
|
|
32
|
+
const circumference = 2 * Math.PI * radius
|
|
33
|
+
const pct = Math.min(Math.max(current / max, 0), 1)
|
|
34
|
+
const offset = circumference - pct * circumference
|
|
35
|
+
const center = size / 2
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className={cx("relative inline-flex items-center justify-center", className)}>
|
|
39
|
+
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
40
|
+
<circle
|
|
41
|
+
cx={center}
|
|
42
|
+
cy={center}
|
|
43
|
+
r={radius}
|
|
44
|
+
fill="none"
|
|
45
|
+
stroke="var(--surface-2)"
|
|
46
|
+
strokeWidth={strokeWidth}
|
|
47
|
+
/>
|
|
48
|
+
<circle
|
|
49
|
+
cx={center}
|
|
50
|
+
cy={center}
|
|
51
|
+
r={radius}
|
|
52
|
+
fill="none"
|
|
53
|
+
stroke="var(--foreground)"
|
|
54
|
+
strokeWidth={strokeWidth}
|
|
55
|
+
strokeDasharray={circumference}
|
|
56
|
+
strokeDashoffset={offset}
|
|
57
|
+
strokeLinecap="round"
|
|
58
|
+
transform={`rotate(-90 ${center} ${center})`}
|
|
59
|
+
style={{ transition: "stroke-dashoffset 600ms ease-out" }}
|
|
60
|
+
/>
|
|
61
|
+
</svg>
|
|
62
|
+
{showValue && (
|
|
63
|
+
<span className="absolute text-xs font-medium tabular-nums">
|
|
64
|
+
{Math.round(pct * 100)}%
|
|
65
|
+
</span>
|
|
66
|
+
)}
|
|
67
|
+
{label && !showValue && (
|
|
68
|
+
<span className="absolute text-xs font-medium">{label}</span>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
|
-
import Link from "next/link"
|
|
3
2
|
import { Terminal, ArrowRight } from "lucide-react"
|
|
4
3
|
import { Badge } from "@/components/core/badge"
|
|
5
4
|
import { cx } from "@/lib/cx"
|
|
@@ -40,7 +39,7 @@ export function RegistryCard({
|
|
|
40
39
|
)}
|
|
41
40
|
{...props}
|
|
42
41
|
>
|
|
43
|
-
<
|
|
42
|
+
<a href={`/blocks/${slug}`} className="block relative aspect-video w-full overflow-hidden border-b border-border bg-surface-2 outline-none focus-visible:ring-2 focus-visible:ring-border-strong">
|
|
44
43
|
{previewPath ? (
|
|
45
44
|
<img
|
|
46
45
|
src={previewPath}
|
|
@@ -68,14 +67,14 @@ export function RegistryCard({
|
|
|
68
67
|
</Badge>
|
|
69
68
|
)}
|
|
70
69
|
</div>
|
|
71
|
-
|
|
70
|
+
</a>
|
|
72
71
|
|
|
73
72
|
<div className="flex flex-1 flex-col p-4">
|
|
74
|
-
<
|
|
73
|
+
<a href={`/blocks/${slug}`} className="outline-none focus-visible:underline">
|
|
75
74
|
<h3 className="font-semibold text-sm leading-tight tracking-tight text-foreground group-hover:text-primary transition-colors">
|
|
76
75
|
{title}
|
|
77
76
|
</h3>
|
|
78
|
-
</
|
|
77
|
+
</a>
|
|
79
78
|
<p className="text-xs text-muted-foreground line-clamp-1 mt-1">
|
|
80
79
|
{description}
|
|
81
80
|
</p>
|
|
@@ -94,9 +93,9 @@ export function RegistryCard({
|
|
|
94
93
|
</code>
|
|
95
94
|
<CopyButton value={`pnpm dlx stampui add ${slug}`} className="h-5 w-5 opacity-0 group-hover/cmd:opacity-100 transition-opacity flex-shrink-0" />
|
|
96
95
|
</div>
|
|
97
|
-
<
|
|
96
|
+
<a href={`/blocks/${slug}`} className="text-xs font-medium text-foreground flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-all duration-200 ease-out translate-x-1 group-hover:translate-x-0 flex-shrink-0">
|
|
98
97
|
View <ArrowRight className="h-3 w-3" />
|
|
99
|
-
</
|
|
98
|
+
</a>
|
|
100
99
|
</div>
|
|
101
100
|
</div>
|
|
102
101
|
</div>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cx } from "@/lib/cx"
|
|
5
|
+
|
|
6
|
+
export interface SignalArcProps {
|
|
7
|
+
size?: number
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function SignalArc({ size = 64, className }: SignalArcProps) {
|
|
12
|
+
const rings = [
|
|
13
|
+
{ r: 6, dur: "1.6s", begin: "0s" },
|
|
14
|
+
{ r: 6, dur: "1.6s", begin: "0.4s" },
|
|
15
|
+
{ r: 6, dur: "1.6s", begin: "0.8s" },
|
|
16
|
+
{ r: 6, dur: "1.6s", begin: "1.2s" },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className={cx("inline-flex items-center justify-center", className)}>
|
|
21
|
+
<svg width={size} height={size} viewBox="0 0 64 64" fill="none">
|
|
22
|
+
<circle cx="32" cy="32" r="3" fill="currentColor" />
|
|
23
|
+
{rings.map((ring, i) => (
|
|
24
|
+
<circle key={i} cx="32" cy="32" r={ring.r} stroke="currentColor" strokeWidth="1.5" fill="none">
|
|
25
|
+
<animate attributeName="r" values={`${ring.r};28`} dur={ring.dur} begin={ring.begin} repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.8 1" />
|
|
26
|
+
<animate attributeName="opacity" values="0.8;0" dur={ring.dur} begin={ring.begin} repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.8 1" />
|
|
27
|
+
</circle>
|
|
28
|
+
))}
|
|
29
|
+
</svg>
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cx } from "@/lib/cx"
|
|
5
|
+
|
|
6
|
+
interface TypewriterTextProps {
|
|
7
|
+
words: string[]
|
|
8
|
+
typingSpeed?: number
|
|
9
|
+
deletingSpeed?: number
|
|
10
|
+
pauseDuration?: number
|
|
11
|
+
className?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type Phase = "typing" | "pausing" | "deleting"
|
|
15
|
+
|
|
16
|
+
export function TypewriterText({
|
|
17
|
+
words,
|
|
18
|
+
typingSpeed = 80,
|
|
19
|
+
deletingSpeed = 40,
|
|
20
|
+
pauseDuration = 1800,
|
|
21
|
+
className,
|
|
22
|
+
}: TypewriterTextProps) {
|
|
23
|
+
const [wordIndex, setWordIndex] = React.useState(0)
|
|
24
|
+
const [displayed, setDisplayed] = React.useState("")
|
|
25
|
+
const [phase, setPhase] = React.useState<Phase>("typing")
|
|
26
|
+
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
const word = words[wordIndex]
|
|
29
|
+
|
|
30
|
+
if (phase === "typing") {
|
|
31
|
+
if (displayed.length < word.length) {
|
|
32
|
+
const t = setTimeout(() => setDisplayed(word.slice(0, displayed.length + 1)), typingSpeed)
|
|
33
|
+
return () => clearTimeout(t)
|
|
34
|
+
} else {
|
|
35
|
+
const t = setTimeout(() => setPhase("pausing"), typingSpeed)
|
|
36
|
+
return () => clearTimeout(t)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (phase === "pausing") {
|
|
41
|
+
const t = setTimeout(() => setPhase("deleting"), pauseDuration)
|
|
42
|
+
return () => clearTimeout(t)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (phase === "deleting") {
|
|
46
|
+
if (displayed.length > 0) {
|
|
47
|
+
const t = setTimeout(() => setDisplayed(displayed.slice(0, -1)), deletingSpeed)
|
|
48
|
+
return () => clearTimeout(t)
|
|
49
|
+
} else {
|
|
50
|
+
setWordIndex((i) => (i + 1) % words.length)
|
|
51
|
+
setPhase("typing")
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}, [phase, displayed, wordIndex, words, typingSpeed, deletingSpeed, pauseDuration])
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<span className={cx(className)}>
|
|
58
|
+
{displayed}
|
|
59
|
+
<span className="inline-block w-0.5 h-[1em] bg-current animate-[blink_1s_step-end_infinite] align-text-bottom ml-0.5 rounded-sm" />
|
|
60
|
+
</span>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -88,7 +88,7 @@ export const AlertDialogAction = React.forwardRef<
|
|
|
88
88
|
ref={ref}
|
|
89
89
|
className={cx(
|
|
90
90
|
"inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium transition-colors outline-none",
|
|
91
|
-
"bg-foreground text-background hover:bg-foreground/90 focus-visible:ring-1 focus-visible:ring-border-strong
|
|
91
|
+
"bg-foreground text-background hover:bg-foreground/90 focus-visible:ring-1 focus-visible:ring-border-strong ",
|
|
92
92
|
className
|
|
93
93
|
)}
|
|
94
94
|
{...props}
|
|
@@ -104,7 +104,7 @@ export const AlertDialogCancel = React.forwardRef<
|
|
|
104
104
|
ref={ref}
|
|
105
105
|
className={cx(
|
|
106
106
|
"inline-flex items-center justify-center rounded-lg border border-border px-4 py-2 text-sm font-medium transition-colors outline-none",
|
|
107
|
-
"bg-transparent text-foreground hover:bg-surface-2 focus-visible:ring-1 focus-visible:ring-border-strong
|
|
107
|
+
"bg-transparent text-foreground hover:bg-surface-2 focus-visible:ring-1 focus-visible:ring-border-strong ",
|
|
108
108
|
className
|
|
109
109
|
)}
|
|
110
110
|
{...props}
|
|
@@ -6,7 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|
|
6
6
|
import { cx } from "@/lib/cx"
|
|
7
7
|
|
|
8
8
|
const avatarStyles = cva(
|
|
9
|
-
"relative flex shrink-0 overflow-hidden
|
|
9
|
+
"relative flex shrink-0 overflow-hidden",
|
|
10
10
|
{
|
|
11
11
|
variants: {
|
|
12
12
|
size: {
|
|
@@ -16,8 +16,12 @@ const avatarStyles = cva(
|
|
|
16
16
|
lg: "h-12 w-12 text-base",
|
|
17
17
|
xl: "h-16 w-16 text-lg",
|
|
18
18
|
},
|
|
19
|
+
shape: {
|
|
20
|
+
circle: "rounded-full",
|
|
21
|
+
square: "rounded-md",
|
|
22
|
+
},
|
|
19
23
|
},
|
|
20
|
-
defaultVariants: { size: "md" },
|
|
24
|
+
defaultVariants: { size: "md", shape: "circle" },
|
|
21
25
|
}
|
|
22
26
|
)
|
|
23
27
|
|
|
@@ -28,10 +32,10 @@ export interface AvatarProps
|
|
|
28
32
|
const Avatar = React.forwardRef<
|
|
29
33
|
React.ElementRef<typeof AvatarPrimitive.Root>,
|
|
30
34
|
AvatarProps
|
|
31
|
-
>(({ className, size, ...props }, ref) => (
|
|
35
|
+
>(({ className, size, shape, ...props }, ref) => (
|
|
32
36
|
<AvatarPrimitive.Root
|
|
33
37
|
ref={ref}
|
|
34
|
-
className={cx(avatarStyles({ size }), className)}
|
|
38
|
+
className={cx(avatarStyles({ size, shape }), className)}
|
|
35
39
|
{...props}
|
|
36
40
|
/>
|
|
37
41
|
))
|
|
@@ -32,7 +32,7 @@ const buttonVariants = cva(
|
|
|
32
32
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap",
|
|
33
33
|
"rounded-lg text-sm font-medium",
|
|
34
34
|
"transition-all duration-[150ms] ease-out",
|
|
35
|
-
"outline-none focus-visible:ring-1 focus-visible:ring-border-strong
|
|
35
|
+
"outline-none focus-visible:ring-1 focus-visible:ring-border-strong ",
|
|
36
36
|
"disabled:pointer-events-none disabled:opacity-40",
|
|
37
37
|
"[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
38
38
|
],
|
|
@@ -8,7 +8,7 @@ import { cx } from "@/lib/cx"
|
|
|
8
8
|
const checkboxStyles = cva(
|
|
9
9
|
[
|
|
10
10
|
"peer shrink-0 rounded border transition-all outline-none",
|
|
11
|
-
"focus-visible:ring-1 focus-visible:ring-border-strong
|
|
11
|
+
"focus-visible:ring-1 focus-visible:ring-border-strong ",
|
|
12
12
|
"disabled:cursor-not-allowed disabled:opacity-40",
|
|
13
13
|
"data-[state=checked]:border-foreground data-[state=checked]:bg-foreground",
|
|
14
14
|
"data-[state=indeterminate]:border-foreground data-[state=indeterminate]:bg-foreground",
|
|
@@ -57,7 +57,7 @@ export function Combobox({
|
|
|
57
57
|
className={cx(
|
|
58
58
|
"flex h-9 w-full items-center justify-between gap-2 rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm outline-none",
|
|
59
59
|
"hover:border-border-strong transition-colors text-left",
|
|
60
|
-
"focus-visible:ring-1 focus-visible:ring-border-strong
|
|
60
|
+
"focus-visible:ring-1 focus-visible:ring-border-strong ",
|
|
61
61
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
62
62
|
!selected && "text-muted-foreground",
|
|
63
63
|
className
|
|
@@ -11,12 +11,15 @@ export interface CommandProps {
|
|
|
11
11
|
open?: boolean
|
|
12
12
|
onOpenChange?: (open: boolean) => void
|
|
13
13
|
children: React.ReactNode
|
|
14
|
+
className?: string
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export function Command({ open, onOpenChange, children }: CommandProps) {
|
|
17
|
+
export function Command({ open, onOpenChange, children, className }: CommandProps) {
|
|
17
18
|
return (
|
|
18
19
|
<RadixDialog.Root open={open} onOpenChange={onOpenChange}>
|
|
19
|
-
{
|
|
20
|
+
<div className={className}>
|
|
21
|
+
{children}
|
|
22
|
+
</div>
|
|
20
23
|
</RadixDialog.Root>
|
|
21
24
|
)
|
|
22
25
|
}
|
|
@@ -88,9 +91,9 @@ export function CommandList({ children, className }: { children: React.ReactNode
|
|
|
88
91
|
|
|
89
92
|
// ── Empty ─────────────────────────────────────────────────────────────────────
|
|
90
93
|
|
|
91
|
-
export function CommandEmpty({ children }: { children?: React.ReactNode }) {
|
|
94
|
+
export function CommandEmpty({ children, className }: { children?: React.ReactNode; className?: string }) {
|
|
92
95
|
return (
|
|
93
|
-
<div className="px-4 py-8 text-center text-sm text-muted-foreground">
|
|
96
|
+
<div className={cx("px-4 py-8 text-center text-sm text-muted-foreground", className)}>
|
|
94
97
|
{children ?? "No results found."}
|
|
95
98
|
</div>
|
|
96
99
|
)
|