Skip to content

Commit 83dd6e5

Browse files
bloveclaude
andauthored
refactor(website): migrate to shadcn/ui component primitives (#1)
* feat(website): add shadcn dependencies to package.json * chore: remove unauthorized pnpm-workspace.yaml from worktree * feat(website): add cn() utility and shadcn components.json * feat(website): add shadcn CSS variables to global.css * feat(website): add shadcn Button component * feat(website): add shadcn Card component * feat(website): add shadcn Input, Textarea, Label, Badge components Add four UI components to the website: Input, Textarea, Label, and Badge. Each component follows the shadcn/ui pattern with proper TypeScript types, forwardRef support, and Tailwind styling with the project's design system. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(website): add baseUrl to tsconfig for @/ path alias resolution * refactor(website): use shadcn Button in Nav * refactor(website): use shadcn Card in FeatureStrip * refactor(website): use shadcn Badge in HeroTwoCol * refactor(website): use shadcn Card, Button, Badge in PricingGrid * refactor(website): use shadcn Input, Textarea, Label, Button in LeadForm * chore: remove erroneous pnpm-lock.yaml from worktree Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): update package-lock.json with shadcn dependencies Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(website): replace empty interfaces with type aliases for lint Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8669e0c commit 83dd6e5

17 files changed

Lines changed: 608 additions & 195 deletions

File tree

apps/website/components.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema.json",
3+
"style": "default",
4+
"rsc": true,
5+
"tsx": true,
6+
"tailwind": {
7+
"config": "",
8+
"css": "src/app/global.css",
9+
"baseColor": "neutral",
10+
"cssVariables": true,
11+
"prefix": ""
12+
},
13+
"aliases": {
14+
"components": "@/components",
15+
"utils": "@/lib/utils",
16+
"ui": "@/components/ui",
17+
"lib": "@/lib",
18+
"hooks": "@/hooks"
19+
},
20+
"iconLibrary": "lucide"
21+
}

apps/website/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
"version": "0.0.1",
44
"private": true,
55
"dependencies": {
6+
"@radix-ui/react-slot": "^1.1.0",
7+
"class-variance-authority": "^0.7.0",
8+
"clsx": "^2.1.1",
69
"next": "~16.1.6",
710
"react": "^19.0.0",
811
"react-dom": "^19.0.0",
9-
"shiki": "*"
12+
"shiki": "*",
13+
"tailwind-merge": "^2.5.0"
1014
}
1115
}

apps/website/src/app/global.css

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@import "tailwindcss";
22

33
@theme {
4+
/* Existing design tokens — preserved for backward compat */
45
--color-bg: #080B14;
56
--color-accent: #6C8EFF;
67
--color-accent-glow: rgba(108, 142, 255, 0.35);
@@ -18,7 +19,25 @@
1819
--font-mono: "JetBrains Mono", monospace;
1920
}
2021

22+
/* shadcn CSS variable tokens — new, non-conflicting names */
23+
@theme inline {
24+
--color-background: var(--background);
25+
--color-foreground: var(--foreground);
26+
--color-primary: var(--primary);
27+
--color-primary-foreground: var(--primary-foreground);
28+
--color-card: var(--card);
29+
--color-card-foreground: var(--card-foreground);
30+
--color-muted: var(--muted);
31+
--color-muted-foreground: var(--muted-foreground);
32+
--color-border: var(--border);
33+
--color-input: var(--input);
34+
--color-ring: var(--ring);
35+
--color-destructive: var(--destructive);
36+
--color-destructive-foreground: var(--destructive-foreground);
37+
}
38+
2139
:root {
40+
/* Existing legacy vars */
2241
--color-bg: #080B14;
2342
--color-accent: #6C8EFF;
2443
--color-accent-glow: rgba(108, 142, 255, 0.35);
@@ -30,6 +49,21 @@
3049
--color-text-muted: #4A527A;
3150
--color-sidebar-bg: #0A0D18;
3251
--color-angular-red: #DD0031;
52+
53+
/* shadcn semantic vars — maps to same palette */
54+
--background: #080B14;
55+
--foreground: #EEF1FF;
56+
--primary: #6C8EFF;
57+
--primary-foreground: #ffffff;
58+
--card: #0A0D18;
59+
--card-foreground: #EEF1FF;
60+
--muted: #0D1020;
61+
--muted-foreground: #8B96C8;
62+
--border: #1a2040;
63+
--input: #1a2040;
64+
--ring: #6C8EFF;
65+
--destructive: #FF6B6B;
66+
--destructive-foreground: #ffffff;
3367
}
3468

