chore: scaffold phase 0 workspace

This commit is contained in:
2026-03-19 13:42:43 +08:00
parent a099912dac
commit 937855362b
24 changed files with 4908 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
node_modules
dist
storybook-static
coverage
.turbo
.pnpm-store
.storybook
.DS_Store
+17
View File
@@ -0,0 +1,17 @@
{
"name": "@ai-ui/docs",
"private": true,
"type": "module",
"scripts": {
"build-storybook": "storybook build",
"storybook": "storybook dev -p 6006 --ci",
"storybook:smoke": "storybook dev -p 6006 --ci --smoke-test",
"typecheck": "tsc --noEmit -p tsconfig.json"
},
"dependencies": {
"@ai-ui/tokens": "workspace:*",
"@ai-ui/ui": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
}
+79
View File
@@ -0,0 +1,79 @@
import { cn, motionDurations } from "@ai-ui/ui";
import { themeNames } from "@ai-ui/tokens";
import type { Meta, StoryObj } from "@storybook/react";
function FoundationShowcase() {
return (
<div className="min-h-screen bg-[var(--color-background)] px-6 py-10 text-[var(--color-foreground)] sm:px-10">
<div className="mx-auto flex w-full max-w-5xl flex-col gap-6">
<div className="max-w-3xl space-y-3">
<p className="text-sm uppercase tracking-[0.28em] text-[var(--color-muted-foreground)]">
AI UI / Phase 0
</p>
<h1 className="text-4xl font-semibold tracking-tight sm:text-5xl">
Monorepo scaffolding for a source-owned component system.
</h1>
<p className="max-w-2xl text-base leading-7 text-[var(--color-muted-foreground)] sm:text-lg">
The repo now has workspace packages for tokens, UI utilities, and docs.
The next phase can focus on component contracts instead of repo setup.
</p>
</div>
<div className="grid gap-4 md:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
<section className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
<div className="space-y-4">
<div className="flex items-center justify-between gap-4">
<h2 className="text-xl font-semibold">Workspace packages</h2>
<span className="rounded-full bg-[var(--color-primary)] px-3 py-1 text-xs font-medium text-[var(--color-primary-foreground)]">
ready
</span>
</div>
<div className="grid gap-3 sm:grid-cols-3">
{["packages/tokens", "packages/ui", "apps/docs"].map((item) => (
<div
key={item}
className={cn(
"rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-surface)] px-4 py-3 text-sm",
"transition-transform duration-200 ease-[var(--ease-standard)] hover:-translate-y-[2px]"
)}
>
{item}
</div>
))}
</div>
</div>
</section>
<aside className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-surface)] p-6 shadow-[var(--shadow-xs)]">
<div className="space-y-3">
<h2 className="text-xl font-semibold">Theme baseline</h2>
<ul className="space-y-2 text-sm text-[var(--color-muted-foreground)]">
{themeNames.map((themeName) => (
<li key={themeName} className="flex items-center justify-between gap-4">
<span>{themeName}</span>
<span className="font-medium text-[var(--color-foreground)]">enabled</span>
</li>
))}
</ul>
<div className="rounded-[var(--radius-md)] bg-[var(--color-background)] px-4 py-3 text-xs text-[var(--color-muted-foreground)]">
<span className="font-medium text-[var(--color-foreground)]">Motion base:</span>{" "}
{motionDurations.base}
</div>
</div>
</aside>
</div>
</div>
</div>
);
}
const meta = {
title: "Foundation/Phase 0",
component: FoundationShowcase
} satisfies Meta<typeof FoundationShowcase>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
+7
View File
@@ -0,0 +1,7 @@
@import "tailwindcss";
@import "@ai-ui/tokens/styles.css";
:root {
background: var(--color-background);
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": ["node"]
},
"include": [".storybook/**/*", "src/**/*"]
}
+38
View File
@@ -0,0 +1,38 @@
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";
export default tseslint.config(
{
ignores: ["dist/**", "node_modules/**", "storybook-static/**", "coverage/**"]
},
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.{ts,tsx,mts,cts}"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: {
...globals.browser,
...globals.node
}
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh
},
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": [
"warn",
{
allowConstantExport: true
}
]
}
}
);
+38
View File
@@ -0,0 +1,38 @@
{
"name": "ai-ui",
"private": true,
"packageManager": "pnpm@10.25.0",
"engines": {
"node": ">=24.0.0",
"pnpm": ">=10.0.0"
},
"scripts": {
"build": "pnpm --filter @ai-ui/tokens build && pnpm --filter @ai-ui/ui build",
"build:docs": "pnpm --filter @ai-ui/docs build-storybook",
"dev:docs": "pnpm --filter @ai-ui/docs storybook",
"lint": "eslint .",
"typecheck": "pnpm -r typecheck"
},
"devDependencies": {
"@eslint/js": "^9.39.4",
"@storybook/addon-a11y": "^8.6.14",
"@storybook/addon-essentials": "^8.6.14",
"@storybook/addon-interactions": "^8.6.14",
"@storybook/react": "^8.6.14",
"@storybook/react-vite": "^8.6.14",
"@tailwindcss/vite": "^4.2.2",
"@types/node": "^25.5.0",
"@types/react": "^18.3.28",
"@types/react-dom": "^18.3.7",
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"storybook": "^8.6.14",
"tailwindcss": "^4.2.2",
"tsup": "^8.5.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.57.1",
"vite": "^6.4.1"
}
}
+25
View File
@@ -0,0 +1,25 @@
{
"name": "@ai-ui/tokens",
"version": "0.0.0",
"private": true,
"type": "module",
"sideEffects": [
"**/*.css"
],
"exports": {
".": "./src/index.ts",
"./base.css": "./src/base.css",
"./motion.css": "./src/motion.css",
"./styles.css": "./src/styles.css",
"./tokens.css": "./src/tokens.css"
},
"files": [
"dist",
"src"
],
"scripts": {
"build": "tsup src/index.ts --clean --dts --format esm",
"typecheck": "tsc --noEmit -p tsconfig.json"
}
}
+26
View File
@@ -0,0 +1,26 @@
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
color-scheme: light;
}
body {
margin: 0;
min-height: 100vh;
background: var(--color-background);
color: var(--color-foreground);
font-family: var(--font-sans);
text-rendering: optimizeLegibility;
}
button,
input,
textarea,
select {
font: inherit;
}
+8
View File
@@ -0,0 +1,8 @@
export const themeNames = ["light", "dark"] as const;
export const motionScale = {
fast: "var(--dur-fast)",
base: "var(--dur-base)",
slow: "var(--dur-slow)"
} as const;
+32
View File
@@ -0,0 +1,32 @@
:root {
--dur-fast: 120ms;
--dur-base: 200ms;
--dur-slow: 320ms;
--ease-standard: cubic-bezier(0.22, 1, 0.36, 1);
--ease-emphasized: cubic-bezier(0.16, 1, 0.3, 1);
--distance-xs: 4px;
--distance-sm: 8px;
--distance-md: 16px;
--scale-press: 0.98;
}
@media (prefers-reduced-motion: reduce) {
:root {
--dur-fast: 1ms;
--dur-base: 1ms;
--dur-slow: 1ms;
}
*,
*::before,
*::after {
animation-duration: 1ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
transition-duration: 1ms !important;
}
}
+4
View File
@@ -0,0 +1,4 @@
@import "./base.css";
@import "./tokens.css";
@import "./motion.css";
+45
View File
@@ -0,0 +1,45 @@
:root,
[data-theme="light"] {
color-scheme: light;
--font-sans: "Avenir Next", "Segoe UI", sans-serif;
--font-mono: "SF Mono", "SFMono-Regular", "Consolas", monospace;
--color-background: oklch(0.985 0.004 85);
--color-foreground: oklch(0.24 0.03 60);
--color-surface: oklch(0.965 0.008 80);
--color-surface-strong: oklch(0.93 0.012 78);
--color-border: oklch(0.87 0.01 75);
--color-ring: oklch(0.56 0.12 32);
--color-primary: oklch(0.53 0.15 30);
--color-primary-foreground: oklch(0.98 0.01 80);
--color-muted: oklch(0.94 0.008 78);
--color-muted-foreground: oklch(0.42 0.028 60);
--color-accent: oklch(0.76 0.1 82);
--color-card: color-mix(in oklch, var(--color-surface) 86%, white 14%);
--color-card-foreground: var(--color-foreground);
--radius-sm: 10px;
--radius-md: 16px;
--radius-lg: 24px;
--shadow-xs: 0 1px 2px oklch(0.28 0.02 55 / 0.06);
--shadow-sm: 0 8px 24px oklch(0.28 0.02 55 / 0.08);
--shadow-md: 0 18px 48px oklch(0.28 0.03 55 / 0.12);
}
[data-theme="dark"] {
color-scheme: dark;
--color-background: oklch(0.2 0.015 60);
--color-foreground: oklch(0.94 0.01 80);
--color-surface: oklch(0.26 0.018 60);
--color-surface-strong: oklch(0.31 0.018 60);
--color-border: oklch(0.4 0.015 60);
--color-ring: oklch(0.7 0.12 35);
--color-primary: oklch(0.72 0.13 40);
--color-primary-foreground: oklch(0.22 0.014 60);
--color-muted: oklch(0.28 0.015 60);
--color-muted-foreground: oklch(0.8 0.02 72);
--color-accent: oklch(0.68 0.09 82);
--color-card: color-mix(in oklch, var(--color-surface) 90%, black 10%);
--color-card-foreground: var(--color-foreground);
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": ["node"]
},
"include": ["src/**/*"]
}
+31
View File
@@ -0,0 +1,31 @@
{
"name": "@ai-ui/ui",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"files": [
"dist",
"src"
],
"scripts": {
"build": "tsup src/index.ts --clean --dts --format esm,cjs",
"typecheck": "tsc --noEmit -p tsconfig.json"
},
"dependencies": {
"@ai-ui/tokens": "workspace:*",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"peerDependencies": {
"react": "^18.3.1 || ^19.0.0",
"react-dom": "^18.3.1 || ^19.0.0"
}
}
+4
View File
@@ -0,0 +1,4 @@
export { cn } from "./lib/cn";
export { cva, cx, type VariantProps } from "./lib/cva";
export { motionDurations, motionEasings } from "./lib/motion";
+7
View File
@@ -0,0 +1,7 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
+2
View File
@@ -0,0 +1,2 @@
export { cva, cx, type VariantProps } from "class-variance-authority";
+11
View File
@@ -0,0 +1,11 @@
export const motionDurations = {
fast: "var(--dur-fast)",
base: "var(--dur-base)",
slow: "var(--dur-slow)"
} as const;
export const motionEasings = {
standard: "var(--ease-standard)",
emphasized: "var(--ease-emphasized)"
} as const;
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": ["node"]
},
"include": ["src/**/*"]
}
+4466
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
packages:
- "apps/*"
- "packages/*"
+24
View File
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Bundler",
"allowArbitraryExtensions": true,
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@ai-ui/ui": ["packages/ui/src/index.ts"],
"@ai-ui/tokens": ["packages/tokens/src/index.ts"],
"@ai-ui/tokens/*": ["packages/tokens/src/*"]
}
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"types": ["node"]
},
"include": []
}