npm package: @johngarcia9110/invariant
Description: A reusable React component library with Tailwind CSS
Author: John Garcia
Latest version: 0.1.5

# @johngarcia9110/invariant

A reusable React component library built with TypeScript and Tailwind CSS.

## Installation

```bash
npm install @johngarcia9110/invariant
```

**Peer Dependencies:** React 18+ is required. This library uses Tailwind CSS v4.

## Usage

CSS is automatically injected when you import components - no additional setup required.

```tsx
import {
  Button,
  Card,
  CardHeader,
  CardBody,
  Input,
} from '@johngarcia9110/invariant';

function App() {
  return (
    <Card>
      <CardHeader>
        <h2>Sign In</h2>
      </CardHeader>
      <CardBody>
        <Input label="Email" type="email" placeholder="Enter your email" />
        <Input label="Password" type="password" />
        <Button variant="primary" size="md">
          Sign In
        </Button>
      </CardBody>
    </Card>
  );
}
```

## Components

### Button

```tsx
<Button variant="primary" size="md">Click me</Button>
<Button variant="secondary" size="sm">Secondary</Button>
<Button variant="outline" size="lg">Outline</Button>
<Button isLoading>Submitting</Button>
<Button isLoading loadingText="Saving...">Save</Button>
```

**Props:**

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `variant` | `'primary'` \| `'secondary'` \| `'outline'` | `'primary'` | Button style variant |
| `size` | `'sm'` \| `'md'` \| `'lg'` | `'md'` | Button size |
| `isLoading` | `boolean` | `false` | Shows loading spinner and disables button |
| `loadingText` | `string` | `'Loading...'` | Text displayed while loading |

Plus all standard button HTML attributes.

### Card

```tsx
<Card>
  <CardHeader>Header content</CardHeader>
  <CardBody>Body content</CardBody>
  <CardFooter>Footer content</CardFooter>
</Card>
```

### Input

```tsx
<Input
  label="Username"
  placeholder="Enter username"
  helperText="Must be at least 3 characters"
/>

<Input label="Email" error="Invalid email address" />
<Input label="Email" success="Email is available" />
<Input size="lg" leadingIcon={<SearchIcon />} placeholder="Search..." />
<Input prefix="$" suffix="USD" placeholder="0.00" />
<Input isLoading placeholder="Loading..." />
```

**Props:**

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `label` | `string` | - | Label text above input |
| `error` | `string` | - | Error message (red styling) |
| `success` | `string` | - | Success message (green styling) |
| `helperText` | `string` | - | Helper text below input |
| `size` | `'sm'` \| `'md'` \| `'lg'` | `'md'` | Input size |
| `leadingIcon` | `ReactNode` | - | Icon at start of input |
| `trailingIcon` | `ReactNode` | - | Icon at end of input |
| `prefix` | `ReactNode` | - | Prefix content (e.g., "$") |
| `suffix` | `ReactNode` | - | Suffix content (e.g., "USD") |
| `isLoading` | `boolean` | `false` | Shows loading spinner |

Plus all standard input HTML attributes.

## Utilities

### cn()

A utility function for merging Tailwind classes:

```tsx
import { cn } from '@johngarcia9110/invariant'

<div className={cn('p-4', isActive && 'bg-blue-500', className)}>
```

## Theme

### ThemeProvider

Wrap your app with `ThemeProvider` to enable theme management:

```tsx
import { ThemeProvider } from '@johngarcia9110/invariant';

function App() {
  return (
    <ThemeProvider defaultMode="light">
      <YourApp />
    </ThemeProvider>
  );
}
```

**Props:**

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `mode` | `'light'` \| `'dark'` \| `'system'` | - | Controlled theme mode |
| `defaultMode` | `'light'` \| `'dark'` | `'light'` | Default mode (uncontrolled) |
| `theme` | `ThemeConfig` | - | Light mode color overrides |
| `darkTheme` | `ThemeConfig` | - | Dark mode color overrides |
| `storageKey` | `string` | `'invariant-theme-mode'` | localStorage key for persistence |
| `disableDarkClass` | `boolean` | `false` | Disable `.dark` class on `<html>` |

### useTheme Hook

Access and control the theme from any component:

```tsx
import { useTheme, Button } from '@johngarcia9110/invariant';

function ThemeToggle() {
  const { mode, setMode, toggleMode } = useTheme();

  return (
    <div>
      <p>Current theme: {mode}</p>
      <Button onClick={toggleMode}>Toggle Theme</Button>
      <Button onClick={() => setMode('system')}>Use System</Button>
    </div>
  );
}
```

**Returns:**

| Property | Type | Description |
|----------|------|-------------|
| `mode` | `'light'` \| `'dark'` | Current effective theme |
| `configuredMode` | `'light'` \| `'dark'` \| `'system'` | User's configured preference |
| `setMode` | `(mode) => void` | Set theme to light, dark, or system |
| `toggleMode` | `() => void` | Toggle between light and dark |
| `colors` | `ThemeColors` | Current resolved color values |

### Custom Colors

Override theme colors using the `theme` and `darkTheme` props:

```tsx
<ThemeProvider
  theme={{
    colors: {
      primary: 'oklch(0.6 0.2 250)',
      secondary: 'oklch(0.7 0.15 180)',
    }
  }}
  darkTheme={{
    colors: {
      primary: 'oklch(0.7 0.2 250)',
    }
  }}
>
  <App />
</ThemeProvider>
```

Available color tokens: `background`, `foreground`, `primary`, `primaryForeground`, `secondary`, `secondaryForeground`, `destructive`, `muted`, `mutedForeground`, `accent`, `accentForeground`, `border`, `input`, `ring`, `card`, `cardForeground`, `popover`, `popoverForeground`.

### CSS Variables

You can also override colors directly in CSS:

```css
:root {
  --primary: oklch(0.205 0 0);
  --primary-foreground: oklch(0.985 0 0);
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
}
```

### Dark Mode

The `ThemeProvider` automatically manages dark mode by:
- Adding/removing the `.dark` class on `<html>`
- Persisting user preference to localStorage
- Detecting system preference when `mode="system"`

## TypeScript

Types are included and exported from the package:

```tsx
import type {
  // Component props
  ButtonProps,
  CardProps,
  InputProps,
  // Theme types
  ThemeProviderProps,
  ThemeConfig,
  ThemeColors,
  ThemeMode,
  ThemeContextValue,
} from '@johngarcia9110/invariant';
```

## License

MIT