3569
* {

apps/website/src/components/landing/FeatureStrip.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client';
22
import { motion } from 'framer-motion';
3+
import { Card, CardContent, CardTitle } from '@/components/ui/card';
34

45
const FEATURES = [
56
{ icon: '⚡', title: 'Token-by-token streaming', desc: 'Real-time SSE streaming via FetchStreamTransport. Messages update as each token arrives.' },
@@ -13,25 +14,34 @@ const FEATURES = [
1314
export function FeatureStrip() {
1415
return (
1516
<section className="px-8 py-16 max-w-6xl mx-auto">
16-
<h2 className="font-mono text-xs uppercase tracking-widest mb-12 text-center"
17-
style={{ color: 'var(--color-accent)', fontWeight: 'normal' }}>Features</h2>
17+
<h2
18+
className="font-mono text-xs uppercase tracking-widest mb-12 text-center"
19+
style={{ color: 'var(--color-accent)', fontWeight: 'normal' }}
20+
>
21+
Features
22+
</h2>
1823
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
1924
{FEATURES.map((f, i) => (
2025
<motion.div
2126
key={f.title}
22-
className="p-6 rounded-lg cursor-default"
23-
style={{ border: '1px solid rgba(108,142,255,0.15)', background: 'rgba(108,142,255,0.02)' }}
2427
initial={{ opacity: 0, y: 20 }}
2528
whileInView={{ opacity: 1, y: 0 }}
2629
viewport={{ once: true }}
2730
transition={{ delay: i * 0.08, duration: 0.4 }}
2831
whileHover={{
2932
borderColor: 'rgba(108,142,255,0.4)',
3033
boxShadow: '0 0 12px rgba(108,142,255,0.2)',
31-
}}>
32-
<div className="mb-3" style={{ fontSize: '1.5rem', color: '#6C8EFF' }} aria-hidden="true">{f.icon}</div>
33-
<h3 style={{ fontFamily: 'var(--font-garamond)', fontWeight: 700, fontSize: '1.125rem', color: '#EEF1FF', marginBottom: 8 }}>{f.title}</h3>
34-
<p style={{ fontSize: '0.875rem', color: '#8B96C8', lineHeight: 1.6 }}>{f.desc}</p>
34+
}}
35+
>
36+
<Card className="p-6 h-full cursor-default transition-all">
37+
<div className="mb-3 text-2xl" style={{ color: '#6C8EFF' }} aria-hidden="true">
38+
{f.icon}
39+
</div>
40+
<CardTitle className="mb-2">{f.title}</CardTitle>
41+
<CardContent className="p-0">
42+
<p className="text-sm text-muted-foreground leading-relaxed">{f.desc}</p>
43+
</CardContent>
44+
</Card>
3545
</motion.div>
3646
))}
3747
</div>

apps/website/src/components/landing/HeroTwoCol.tsx

Lines changed: 68 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,45 @@
11
import { GenerativeUIFrame } from './GenerativeUIFrame';
22
import { CopyPromptButton } from '../docs/CopyPromptButton';
33
import { getPromptBySlug } from '../../lib/docs';
4+
import { Badge } from '@/components/ui/badge';
45

56
function LangChainBadge() {
67
return (
7-
<span style={{
8-
display: 'inline-flex', alignItems: 'center', gap: 6,
9-
background: 'rgba(108,142,255,0.08)',
10-
border: '1px solid rgba(108,142,255,0.15)',
11-
borderRadius: 20, padding: '4px 10px 4px 8px',
12-
fontFamily: 'var(--font-mono)', fontSize: 11, color: '#EEF1FF',
13-
}}>
14-
<span style={{
15-
width: 14, height: 14, borderRadius: 3,
16-
background: 'rgba(127,200,255,0.2)',
17-
border: '1px solid rgba(127,200,255,0.4)',
18-
display: 'flex', alignItems: 'center', justifyContent: 'center',
19-
fontSize: 7, fontWeight: 700, color: '#7FC8FF', lineHeight: 1,
20-
}}>LC</span>
8+
<Badge variant="default">
9+
<span
10+
style={{
11+
width: 14,
12+
height: 14,
13+
borderRadius: 3,
14+
background: 'rgba(127,200,255,0.2)',
15+
border: '1px solid rgba(127,200,255,0.4)',
16+
display: 'inline-flex',
17+
alignItems: 'center',
18+
justifyContent: 'center',
19+
fontSize: 7,
20+
fontWeight: 700,
21+
color: '#7FC8FF',
22+
lineHeight: 1,
23+
flexShrink: 0,
24+
}}
25+
>
26+
LC
27+
</span>
2128
LangChain
22-
</span>
29+
</Badge>
2330
);
2431
}
2532

2633
function AngularBadge() {
2734
return (
28-
<span style={{
29-
display: 'inline-flex', alignItems: 'center', gap: 6,
30-
background: 'rgba(108,142,255,0.08)',
31-
border: '1px solid rgba(108,142,255,0.15)',
32-
borderRadius: 20, padding: '4px 10px 4px 8px',
33-
fontFamily: 'var(--font-mono)', fontSize: 11, color: '#EEF1FF',
34-
}}>
35-
<svg width="14" height="14" viewBox="0 0 250 250" aria-hidden={true}>
36-
<path fill="#DD0031" d="M125 30L31.9 63.2l14.2 123.1L125 230l78.9-43.7 14.2-123.1z"/>
37-
<path fill="#C3002F" d="M125 30v22.2l-61.7 162.4 37.8 15.4z"/>
38-
<path fill="#fff" d="M125 52.1L66.8 182.6h21.7l11.7-29.2h49.4l11.7 29.2H183L125 52.1zm17 83.3h-34l17-40.9z"/>
35+
<Badge variant="default">
36+
<svg width="14" height="14" viewBox="0 0 250 250" aria-hidden={true} style={{ flexShrink: 0 }}>
37+
<path fill="#DD0031" d="M125 30L31.9 63.2l14.2 123.1L125 230l78.9-43.7 14.2-123.1z" />
38+
<path fill="#C3002F" d="M125 30v22.2l-61.7 162.4 37.8 15.4z" />
39+
<path fill="#fff" d="M125 52.1L66.8 182.6h21.7l11.7-29.2h49.4l11.7 29.2H183L125 52.1zm17 83.3h-34l17-40.9z" />
3940
</svg>
4041
Angular
41-
</span>
42+
</Badge>
4243
);
4344
}
4445

@@ -63,61 +64,64 @@ export async function HeroTwoCol() {
6364
.hero-right { flex: none; max-width: 100%; width: 100%; max-height: 300px; overflow: hidden; }
6465
}
6566
`}</style>
67+
6668
{/* Left column — 55% */}
6769
<div className="hero-left">
6870
<div style={{ display: 'flex', gap: 8, marginBottom: 28, flexWrap: 'wrap' }}>
6971
<LangChainBadge />
7072
<AngularBadge />
7173
</div>
7274

73-
<h1 id="hero-heading" style={{
74-
fontFamily: 'var(--font-garamond)',
75-
fontSize: 'clamp(36px, 4.5vw, 72px)',
76-
fontWeight: 800,
77-
lineHeight: 1.05,
78-
color: '#EEF1FF',
79-
margin: 0,
80-
marginBottom: 20,
81-
}}>
75+
<h1
76+
id="hero-heading"
77+
style={{
78+
fontFamily: 'var(--font-garamond)',
79+
fontSize: 'clamp(36px, 4.5vw, 72px)',
80+
fontWeight: 800,
81+
lineHeight: 1.05,
82+
color: '#EEF1FF',
83+
margin: 0,
84+
marginBottom: 20,
85+
}}
86+
>
8287
The Enterprise Streaming Resource for LangChain and{' '}
83-
<span style={{
84-
color: '#DD0031',
85-
textShadow: '0 0 30px rgba(221,0,49,0.5)',
86-
}}>
88+
<span style={{ color: '#DD0031', textShadow: '0 0 30px rgba(221,0,49,0.5)' }}>
8789
Angular
8890
</span>
8991
</h1>
9092

91-
<p style={{
92-
fontFamily: 'var(--font-garamond)',
93-
fontStyle: 'italic',
94-
fontSize: 'clamp(16px, 1.8vw, 20px)',
95-
color: '#8B96C8',
96-
maxWidth: '44ch',
97-
lineHeight: 1.5,
98-
margin: 0,
99-
marginBottom: 32,
100-
}}>
93+
<p
94+
style={{
95+
fontFamily: 'var(--font-garamond)',
96+
fontStyle: 'italic',
97+
fontSize: 'clamp(16px, 1.8vw, 20px)',
98+
color: '#8B96C8',
99+
maxWidth: '44ch',
100+
lineHeight: 1.5,
101+
margin: 0,
102+
marginBottom: 32,
103+
}}
104+
>
101105
Full parity with React{' '}
102-
<code style={{
103-
fontStyle: 'normal',
104-
fontFamily: 'var(--font-mono)',
105-
fontSize: '0.8em',
106-
background: 'rgba(108,142,255,0.08)',
107-
color: '#6C8EFF',
108-
padding: '2px 6px',
109-
borderRadius: 4,
110-
}}>useStream()</code>
106+
<code
107+
style={{
108+
fontStyle: 'normal',
109+
fontFamily: 'var(--font-mono)',
110+
fontSize: '0.8em',
111+
background: 'rgba(108,142,255,0.08)',
112+
color: '#6C8EFF',
113+
padding: '2px 6px',
114+
borderRadius: 4,
115+
}}
116+
>
117+
useStream()
118+
</code>
111119
{' '}— built natively for Angular 20+.
112120
</p>
113121

114122
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 12 }}>
115123
<CopyPromptButton prompt={prompt} variant="hero" />
116-
<span style={{
117-
fontFamily: 'var(--font-mono)',
118-
fontSize: 12,
119-
color: '#4A527A',
120-
}}>
124+
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: '#4A527A' }}>
121125
npm install @cacheplane/stream-resource
122126
</span>
123127
</div>

0 commit comments

Comments
 (0)