feat: turborepo monorepo scaffold and provisioning
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
22
lib/scaffold/turborepo/packages/ui/package.json
Normal file
22
lib/scaffold/turborepo/packages/ui/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
29
lib/scaffold/turborepo/packages/ui/src/components/Badge.tsx
Normal file
29
lib/scaffold/turborepo/packages/ui/src/components/Badge.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
49
lib/scaffold/turborepo/packages/ui/src/components/Button.tsx
Normal file
49
lib/scaffold/turborepo/packages/ui/src/components/Button.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
21
lib/scaffold/turborepo/packages/ui/src/components/Card.tsx
Normal file
21
lib/scaffold/turborepo/packages/ui/src/components/Card.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
35
lib/scaffold/turborepo/packages/ui/src/components/Input.tsx
Normal file
35
lib/scaffold/turborepo/packages/ui/src/components/Input.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
4
lib/scaffold/turborepo/packages/ui/src/index.ts
Normal file
4
lib/scaffold/turborepo/packages/ui/src/index.ts
Normal 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';
|
||||
1
lib/scaffold/turborepo/packages/ui/src/styles.css
Normal file
1
lib/scaffold/turborepo/packages/ui/src/styles.css
Normal file
@@ -0,0 +1 @@
|
||||
@import '@{{project-slug}}/tokens/css';
|
||||
Reference in New Issue
Block a user