@stampui/blocks 1.1.0 → 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.
Files changed (56) hide show
  1. package/dist/manifests.js +917 -170
  2. package/package.json +15 -10
  3. package/src/components/blocks/animated-counter.tsx +70 -0
  4. package/src/components/blocks/changelog-feed.tsx +3 -3
  5. package/src/components/blocks/gradient-text.tsx +39 -0
  6. package/src/components/blocks/grid-wave.tsx +40 -0
  7. package/src/components/blocks/loading-card.tsx +48 -0
  8. package/src/components/blocks/loading-dots.tsx +68 -0
  9. package/src/components/blocks/orbit-trail.tsx +30 -0
  10. package/src/components/blocks/progress-ring.tsx +72 -0
  11. package/src/components/blocks/registry-card.tsx +9 -10
  12. package/src/components/blocks/signal-arc.tsx +32 -0
  13. package/src/components/blocks/typewriter-text.tsx +62 -0
  14. package/src/components/blocks/waitlist-section.tsx +1 -1
  15. package/src/components/core/alert-dialog.tsx +2 -2
  16. package/src/components/core/avatar.tsx +8 -4
  17. package/src/components/core/button.tsx +1 -1
  18. package/src/components/core/checkbox.tsx +1 -1
  19. package/src/components/core/combobox.tsx +1 -1
  20. package/src/components/core/command.tsx +7 -4
  21. package/src/components/core/date-picker.tsx +1 -1
  22. package/src/components/core/dialog.tsx +1 -1
  23. package/src/components/core/drawer.tsx +1 -1
  24. package/src/components/core/input.tsx +2 -0
  25. package/src/components/core/label.tsx +1 -1
  26. package/src/components/core/multi-select.tsx +1 -1
  27. package/src/components/core/native-select.tsx +1 -1
  28. package/src/components/core/password-input.tsx +3 -0
  29. package/src/components/core/radio-group.tsx +1 -1
  30. package/src/components/core/resizable.tsx +1 -1
  31. package/src/components/core/select.tsx +1 -1
  32. package/src/components/core/sheet.tsx +1 -1
  33. package/src/components/core/slider.tsx +1 -1
  34. package/src/components/core/status-pulse.tsx +6 -0
  35. package/src/components/core/switch.tsx +1 -1
  36. package/src/components/core/table.tsx +7 -2
  37. package/src/components/core/tabs.tsx +1 -1
  38. package/src/components/core/toggle.tsx +1 -1
  39. package/src/components/core/typing-indicator.tsx +41 -27
  40. package/src/manifests.ts +932 -183
  41. package/src/components/blocks/ai-chat-shell.tsx +0 -97
  42. package/src/components/blocks/auth-panel.tsx +0 -203
  43. package/src/components/blocks/dashboard-shell.tsx +0 -135
  44. package/src/components/blocks/notification-center.tsx +0 -185
  45. package/src/components/blocks/onboarding-flow.tsx +0 -230
  46. package/src/components/blocks/project-command-center.tsx +0 -188
  47. package/src/components/blocks/prompt-input.tsx +0 -81
  48. package/src/components/blocks/settings-layout.tsx +0 -178
  49. package/src/components/blocks/token-stream.tsx +0 -42
  50. package/src/components/core/carousel.tsx +0 -170
  51. package/src/components/core/chart.tsx +0 -377
  52. package/src/components/core/data-table.tsx +0 -173
  53. package/src/components/core/file-upload.tsx +0 -143
  54. package/src/components/core/input-otp.tsx +0 -108
  55. package/src/components/core/stepper.tsx +0 -111
  56. 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": "1.1.0",
4
- "description": "StampUI blocks, registry, and source files",
5
- "files": ["dist", "src"],
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
+ }
@@ -18,9 +18,9 @@ export interface ChangelogFeedProps {
18
18
  }
19
19
 
20
20
  const TYPE_STYLES: Record<ChangelogEntry["type"], string> = {
21
- feature: "bg-emerald-500/10 text-emerald-400 border-emerald-500/20",
22
- fix: "bg-sky-500/10 text-sky-400 border-sky-500/20",
23
- breaking: "bg-red-500/10 text-red-400 border-red-500/20",
21
+ feature: "bg-[#101114] text-[#FAFAFA] border-[#23252A]",
22
+ fix: "bg-[#101114] text-muted-foreground border-[#23252A]",
23
+ breaking: "bg-[#101114] text-muted-foreground border-[#23252A] font-semibold",
24
24
  }
25
25
 
26
26
  const TYPE_LABELS: Record<ChangelogEntry["type"], string> = {
@@ -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
- <Link 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">
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}
@@ -53,29 +52,29 @@ export function RegistryCard({
53
52
 
54
53
  <div className="absolute top-3 right-3 flex gap-1.5">
55
54
  {isGated && (
56
- <Badge variant="pro" className="bg-background/70 backdrop-blur-sm text-[10px] tracking-widest font-mono">
55
+ <Badge variant="pro" className="bg-[#09090B] text-[10px] tracking-widest font-mono">
57
56
  PRO
58
57
  </Badge>
59
58
  )}
60
59
  {status === "new" && !isGated && (
61
- <Badge variant="new" className="bg-background/70 backdrop-blur-sm text-[10px] tracking-widest font-mono">
60
+ <Badge variant="new" className="bg-[#09090B] text-[10px] tracking-widest font-mono">
62
61
  NEW
63
62
  </Badge>
64
63
  )}
65
64
  {promptReady && (
66
- <Badge variant="neutral" className="bg-background/70 backdrop-blur-sm text-[10px] font-mono hidden group-hover:inline-flex">
65
+ <Badge variant="neutral" className="bg-[#09090B] text-[10px] font-mono hidden group-hover:inline-flex">
67
66
  Prompt Ready
68
67
  </Badge>
69
68
  )}
70
69
  </div>
71
- </Link>
70
+ </a>
72
71
 
73
72
  <div className="flex flex-1 flex-col p-4">
74
- <Link href={`/blocks/${slug}`} className="outline-none focus-visible:underline">
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
- </Link>
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
- <Link 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">
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
- </Link>
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
+ }
@@ -56,7 +56,7 @@ export function WaitlistSection({
56
56
  )}
57
57
 
58
58
  <div className="mt-3 flex items-center gap-1.5">
59
- <span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
59
+ <span className="w-1.5 h-1.5 rounded-full bg-[#FAFAFA]/40" />
60
60
  <span className="text-xs text-muted-foreground font-medium">
61
61
  {formattedCount} developers waiting
62
62
  </span>
@@ -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 focus-visible:ring-offset-1 focus-visible:ring-offset-background",
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 focus-visible:ring-offset-1 focus-visible:ring-offset-background",
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 rounded-full",
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 focus-visible:ring-offset-1 focus-visible:ring-offset-background",
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 focus-visible:ring-offset-1 focus-visible:ring-offset-background",
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",