Initial scaffold: React 19 + Vite + TypeScript + Tailwind CSS v4 + Storybook 10
Design system and component library for the Research Synthesiser. Includes: - Tailwind CSS v4 with @theme-based design tokens from the existing synthesiser - Storybook 10.4 with MCP, a11y, docs, and vitest addons - ESLint + Prettier with Tailwind class sorting - Button component as pipeline validation - CLAUDE.md with project principles and conventions - ARCHITECTURE.md as living architecture document - Penpot and Storybook MCP server configuration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
16
src/App.tsx
Normal file
16
src/App.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
function App() {
|
||||
return (
|
||||
<div className="min-h-screen bg-bg text-text">
|
||||
<header className="bg-surface border-b border-border px-6 py-3">
|
||||
<h1 className="text-lg font-semibold">SDC Design System</h1>
|
||||
</header>
|
||||
<main className="p-6">
|
||||
<p className="text-text-secondary">
|
||||
Component library for the Research Synthesiser.
|
||||
</p>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
47
src/components/ui/Button/Button.stories.tsx
Normal file
47
src/components/ui/Button/Button.stories.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import { Button } from './Button'
|
||||
|
||||
const meta: Meta<typeof Button> = {
|
||||
title: 'UI/Button',
|
||||
component: Button,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['primary', 'secondary', 'danger'],
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['sm', 'md', 'lg'],
|
||||
},
|
||||
disabled: { control: 'boolean' },
|
||||
children: { control: 'text' },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Primary: Story = {
|
||||
args: { children: 'Primary Button', variant: 'primary' },
|
||||
}
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: { children: 'Secondary Button', variant: 'secondary' },
|
||||
}
|
||||
|
||||
export const Danger: Story = {
|
||||
args: { children: 'Delete', variant: 'danger' },
|
||||
}
|
||||
|
||||
export const Small: Story = {
|
||||
args: { children: 'Small', size: 'sm' },
|
||||
}
|
||||
|
||||
export const Large: Story = {
|
||||
args: { children: 'Large', size: 'lg' },
|
||||
}
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: { children: 'Disabled', disabled: true },
|
||||
}
|
||||
34
src/components/ui/Button/Button.tsx
Normal file
34
src/components/ui/Button/Button.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { forwardRef, type ButtonHTMLAttributes } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: 'primary' | 'secondary' | 'danger'
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
}
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant = 'primary', size = 'md', children, ...props }, ref) => {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center font-medium rounded-default transition-colors',
|
||||
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
variant === 'primary' && 'bg-primary text-white hover:bg-primary-hover',
|
||||
variant === 'secondary' && 'bg-surface text-text border border-border hover:bg-bg',
|
||||
variant === 'danger' && 'bg-error text-white hover:bg-red-700',
|
||||
size === 'sm' && 'px-3 py-1.5 text-sm',
|
||||
size === 'md' && 'px-4 py-2 text-base',
|
||||
size === 'lg' && 'px-6 py-3 text-lg',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Button.displayName = 'Button'
|
||||
2
src/components/ui/Button/index.ts
Normal file
2
src/components/ui/Button/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { Button } from './Button'
|
||||
export type { ButtonProps } from './Button'
|
||||
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
10
src/main.tsx
Normal file
10
src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './styles/global.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
15
src/styles/global.css
Normal file
15
src/styles/global.css
Normal file
@@ -0,0 +1,15 @@
|
||||
@import "tailwindcss";
|
||||
@import "../tokens/tokens.css";
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||
line-height: 1.5;
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
37
src/tokens/tokens.css
Normal file
37
src/tokens/tokens.css
Normal file
@@ -0,0 +1,37 @@
|
||||
@theme {
|
||||
/* Surface colors */
|
||||
--color-bg: #f5f6f8;
|
||||
--color-surface: #ffffff;
|
||||
--color-border: #e2e5ea;
|
||||
|
||||
/* Text colors */
|
||||
--color-text: #1a1d23;
|
||||
--color-text-secondary: #5f6672;
|
||||
|
||||
/* Primary */
|
||||
--color-primary: #2563eb;
|
||||
--color-primary-hover: #1d4ed8;
|
||||
--color-primary-light: #eff4ff;
|
||||
|
||||
/* Semantic: Success */
|
||||
--color-success: #16a34a;
|
||||
--color-success-bg: #dcfce7;
|
||||
|
||||
/* Semantic: Warning */
|
||||
--color-warning: #d97706;
|
||||
--color-warning-bg: #fef9c3;
|
||||
|
||||
/* Semantic: Error */
|
||||
--color-error: #dc2626;
|
||||
--color-error-bg: #fee2e2;
|
||||
|
||||
/* Radius */
|
||||
--radius-sm: 4px;
|
||||
--radius-default: 6px;
|
||||
--radius-lg: 10px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-default: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
Reference in New Issue
Block a user