@stampui/blocks 1.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/components/ai-chat-shell.d.ts +1 -0
- package/dist/components/ai-chat-shell.js +23 -0
- package/dist/components/prompt-input.d.ts +5 -0
- package/dist/components/prompt-input.js +47 -0
- package/dist/components/registry-card.d.ts +6 -0
- package/dist/components/registry-card.js +15 -0
- package/dist/components/registry-explorer.d.ts +8 -0
- package/dist/components/registry-explorer.js +38 -0
- package/dist/components/token-stream.d.ts +7 -0
- package/dist/components/token-stream.js +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +23 -0
- package/dist/manifests.d.ts +3 -0
- package/dist/manifests.js +1666 -0
- package/dist/types.d.ts +44 -0
- package/dist/types.js +2 -0
- package/package.json +28 -0
- package/src/components/blocks/ai-chat-shell.tsx +97 -0
- package/src/components/blocks/auth-panel.tsx +203 -0
- package/src/components/blocks/feature-grid.tsx +122 -0
- package/src/components/blocks/hero-section.tsx +73 -0
- package/src/components/blocks/notification-center.tsx +185 -0
- package/src/components/blocks/onboarding-flow.tsx +230 -0
- package/src/components/blocks/pricing-section.tsx +135 -0
- package/src/components/blocks/project-command-center.tsx +188 -0
- package/src/components/blocks/prompt-input.tsx +81 -0
- package/src/components/blocks/registry-card.tsx +104 -0
- package/src/components/blocks/registry-explorer.tsx +78 -0
- package/src/components/blocks/settings-layout.tsx +178 -0
- package/src/components/blocks/stats-strip.tsx +100 -0
- package/src/components/blocks/token-stream.tsx +42 -0
- package/src/components/blocks/usage-card.tsx +116 -0
- package/src/components/core/accordion.tsx +58 -0
- package/src/components/core/alert-dialog.tsx +113 -0
- package/src/components/core/alert.tsx +48 -0
- package/src/components/core/animated-number.tsx +77 -0
- package/src/components/core/aspect-ratio.tsx +20 -0
- package/src/components/core/avatar-stack.tsx +61 -0
- package/src/components/core/avatar.tsx +90 -0
- package/src/components/core/badge.tsx +39 -0
- package/src/components/core/breadcrumb.tsx +63 -0
- package/src/components/core/button-group.tsx +37 -0
- package/src/components/core/button.tsx +110 -0
- package/src/components/core/calendar.tsx +143 -0
- package/src/components/core/card.tsx +60 -0
- package/src/components/core/carousel.tsx +170 -0
- package/src/components/core/chart.tsx +377 -0
- package/src/components/core/checkbox.tsx +64 -0
- package/src/components/core/collapsible.tsx +30 -0
- package/src/components/core/combobox.tsx +114 -0
- package/src/components/core/command-box.tsx +22 -0
- package/src/components/core/command.tsx +165 -0
- package/src/components/core/confirm-action.tsx +94 -0
- package/src/components/core/context-menu.tsx +139 -0
- package/src/components/core/copy-button.tsx +41 -0
- package/src/components/core/data-table.tsx +173 -0
- package/src/components/core/date-picker.tsx +73 -0
- package/src/components/core/dialog.tsx +83 -0
- package/src/components/core/drawer.tsx +87 -0
- package/src/components/core/dropdown-menu.tsx +147 -0
- package/src/components/core/empty.tsx +34 -0
- package/src/components/core/field.tsx +39 -0
- package/src/components/core/file-upload.tsx +143 -0
- package/src/components/core/hover-card.tsx +31 -0
- package/src/components/core/inline-edit.tsx +104 -0
- package/src/components/core/input-group.tsx +47 -0
- package/src/components/core/input-otp.tsx +108 -0
- package/src/components/core/input.tsx +37 -0
- package/src/components/core/kbd.tsx +47 -0
- package/src/components/core/label.tsx +28 -0
- package/src/components/core/marquee.tsx +61 -0
- package/src/components/core/menubar.tsx +120 -0
- package/src/components/core/multi-select.tsx +145 -0
- package/src/components/core/native-select.tsx +27 -0
- package/src/components/core/navigation-menu.tsx +130 -0
- package/src/components/core/number-stepper.tsx +80 -0
- package/src/components/core/pagination.tsx +80 -0
- package/src/components/core/password-input.tsx +90 -0
- package/src/components/core/popover.tsx +34 -0
- package/src/components/core/progress.tsx +63 -0
- package/src/components/core/radio-group.tsx +77 -0
- package/src/components/core/resizable.tsx +250 -0
- package/src/components/core/scroll-area.tsx +38 -0
- package/src/components/core/select.tsx +128 -0
- package/src/components/core/separator.tsx +47 -0
- package/src/components/core/sheet.tsx +118 -0
- package/src/components/core/sidebar.tsx +129 -0
- package/src/components/core/skeleton.tsx +32 -0
- package/src/components/core/slider.tsx +97 -0
- package/src/components/core/sonner.tsx +29 -0
- package/src/components/core/spinner.tsx +60 -0
- package/src/components/core/status-pulse.tsx +67 -0
- package/src/components/core/stepper.tsx +111 -0
- package/src/components/core/switch.tsx +72 -0
- package/src/components/core/table.tsx +104 -0
- package/src/components/core/tabs.tsx +55 -0
- package/src/components/core/tag-input.tsx +93 -0
- package/src/components/core/textarea.tsx +44 -0
- package/src/components/core/timeline.tsx +81 -0
- package/src/components/core/toggle-group.tsx +56 -0
- package/src/components/core/toggle.tsx +66 -0
- package/src/components/core/tooltip.tsx +31 -0
- package/src/components/core/typing-indicator.tsx +51 -0
- package/src/index.ts +8 -0
- package/src/manifests.ts +1682 -0
- package/src/types.ts +58 -0
- package/src/ui.ts +13 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tier of a block. Determines CLI install behavior and registry visibility.
|
|
3
|
+
* - free: Publicly installable by anyone.
|
|
4
|
+
* - pro: Requires a valid StampUI license key.
|
|
5
|
+
* - new: Recently published free block.
|
|
6
|
+
* - locked: Explicitly gated (e.g. not yet released).
|
|
7
|
+
*/
|
|
8
|
+
export type BlockStatus = "free" | "pro" | "new" | "locked";
|
|
9
|
+
/**
|
|
10
|
+
* A single file reference included in a block install.
|
|
11
|
+
*/
|
|
12
|
+
export interface BlockFile {
|
|
13
|
+
path: string;
|
|
14
|
+
type: "block" | "util" | "hook" | "registry:ui";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The canonical manifest for a StampUI registry block.
|
|
18
|
+
* This is the single source of truth consumed by the CLI, web registry, and preview system.
|
|
19
|
+
*/
|
|
20
|
+
export interface BlockManifest {
|
|
21
|
+
slug: string;
|
|
22
|
+
title: string;
|
|
23
|
+
description: string;
|
|
24
|
+
category: string;
|
|
25
|
+
tags: string[];
|
|
26
|
+
version: string;
|
|
27
|
+
updatedAt: string;
|
|
28
|
+
minCliVersion?: string;
|
|
29
|
+
changelog?: string[];
|
|
30
|
+
status: BlockStatus;
|
|
31
|
+
licenseRequired?: boolean;
|
|
32
|
+
difficulty: "beginner" | "intermediate" | "advanced";
|
|
33
|
+
frameworks: string[];
|
|
34
|
+
dependencies: string[];
|
|
35
|
+
files: BlockFile[];
|
|
36
|
+
tokens: string[];
|
|
37
|
+
supportsDarkMode: boolean;
|
|
38
|
+
supportsLightMode: boolean;
|
|
39
|
+
promptReady: boolean;
|
|
40
|
+
/** Relative path to a local preview asset, e.g. /previews/token-stream.svg */
|
|
41
|
+
previewPath?: string;
|
|
42
|
+
/** CLI and Web specific logic, populated at runtime if needed */
|
|
43
|
+
source?: string;
|
|
44
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stampui/blocks",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "StampUI blocks, registry, and source files",
|
|
5
|
+
"files": ["dist", "src"],
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"dev": "tsc -w",
|
|
11
|
+
"prepublishOnly": "tsc"
|
|
12
|
+
},
|
|
13
|
+
"peerDependencies": {
|
|
14
|
+
"react": "^18.2.0 || ^19.0.0",
|
|
15
|
+
"react-dom": "^18.2.0 || ^19.0.0"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"lucide-react": "^0.475.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^20.0.0",
|
|
22
|
+
"@types/react": "^19.2.14",
|
|
23
|
+
"typescript": "^5.0.0"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Menu, Plus, MessageSquare, Settings } from "lucide-react"
|
|
5
|
+
import { cx } from "@/lib/cx"
|
|
6
|
+
import { Button } from "@/components/core/button"
|
|
7
|
+
import { PromptInput } from "@/components/blocks/prompt-input"
|
|
8
|
+
import { TokenStream } from "@/components/blocks/token-stream"
|
|
9
|
+
|
|
10
|
+
export function AIChatShell() {
|
|
11
|
+
const [sidebarOpen, setSidebarOpen] = React.useState(true)
|
|
12
|
+
const [messages, setMessages] = React.useState([
|
|
13
|
+
{ role: "assistant", content: "Hello! I am ready to help you build your next app. What would you like to create today?" }
|
|
14
|
+
])
|
|
15
|
+
|
|
16
|
+
const handleSubmit = (val: string) => {
|
|
17
|
+
setMessages([...messages, { role: "user", content: val }])
|
|
18
|
+
setTimeout(() => {
|
|
19
|
+
setMessages(prev => [...prev, { role: "assistant", content: "I can certainly help with that! Let's break down the requirements..." }])
|
|
20
|
+
}, 600)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="flex h-[600px] w-full overflow-hidden rounded-xl border border-border bg-background shadow-xl">
|
|
25
|
+
{/* Sidebar */}
|
|
26
|
+
<div
|
|
27
|
+
className={cx(
|
|
28
|
+
"flex flex-col border-r border-border bg-muted/20 transition-all duration-300",
|
|
29
|
+
sidebarOpen ? "w-64" : "w-0 overflow-hidden border-r-0"
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
32
|
+
<div className="flex h-14 items-center justify-between px-4 border-b border-border">
|
|
33
|
+
<span className="font-semibold text-sm">Chats</span>
|
|
34
|
+
<Button variant="ghost" size="icon" className="h-8 w-8">
|
|
35
|
+
<Plus className="h-4 w-4" />
|
|
36
|
+
</Button>
|
|
37
|
+
</div>
|
|
38
|
+
<div className="flex-1 overflow-y-auto p-2 space-y-1">
|
|
39
|
+
<Button variant="ghost" className="w-full justify-start font-normal text-sm text-muted-foreground hover:text-foreground">
|
|
40
|
+
<MessageSquare className="mr-2 h-4 w-4" /> E-commerce Data Model
|
|
41
|
+
</Button>
|
|
42
|
+
<Button variant="ghost" className="w-full justify-start font-normal text-sm text-muted-foreground hover:text-foreground">
|
|
43
|
+
<MessageSquare className="mr-2 h-4 w-4" /> Fix Next.js Hydration Error
|
|
44
|
+
</Button>
|
|
45
|
+
</div>
|
|
46
|
+
<div className="p-2 border-t border-border">
|
|
47
|
+
<Button variant="ghost" className="w-full justify-start text-sm">
|
|
48
|
+
<Settings className="mr-2 h-4 w-4" /> Settings
|
|
49
|
+
</Button>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
{/* Main Chat Area */}
|
|
54
|
+
<div className="flex flex-1 flex-col bg-background">
|
|
55
|
+
<div className="flex h-14 items-center border-b border-border px-4">
|
|
56
|
+
<Button
|
|
57
|
+
variant="ghost"
|
|
58
|
+
size="icon"
|
|
59
|
+
className="h-8 w-8 mr-2 md:hidden"
|
|
60
|
+
onClick={() => setSidebarOpen(!sidebarOpen)}
|
|
61
|
+
>
|
|
62
|
+
<Menu className="h-4 w-4" />
|
|
63
|
+
</Button>
|
|
64
|
+
<span className="font-medium text-sm">New Chat</span>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div className="flex-1 overflow-y-auto p-4 md:p-6 space-y-6">
|
|
68
|
+
{messages.map((m, i) => (
|
|
69
|
+
<div key={i} className={cx("flex w-full", m.role === "user" ? "justify-end" : "justify-start")}>
|
|
70
|
+
<div className={cx(
|
|
71
|
+
"max-w-[80%] rounded-2xl px-5 py-3 text-sm",
|
|
72
|
+
m.role === "user"
|
|
73
|
+
? "bg-primary text-primary-foreground"
|
|
74
|
+
: "bg-muted/50 text-foreground border border-border/50"
|
|
75
|
+
)}>
|
|
76
|
+
{m.role === "assistant" && i === messages.length - 1 ? (
|
|
77
|
+
<TokenStream content={m.content} speed={20} />
|
|
78
|
+
) : (
|
|
79
|
+
<span className="whitespace-pre-wrap">{m.content}</span>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div className="p-4 md:p-6 pt-0">
|
|
87
|
+
<div className="mx-auto max-w-3xl">
|
|
88
|
+
<PromptInput onSubmit={handleSubmit} />
|
|
89
|
+
<div className="mt-2 text-center text-xs text-muted-foreground">
|
|
90
|
+
AI can make mistakes. Verify important information.
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Eye, EyeOff } from "lucide-react"
|
|
5
|
+
import { cx } from "@/lib/cx"
|
|
6
|
+
|
|
7
|
+
type AuthMode = "login" | "register"
|
|
8
|
+
|
|
9
|
+
interface AuthPanelProps {
|
|
10
|
+
defaultMode?: AuthMode
|
|
11
|
+
onLogin?: (email: string, password: string) => Promise<void> | void
|
|
12
|
+
onRegister?: (email: string, password: string, name: string) => Promise<void> | void
|
|
13
|
+
onGithub?: () => void
|
|
14
|
+
onGoogle?: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const GitHubIcon = () => (
|
|
18
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
19
|
+
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />
|
|
20
|
+
</svg>
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const GoogleIcon = () => (
|
|
24
|
+
<svg width="16" height="16" viewBox="0 0 24 24">
|
|
25
|
+
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
|
|
26
|
+
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
|
|
27
|
+
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
|
|
28
|
+
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
|
|
29
|
+
</svg>
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const INPUT_CLS = "h-9 w-full rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm outline-none transition-colors placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-border-strong focus-visible:ring-offset-1 focus-visible:ring-offset-background"
|
|
33
|
+
|
|
34
|
+
export function AuthPanel({
|
|
35
|
+
defaultMode = "login",
|
|
36
|
+
onLogin,
|
|
37
|
+
onRegister,
|
|
38
|
+
onGithub,
|
|
39
|
+
onGoogle,
|
|
40
|
+
}: AuthPanelProps) {
|
|
41
|
+
const [mode, setMode] = React.useState<AuthMode>(defaultMode)
|
|
42
|
+
const [email, setEmail] = React.useState("")
|
|
43
|
+
const [password, setPassword] = React.useState("")
|
|
44
|
+
const [name, setName] = React.useState("")
|
|
45
|
+
const [showPassword, setShowPassword] = React.useState(false)
|
|
46
|
+
const [loading, setLoading] = React.useState(false)
|
|
47
|
+
|
|
48
|
+
async function handleSubmit(e: React.FormEvent) {
|
|
49
|
+
e.preventDefault()
|
|
50
|
+
setLoading(true)
|
|
51
|
+
try {
|
|
52
|
+
if (mode === "login") {
|
|
53
|
+
await onLogin?.(email, password)
|
|
54
|
+
} else {
|
|
55
|
+
await onRegister?.(email, password, name)
|
|
56
|
+
}
|
|
57
|
+
} finally {
|
|
58
|
+
setLoading(false)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className="w-full max-w-sm rounded-2xl border border-border bg-card p-8">
|
|
64
|
+
<div className="mb-6">
|
|
65
|
+
<h1 className="text-xl font-semibold tracking-tight">
|
|
66
|
+
{mode === "login" ? "Sign in" : "Create account"}
|
|
67
|
+
</h1>
|
|
68
|
+
<p className="text-sm text-muted-foreground mt-1">
|
|
69
|
+
{mode === "login"
|
|
70
|
+
? "Welcome back. Enter your credentials to continue."
|
|
71
|
+
: "Get started in seconds. No credit card required."}
|
|
72
|
+
</p>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div className="flex gap-2 mb-6">
|
|
76
|
+
{onGithub && (
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
onClick={onGithub}
|
|
80
|
+
className="flex flex-1 items-center justify-center gap-2 rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm font-medium transition-colors hover:bg-surface-3 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-strong"
|
|
81
|
+
>
|
|
82
|
+
<GitHubIcon />
|
|
83
|
+
GitHub
|
|
84
|
+
</button>
|
|
85
|
+
)}
|
|
86
|
+
{onGoogle && (
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
onClick={onGoogle}
|
|
90
|
+
className="flex flex-1 items-center justify-center gap-2 rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm font-medium transition-colors hover:bg-surface-3 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-strong"
|
|
91
|
+
>
|
|
92
|
+
<GoogleIcon />
|
|
93
|
+
Google
|
|
94
|
+
</button>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{(onGithub || onGoogle) && (
|
|
99
|
+
<div className="relative flex items-center justify-center mb-6">
|
|
100
|
+
<div className="absolute inset-0 flex items-center">
|
|
101
|
+
<div className="w-full border-t border-border" />
|
|
102
|
+
</div>
|
|
103
|
+
<span className="relative bg-card px-3 text-xs text-muted-foreground">or continue with email</span>
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
108
|
+
{mode === "register" && (
|
|
109
|
+
<div className="space-y-1.5">
|
|
110
|
+
<label className="text-xs font-medium text-foreground">Name</label>
|
|
111
|
+
<input
|
|
112
|
+
type="text"
|
|
113
|
+
placeholder="Your name"
|
|
114
|
+
value={name}
|
|
115
|
+
onChange={(e) => setName(e.target.value)}
|
|
116
|
+
required
|
|
117
|
+
autoComplete="name"
|
|
118
|
+
className={INPUT_CLS}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
|
|
123
|
+
<div className="space-y-1.5">
|
|
124
|
+
<label className="text-xs font-medium text-foreground">Email</label>
|
|
125
|
+
<input
|
|
126
|
+
type="email"
|
|
127
|
+
placeholder="you@example.com"
|
|
128
|
+
value={email}
|
|
129
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
130
|
+
required
|
|
131
|
+
autoComplete="email"
|
|
132
|
+
className={INPUT_CLS}
|
|
133
|
+
/>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div className="space-y-1.5">
|
|
137
|
+
<div className="flex items-center justify-between">
|
|
138
|
+
<label className="text-xs font-medium text-foreground">Password</label>
|
|
139
|
+
{mode === "login" && (
|
|
140
|
+
<button type="button" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
|
|
141
|
+
Forgot password?
|
|
142
|
+
</button>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
<div className="relative">
|
|
146
|
+
<input
|
|
147
|
+
type={showPassword ? "text" : "password"}
|
|
148
|
+
placeholder="••••••••"
|
|
149
|
+
value={password}
|
|
150
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
151
|
+
required
|
|
152
|
+
minLength={8}
|
|
153
|
+
autoComplete={mode === "login" ? "current-password" : "new-password"}
|
|
154
|
+
className={cx(INPUT_CLS, "pr-9")}
|
|
155
|
+
/>
|
|
156
|
+
<button
|
|
157
|
+
type="button"
|
|
158
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
159
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
|
|
160
|
+
aria-label={showPassword ? "Hide password" : "Show password"}
|
|
161
|
+
>
|
|
162
|
+
{showPassword ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<button
|
|
168
|
+
type="submit"
|
|
169
|
+
disabled={loading}
|
|
170
|
+
className="w-full rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-strong focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50"
|
|
171
|
+
>
|
|
172
|
+
{loading ? "Loading…" : mode === "login" ? "Sign in" : "Create account"}
|
|
173
|
+
</button>
|
|
174
|
+
</form>
|
|
175
|
+
|
|
176
|
+
<p className="mt-5 text-center text-xs text-muted-foreground">
|
|
177
|
+
{mode === "login" ? (
|
|
178
|
+
<>
|
|
179
|
+
Don't have an account?{" "}
|
|
180
|
+
<button
|
|
181
|
+
type="button"
|
|
182
|
+
onClick={() => setMode("register")}
|
|
183
|
+
className="font-medium text-foreground hover:underline underline-offset-2"
|
|
184
|
+
>
|
|
185
|
+
Sign up
|
|
186
|
+
</button>
|
|
187
|
+
</>
|
|
188
|
+
) : (
|
|
189
|
+
<>
|
|
190
|
+
Already have an account?{" "}
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
onClick={() => setMode("login")}
|
|
194
|
+
className="font-medium text-foreground hover:underline underline-offset-2"
|
|
195
|
+
>
|
|
196
|
+
Sign in
|
|
197
|
+
</button>
|
|
198
|
+
</>
|
|
199
|
+
)}
|
|
200
|
+
</p>
|
|
201
|
+
</div>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cx } from "@/lib/cx"
|
|
3
|
+
|
|
4
|
+
export interface Feature {
|
|
5
|
+
icon: React.ReactNode
|
|
6
|
+
title: string
|
|
7
|
+
description: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface FeatureGridProps {
|
|
11
|
+
eyebrow?: string
|
|
12
|
+
headline?: string
|
|
13
|
+
subtext?: string
|
|
14
|
+
features?: Feature[]
|
|
15
|
+
columns?: 2 | 3 | 4
|
|
16
|
+
className?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const defaultFeatures: Feature[] = [
|
|
20
|
+
{
|
|
21
|
+
icon: (
|
|
22
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
23
|
+
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
|
|
24
|
+
</svg>
|
|
25
|
+
),
|
|
26
|
+
title: "Instant Install",
|
|
27
|
+
description: "One CLI command stamps any block directly into your project. No config, no setup, no runtime dependency.",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
icon: (
|
|
31
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
32
|
+
<rect width="18" height="18" x="3" y="3" rx="2" />
|
|
33
|
+
<path d="M3 9h18M9 21V9" />
|
|
34
|
+
</svg>
|
|
35
|
+
),
|
|
36
|
+
title: "Full Source Ownership",
|
|
37
|
+
description: "The code lands in your repo. No dependency to maintain, no upstream surprises. Customize it freely.",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
icon: (
|
|
41
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
42
|
+
<circle cx="12" cy="12" r="3" />
|
|
43
|
+
<path d="M12 2v3M12 19v3M4.22 4.22l2.12 2.12M17.66 17.66l2.12 2.12M2 12h3M19 12h3M4.22 19.78l2.12-2.12M17.66 6.34l2.12-2.12" />
|
|
44
|
+
</svg>
|
|
45
|
+
),
|
|
46
|
+
title: "AI-Ready Prompts",
|
|
47
|
+
description: "Every block ships with optimized prompts for Cursor, Claude, v0, and Bolt. Describe what you want, get it built.",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
icon: (
|
|
51
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
52
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
|
53
|
+
</svg>
|
|
54
|
+
),
|
|
55
|
+
title: "Accessible by Default",
|
|
56
|
+
description: "Built on Radix UI primitives. Keyboard navigation, ARIA attributes, and screen reader support out of the box.",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
icon: (
|
|
60
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
61
|
+
<circle cx="12" cy="8" r="6" />
|
|
62
|
+
<path d="M15.477 12.89 17 22l-5-3-5 3 1.523-9.11" />
|
|
63
|
+
</svg>
|
|
64
|
+
),
|
|
65
|
+
title: "Dark Mode Native",
|
|
66
|
+
description: "Every component is built with CSS variables and supports dark mode out of the box. No extra config.",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
icon: (
|
|
70
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
71
|
+
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />
|
|
72
|
+
</svg>
|
|
73
|
+
),
|
|
74
|
+
title: "Themeable",
|
|
75
|
+
description: "Token-based design system. Change one CSS variable, every component updates. Consistent without effort.",
|
|
76
|
+
},
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
const colClass: Record<2 | 3 | 4, string> = {
|
|
80
|
+
2: "sm:grid-cols-2",
|
|
81
|
+
3: "sm:grid-cols-2 lg:grid-cols-3",
|
|
82
|
+
4: "sm:grid-cols-2 lg:grid-cols-4",
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function FeatureGrid({
|
|
86
|
+
eyebrow = "Why StampUI",
|
|
87
|
+
headline = "Built for how you actually ship",
|
|
88
|
+
subtext,
|
|
89
|
+
features = defaultFeatures,
|
|
90
|
+
columns = 3,
|
|
91
|
+
className,
|
|
92
|
+
}: FeatureGridProps) {
|
|
93
|
+
return (
|
|
94
|
+
<section className={cx("w-full py-24 px-6", className)}>
|
|
95
|
+
<div className="mx-auto max-w-5xl">
|
|
96
|
+
<div className="mb-16 text-center">
|
|
97
|
+
{eyebrow && (
|
|
98
|
+
<p className="text-xs font-mono text-muted-foreground uppercase tracking-widest mb-4">{eyebrow}</p>
|
|
99
|
+
)}
|
|
100
|
+
<h2 className="text-3xl sm:text-4xl font-bold tracking-tight text-foreground">{headline}</h2>
|
|
101
|
+
{subtext && (
|
|
102
|
+
<p className="mt-4 text-base text-muted-foreground max-w-xl mx-auto">{subtext}</p>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div className={cx("grid grid-cols-1 gap-8", colClass[columns])}>
|
|
107
|
+
{features.map((feature, i) => (
|
|
108
|
+
<div key={i} className="flex flex-col gap-4">
|
|
109
|
+
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg border border-border bg-surface-2 text-foreground">
|
|
110
|
+
{feature.icon}
|
|
111
|
+
</div>
|
|
112
|
+
<div>
|
|
113
|
+
<h3 className="text-sm font-semibold text-foreground mb-1.5">{feature.title}</h3>
|
|
114
|
+
<p className="text-sm text-muted-foreground leading-relaxed">{feature.description}</p>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
))}
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</section>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Badge } from "@/components/core/badge"
|
|
3
|
+
import { Button } from "@/components/core/button"
|
|
4
|
+
import { cx } from "@/lib/cx"
|
|
5
|
+
|
|
6
|
+
export interface HeroSectionProps {
|
|
7
|
+
eyebrow?: string
|
|
8
|
+
headline?: string
|
|
9
|
+
subtext?: string
|
|
10
|
+
primaryCta?: { label: string; href?: string; onClick?: () => void }
|
|
11
|
+
secondaryCta?: { label: string; href?: string; onClick?: () => void }
|
|
12
|
+
align?: "left" | "center"
|
|
13
|
+
className?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function HeroSection({
|
|
17
|
+
eyebrow = "Now in public beta",
|
|
18
|
+
headline = "Your app works.\nNow make it look shipped.",
|
|
19
|
+
subtext = "Production-ready UI blocks stamped directly into your project. No runtime, no lock-in — just clean code you own.",
|
|
20
|
+
primaryCta = { label: "Browse Blocks", href: "/blocks" },
|
|
21
|
+
secondaryCta = { label: "View Components", href: "/blocks/components" },
|
|
22
|
+
align = "center",
|
|
23
|
+
className,
|
|
24
|
+
}: HeroSectionProps) {
|
|
25
|
+
return (
|
|
26
|
+
<section
|
|
27
|
+
className={cx(
|
|
28
|
+
"w-full py-24 px-6",
|
|
29
|
+
align === "center" && "flex flex-col items-center text-center",
|
|
30
|
+
align === "left" && "flex flex-col items-start text-left max-w-2xl",
|
|
31
|
+
className
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
{eyebrow && (
|
|
35
|
+
<Badge variant="neutral" className="mb-6 font-mono text-xs tracking-wider">
|
|
36
|
+
{eyebrow}
|
|
37
|
+
</Badge>
|
|
38
|
+
)}
|
|
39
|
+
|
|
40
|
+
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold tracking-tight text-foreground leading-tight whitespace-pre-line max-w-3xl">
|
|
41
|
+
{headline}
|
|
42
|
+
</h1>
|
|
43
|
+
|
|
44
|
+
{subtext && (
|
|
45
|
+
<p className="mt-6 text-base sm:text-lg text-muted-foreground max-w-xl leading-relaxed">
|
|
46
|
+
{subtext}
|
|
47
|
+
</p>
|
|
48
|
+
)}
|
|
49
|
+
|
|
50
|
+
<div className={cx("mt-10 flex flex-wrap gap-3", align === "center" && "justify-center")}>
|
|
51
|
+
{primaryCta && (
|
|
52
|
+
<Button
|
|
53
|
+
asChild={!!primaryCta.href}
|
|
54
|
+
onClick={primaryCta.onClick}
|
|
55
|
+
size="lg"
|
|
56
|
+
>
|
|
57
|
+
{primaryCta.href ? <a href={primaryCta.href}>{primaryCta.label}</a> : <span>{primaryCta.label}</span>}
|
|
58
|
+
</Button>
|
|
59
|
+
)}
|
|
60
|
+
{secondaryCta && (
|
|
61
|
+
<Button
|
|
62
|
+
asChild={!!secondaryCta.href}
|
|
63
|
+
onClick={secondaryCta.onClick}
|
|
64
|
+
variant="outline"
|
|
65
|
+
size="lg"
|
|
66
|
+
>
|
|
67
|
+
{secondaryCta.href ? <a href={secondaryCta.href}>{secondaryCta.label}</a> : <span>{secondaryCta.label}</span>}
|
|
68
|
+
</Button>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
</section>
|
|
72
|
+
)
|
|
73
|
+
}
|