diff --git a/.gitignore b/.gitignore
index a10e7d2..c63410e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@ node_modules
*storybook.log
storybook-static
+
+dist
diff --git a/README.md b/README.md
index bc8db86..a055426 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,62 @@
# @foomo/ui
-UI - just the basics.
\ No newline at end of file
+A modern React component library built with Tailwind CSS v4, TypeScript, and Radix UI primitives.
+
+## Features
+
+- 🎨 Built with Tailwind CSS v4
+- 🔧 Full TypeScript support
+- 📦 Tree-shakeable - import only what you need
+- 🎯 No barrel exports - better performance
+- 🌗 Dark mode support
+- ♿ Accessible components via Radix UI
+
+## Installation
+
+```bash
+npm install @foomo/ui
+```
+
+## Usage
+
+Import the CSS file once in your app:
+
+```tsx
+// In your app's entry file (e.g., App.tsx or main.tsx)
+import "@foomo/ui/ui.css";
+```
+
+Import components individually:
+
+```tsx
+import { Button } from "@foomo/ui/components/button";
+import { cn } from "@foomo/ui/lib/utils";
+
+function App() {
+ return (
+
tr]:last:border-b-0",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
+ return (
+
+ )
+}
+
+function TableHead({ className, ...props }: React.ComponentProps<"th">) {
+ return (
+ [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableCell({ className, ...props }: React.ComponentProps<"td">) {
+ return (
+ | [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableCaption({
+ className,
+ ...props
+}: React.ComponentProps<"caption">) {
+ return (
+
+ )
+}
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/src/stories/Button.stories.tsx b/src/stories/Button.stories.tsx
index 48c7153..81d65fd 100644
--- a/src/stories/Button.stories.tsx
+++ b/src/stories/Button.stories.tsx
@@ -1,34 +1,146 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { fn } from 'storybook/test';
-import { Button } from '@/components/ui/button';
+import { Button } from '../components/button';
-
-
-// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
title: 'Button',
component: Button,
parameters: {
- // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
},
- // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
- // More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {
- // backgroundColor: { control: 'color' },
+ variant: {
+ control: 'select',
+ options: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'],
+ },
+ size: {
+ control: 'select',
+ options: ['default', 'sm', 'lg', 'icon'],
+ },
+ disabled: {
+ control: 'boolean',
+ },
},
- // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
args: { onClick: fn() },
} satisfies Meta;
export default meta;
type Story = StoryObj;
-// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
-export const Primary: Story = {
- render: (args) => ,
+export const Default: Story = {
args: {
- variant: "ghost",
+ children: 'Button',
},
};
+
+export const Secondary: Story = {
+ args: {
+ variant: 'secondary',
+ children: 'Secondary',
+ },
+};
+
+export const Destructive: Story = {
+ args: {
+ variant: 'destructive',
+ children: 'Destructive',
+ },
+};
+
+export const Outline: Story = {
+ args: {
+ variant: 'outline',
+ children: 'Outline',
+ },
+};
+
+export const Ghost: Story = {
+ args: {
+ variant: 'ghost',
+ children: 'Ghost',
+ },
+};
+
+export const Link: Story = {
+ args: {
+ variant: 'link',
+ children: 'Link',
+ },
+};
+
+export const Small: Story = {
+ args: {
+ size: 'sm',
+ children: 'Small',
+ },
+};
+
+export const Large: Story = {
+ args: {
+ size: 'lg',
+ children: 'Large',
+ },
+};
+
+export const Icon: Story = {
+ args: {
+ size: 'icon',
+ children: '🔍',
+ },
+};
+
+export const Disabled: Story = {
+ args: {
+ disabled: true,
+ children: 'Disabled',
+ },
+};
+
+export const WithIcon: Story = {
+ render: (args) => (
+
+ ),
+ args: {
+ variant: 'default',
+ },
+};
+
+export const AllVariants: Story = {
+ render: () => (
+
+
+
+
+
+
+
+
+ ),
+};
+
+export const AllSizes: Story = {
+ render: () => (
+
+
+
+
+
+
+ ),
+};
diff --git a/src/stories/Table.stories.tsx b/src/stories/Table.stories.tsx
new file mode 100644
index 0000000..27fd8fb
--- /dev/null
+++ b/src/stories/Table.stories.tsx
@@ -0,0 +1,91 @@
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption
+} from '../components/table';
+
+const meta = {
+ title: 'Table',
+ component: Table,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => (
+
+ A list of your recent invoices.
+
+
+ Invoice
+ Status
+ Method
+ Amount
+
+
+
+
+ INV001
+ Paid
+ Credit Card
+ $250.00
+
+
+ INV002
+ Pending
+ PayPal
+ $150.00
+
+
+ INV003
+ Unpaid
+ Bank Transfer
+ $350.00
+
+
+
+ ),
+};
+
+export const WithFooter: Story = {
+ render: () => (
+
+
+
+ Product
+ Quantity
+ Price
+
+
+
+
+ Widget A
+ 5
+ $25.00
+
+
+ Widget B
+ 3
+ $15.00
+
+
+
+
+ Total
+ $40.00
+
+
+
+ ),
+};
\ No newline at end of file
diff --git a/src/styles.ts b/src/styles.ts
new file mode 100644
index 0000000..835da9c
--- /dev/null
+++ b/src/styles.ts
@@ -0,0 +1,5 @@
+// Import the main CSS file to ensure it's bundled
+import './index.css';
+
+// This file exists solely to bundle the CSS
+export {};
\ No newline at end of file
diff --git a/tsconfig.build.json b/tsconfig.build.json
new file mode 100644
index 0000000..3e118a5
--- /dev/null
+++ b/tsconfig.build.json
@@ -0,0 +1,13 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "noEmit": false,
+ "declaration": true,
+ "declarationMap": true,
+ "emitDeclarationOnly": true,
+ "outDir": "./dist",
+ "rootDir": "./src"
+ },
+ "include": ["src"],
+ "exclude": ["src/**/*.stories.tsx", "src/**/*.test.ts", "src/**/*.test.tsx"]
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index 3a3e60b..fb5328a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,7 +12,7 @@
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
+ "allowImportingTsExtensions": false,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
diff --git a/vite.config.ts b/vite.config.ts
index 149fa0b..fe97f80 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,43 +1,82 @@
///
-import tailwindcss from "@tailwindcss/vite";
-import react from "@vitejs/plugin-react";
-import path from "path";
import { defineConfig } from "vite";
-
+import path from "node:path";
+import react from "@vitejs/plugin-react";
+import tailwindcss from "@tailwindcss/vite";
+import { glob } from "glob";
+import { fileURLToPath } from "node:url";
+import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
// https://vite.dev/config/
-import { fileURLToPath } from 'node:url';
-import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
-const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
-
-// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
+const dirname =
+ typeof __dirname !== "undefined"
+ ? __dirname
+ : path.dirname(fileURLToPath(import.meta.url));
export default defineConfig({
- plugins: [react(), tailwindcss()],
- resolve: {
- alias: {
- "@": path.resolve(__dirname, "./src")
- }
- },
- test: {
- projects: [{
- extends: true,
- plugins: [
- // The plugin will run tests for the stories defined in your Storybook config
- // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
- storybookTest({
- configDir: path.join(dirname, '.storybook')
- })],
- test: {
- name: 'storybook',
- browser: {
- enabled: true,
- headless: true,
- provider: 'playwright',
- instances: [{
- browser: 'chromium'
- }]
- },
- setupFiles: ['.storybook/vitest.setup.ts']
- }
- }]
- }
-});
\ No newline at end of file
+ plugins: [react(), tailwindcss()],
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src"),
+ },
+ },
+ test: {
+ projects: [
+ {
+ extends: true,
+ plugins: [
+ // The plugin will run tests for the stories defined in your Storybook config
+ // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
+ storybookTest({
+ configDir: path.join(dirname, ".storybook"),
+ }),
+ ],
+ test: {
+ name: "storybook",
+ browser: {
+ enabled: true,
+ headless: true,
+ provider: "playwright",
+ instances: [
+ {
+ browser: "chromium",
+ },
+ ],
+ },
+ setupFiles: [".storybook/vitest.setup.ts"],
+ },
+ },
+ ],
+ },
+ build: {
+ lib: {
+ entry: Object.fromEntries(
+ glob
+ .sync("src/**/*.{ts,tsx}", {
+ ignore: ["**/*.d.ts", "**/*.stories.*", "**/*.test.*"],
+ })
+ .map((file) => [
+ file.slice(4, file.length - path.extname(file).length),
+ path.resolve(__dirname, file),
+ ]),
+ ),
+ formats: ["es", "cjs"],
+ },
+ cssCodeSplit: false,
+ rollupOptions: {
+ external: [
+ "react",
+ "react-dom",
+ "react/jsx-runtime",
+ "clsx",
+ "tailwind-merge",
+ "class-variance-authority",
+ "@radix-ui/react-slot",
+ ],
+ output: {
+ preserveModules: true,
+ preserveModulesRoot: "src",
+ },
+ },
+ sourcemap: true,
+ emptyOutDir: true,
+ },
+});
|