feat: initial monorepo setup with Turborepo, Next.js 16, and .ts-only components

- Set up Turborepo monorepo with pnpm workspaces
- Configure shared configs (Biome, ESLint, Tailwind, TypeScript)
- Create @tpmjs/ui package with .ts-only React components (Button, Card)
- Create @tpmjs/utils package with utility functions
- Create @tpmjs/types package with Zod schemas
- Create @tpmjs/env package for environment validation
- Set up Next.js 16 App Router application
- Configure Storybook in separate package
- Add GitHub Actions CI/CD workflows
- Configure Changesets for versioning and publishing
- Set up Lefthook pre-commit hooks
- Add comprehensive documentation

Key architecture decisions:
- All React components use .ts extension with createElement (no JSX)
- No barrel exports (index.ts) - direct imports required
- Module boundaries enforced via ESLint
- tsup for package builds with ESM + types
- Tailwind CSS with shared design tokens

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-11-25 21:59:15 +10:00
commit bc2f4a01cf
9 changed files with 218 additions and 0 deletions

View file

@ -0,0 +1,19 @@
import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
stories: ["../stories/**/*.stories.@(ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
],
framework: {
name: "@storybook/react-vite",
options: {},
},
docs: {
autodocs: "tag",
},
};
export default config;

View file

@ -0,0 +1,15 @@
import type { Preview } from "@storybook/react";
import "../styles/globals.css";
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;

View file

@ -0,0 +1,33 @@
{
"name": "@tpmjs/storybook",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "storybook dev -p 6006",
"build": "storybook build",
"clean": "rm -rf storybook-static .turbo"
},
"dependencies": {
"@tpmjs/ui": "workspace:*",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@storybook/addon-essentials": "^8.5.0",
"@storybook/addon-interactions": "^8.5.0",
"@storybook/addon-links": "^8.5.0",
"@storybook/blocks": "^8.5.0",
"@storybook/react": "^8.5.0",
"@storybook/react-vite": "^8.5.0",
"@storybook/test": "^8.5.0",
"@tpmjs/tailwind-config": "workspace:*",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"autoprefixer": "^10.4.20",
"postcss": "^8.5.1",
"storybook": "^8.5.0",
"tailwindcss": "^3.4.17",
"vite": "^6.0.7"
}
}

View file

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View file

@ -0,0 +1,69 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "@tpmjs/ui/Button/Button";
const meta = {
title: "Components/Button",
component: Button,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: "Button",
},
};
export const Destructive: Story = {
args: {
variant: "destructive",
children: "Delete",
},
};
export const Outline: Story = {
args: {
variant: "outline",
children: "Outline",
},
};
export const Secondary: Story = {
args: {
variant: "secondary",
children: "Secondary",
},
};
export const Ghost: Story = {
args: {
variant: "ghost",
children: "Ghost",
},
};
export const Link: Story = {
args: {
variant: "link",
children: "Link",
},
};
export const Large: Story = {
args: {
size: "lg",
children: "Large Button",
},
};
export const Small: Story = {
args: {
size: "sm",
children: "Small Button",
},
};

View file

@ -0,0 +1,39 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Card, CardContent, CardHeader, CardTitle } from "@tpmjs/ui/Card/Card";
import { createElement } from "react";
const meta = {
title: "Components/Card",
component: Card,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof Card>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () =>
createElement(
Card,
{ className: "w-[350px]" },
createElement(CardHeader, {}, createElement(CardTitle, {}, "Card Title")),
createElement(CardContent, {}, "This is the card content."),
),
};
export const WithLongContent: Story = {
render: () =>
createElement(
Card,
{ className: "w-[400px]" },
createElement(CardHeader, {}, createElement(CardTitle, {}, "TPMJS")),
createElement(
CardContent,
{},
"Tool Package Manager for AI Agents. The registry for AI tools. Discover, share, and integrate tools that give your agents superpowers.",
),
),
};

View file

@ -0,0 +1,26 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
}

View file

@ -0,0 +1,7 @@
import baseConfig from "@tpmjs/tailwind-config/base";
import type { Config } from "tailwindcss";
export default {
...baseConfig,
content: ["./stories/**/*.{ts,tsx}", "../ui/src/**/*.{ts,tsx}"],
} satisfies Config;

View file

@ -0,0 +1,4 @@
{
"extends": "@tpmjs/tsconfig/react-library.json",
"include": [".storybook", "stories"]
}