feat: turborepo monorepo scaffold and provisioning

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-21 16:44:30 -08:00
parent e22f5e379f
commit 8587644a62
35 changed files with 841 additions and 5 deletions

View File

@@ -0,0 +1,22 @@
{
"name": "@{{project-slug}}/ui",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts",
"./styles": "./src/styles.css"
},
"dependencies": {
"@{{project-slug}}/tokens": "workspace:*"
},
"devDependencies": {
"@{{project-slug}}/config": "workspace:*",
"@types/react": "^19.0.0",
"typescript": "^5.7.0"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}

View File

@@ -0,0 +1,29 @@
import type { HTMLAttributes } from 'react';
type BadgeVariant = 'default' | 'success' | 'warning' | 'error' | 'brand';
interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
variant?: BadgeVariant;
}
const variantClasses: Record<BadgeVariant, string> = {
default: 'bg-[var(--color-neutral-100)] text-[var(--color-neutral-700)]',
success: 'bg-[var(--color-success-light)] text-[var(--color-success-dark,#15803d)]',
warning: 'bg-[var(--color-warning-light)] text-[var(--color-warning-dark,#b45309)]',
error: 'bg-[var(--color-error-light)] text-[var(--color-error-dark,#b91c1c)]',
brand: 'bg-[var(--color-brand-100)] text-[var(--color-brand-700)]',
};
export function Badge({ variant = 'default', className = '', children, ...props }: BadgeProps) {
return (
<span
{...props}
className={[
'inline-flex items-center rounded-[var(--radius-full)] px-2.5 py-0.5 text-xs font-medium',
variantClasses[variant], className,
].join(' ')}
>
{children}
</span>
);
}

View File

@@ -0,0 +1,49 @@
import type { ButtonHTMLAttributes } from 'react';
type Variant = 'primary' | 'secondary' | 'ghost' | 'destructive';
type Size = 'sm' | 'md' | 'lg';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: Variant;
size?: Size;
loading?: boolean;
}
const variantClasses: Record<Variant, string> = {
primary: 'bg-[var(--color-brand-600)] text-white hover:bg-[var(--color-brand-700)]',
secondary: 'bg-[var(--color-neutral-100)] text-[var(--color-neutral-900)] hover:bg-[var(--color-neutral-200)]',
ghost: 'bg-transparent text-[var(--color-neutral-700)] hover:bg-[var(--color-neutral-100)]',
destructive: 'bg-[var(--color-error)] text-white hover:opacity-90',
};
const sizeClasses: Record<Size, string> = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-sm',
lg: 'px-5 py-2.5 text-base',
};
export function Button({
variant = 'primary', size = 'md', loading = false,
disabled, className = '', children, ...props
}: ButtonProps) {
return (
<button
{...props}
disabled={disabled ?? loading}
className={[
'inline-flex items-center justify-center gap-2 rounded-[var(--radius-md)] font-medium transition-colors',
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
'disabled:opacity-50 disabled:cursor-not-allowed',
variantClasses[variant], sizeClasses[size], className,
].join(' ')}
>
{loading && (
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24" fill="none">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
)}
{children}
</button>
);
}

View File

@@ -0,0 +1,21 @@
import type { HTMLAttributes } from 'react';
interface CardProps extends HTMLAttributes<HTMLDivElement> {
padding?: 'none' | 'sm' | 'md' | 'lg';
}
const paddingClasses = { none: '', sm: 'p-3', md: 'p-5', lg: 'p-8' };
export function Card({ padding = 'md', className = '', children, ...props }: CardProps) {
return (
<div
{...props}
className={[
'rounded-[var(--radius-xl)] border border-[var(--color-neutral-200)] bg-white shadow-sm',
paddingClasses[padding], className,
].join(' ')}
>
{children}
</div>
);
}

View File

@@ -0,0 +1,35 @@
import type { InputHTMLAttributes } from 'react';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
hint?: string;
}
export function Input({ label, error, hint, className = '', id, ...props }: InputProps) {
const inputId = id ?? label?.toLowerCase().replace(/\s+/g, '-');
return (
<div className="flex flex-col gap-1">
{label && (
<label htmlFor={inputId} className="text-sm font-medium text-[var(--color-neutral-700)]">
{label}
</label>
)}
<input
{...props} id={inputId}
className={[
'w-full rounded-[var(--radius-md)] border px-3 py-2 text-sm transition-colors',
'placeholder:text-[var(--color-neutral-400)]',
'focus:outline-none focus:ring-2 focus:ring-[var(--color-brand-500)] focus:border-transparent',
'disabled:opacity-50 disabled:cursor-not-allowed',
error
? 'border-[var(--color-error)] bg-[var(--color-error-light)]'
: 'border-[var(--color-neutral-300)] bg-white',
className,
].join(' ')}
/>
{error && <p className="text-xs text-[var(--color-error)]">{error}</p>}
{hint && !error && <p className="text-xs text-[var(--color-neutral-500)]">{hint}</p>}
</div>
);
}

View File

@@ -0,0 +1,4 @@
export { Button } from './components/Button.js';
export { Card } from './components/Card.js';
export { Input } from './components/Input.js';
export { Badge } from './components/Badge.js';

View File

@@ -0,0 +1 @@
@import '@{{project-slug}}/tokens/css';