npm package: @redrob-ui/vue
Description: Vue 3 components for Redrob AI Design System
Author: Redrob AI
Latest version: 0.1.2

# @redrob-ui/vue

Vue 3 components for the Redrob AI Design System, built with Vite and Tailwind CSS.

## 🚨 Important: Complete Setup Required

**Your styles won't work without proper setup!** Follow ALL steps below.

## Installation

```bash
npm install @redrob-ui/vue @redrob-ui/tailwind-config
# or
yarn add @redrob-ui/vue @redrob-ui/tailwind-config
# or
pnpm add @redrob-ui/vue @redrob-ui/tailwind-config
```

## Setup (Critical Steps)

### Step 1: Configure Tailwind CSS

Create or update `tailwind.config.js` in your project root:

```js
// tailwind.config.js
const redrobConfig = require('@redrob-ui/tailwind-config');

module.exports = {
  presets: [redrobConfig],
  content: [
    './src/**/*.{vue,js,ts,jsx,tsx}',
    './index.html',
    // 👇 THIS IS CRITICAL - Include the component library files
    './node_modules/@redrob-ui/vue/**/*.{vue,js,ts,jsx,tsx,mjs}',
  ],
};
```

**⚠️ Common Issue:** If styles don't work, it's usually because the `node_modules/@redrob-ui/vue/**/*.{vue,js,ts,jsx,tsx,mjs}` line is missing from `content`.

### Step 2: Import Styles

Import the component styles **AND** Tailwind directives in your main CSS file:

```css
/* src/style.css or src/main.css */
@import '@redrob-ui/vue/styles.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
```

### Step 3: Import CSS in Your App

Make sure to import your CSS file in your main entry point:

```ts
// src/main.ts
import { createApp } from 'vue';
import './style.css'; // 👈 Import your CSS file
import App from './App.vue';

createApp(App).mount('#app');
```

## Basic Usage

```vue
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Card, Input } from '@redrob-ui/vue';

const email = ref('');

const handleSubmit = () => {
  console.log('Email:', email.value);
};
</script>

<template>
  <Card>
    <h1>Welcome to Redrob UI</h1>
    <Input 
      v-model="email" 
      label="Email" 
      placeholder="you@example.com"
      helper-text="We'll never share your email"
    />
    <Button variant="primary" @click="handleSubmit">
      Submit
    </Button>
  </Card>
</template>
```

## Components

### Button

A versatile button component with multiple variants and states.

```vue
<template>
  <Button 
    variant="primary" 
    size="md" 
    :full-width="false" 
    :is-loading="false"
    :disabled="false"
  >
    Click me
  </Button>
</template>
```

**Props:**

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `variant` | `'primary' \| 'secondary' \| 'outline' \| 'ghost' \| 'danger'` | `'primary'` | Visual style variant |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size |
| `fullWidth` | `boolean` | `false` | Make button full width |
| `isLoading` | `boolean` | `false` | Show loading spinner |
| `disabled` | `boolean` | `false` | Disable the button |

**Slots:**

```vue
<template>
  <!-- Left icon slot -->
  <Button>
    <template #leftIcon>
      <HomeIcon class="w-4 h-4" />
    </template>
    Home
  </Button>

  <!-- Right icon slot -->
  <Button variant="secondary">
    Search
    <template #rightIcon>
      <SearchIcon class="w-4 h-4" />
    </template>
  </Button>
</template>

<script setup lang="ts">
import { Button } from '@redrob-ui/vue';
import { HomeIcon, SearchIcon } from '@redrob-ui/icons';
</script>
```

**Examples:**

```vue
<template>
  <div class="flex flex-col gap-4">
    <!-- Variants -->
    <Button variant="primary">Primary</Button>
    <Button variant="secondary">Secondary</Button>
    <Button variant="outline">Outline</Button>
    <Button variant="ghost">Ghost</Button>
    <Button variant="danger">Danger</Button>

    <!-- Sizes -->
    <Button size="sm">Small</Button>
    <Button size="md">Medium</Button>
    <Button size="lg">Large</Button>

    <!-- States -->
    <Button :is-loading="true">Loading...</Button>
    <Button :disabled="true">Disabled</Button>
    <Button :full-width="true">Full Width</Button>
  </div>
</template>
```

### Card

A container component with consistent padding and shadow.

```vue
<template>
  <Card padding="md" :shadow="true">
    <h2>Card Title</h2>
    <p>Card content goes here</p>
  </Card>
</template>
```

**Props:**

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `padding` | `'none' \| 'sm' \| 'md' \| 'lg'` | `'md'` | Internal padding |
| `shadow` | `boolean` | `true` | Show drop shadow |

**Examples:**

```vue
<template>
  <div class="grid gap-4">
    <!-- Standard card -->
    <Card>
      <h3 class="text-lg font-semibold mb-2">Standard Card</h3>
      <p class="text-neutral-600">With default padding and shadow</p>
    </Card>

    <!-- Large padding -->
    <Card padding="lg">
      <h3 class="text-lg font-semibold mb-2">Large Padding</h3>
      <p class="text-neutral-600">More spacious layout</p>
    </Card>

    <!-- No shadow -->
    <Card :shadow="false">
      <h3 class="text-lg font-semibold mb-2">Flat Card</h3>
      <p class="text-neutral-600">Without shadow</p>
    </Card>
  </div>
</template>
```

### Input

Form input component with label, helper text, and error states.

```vue
<template>
  <Input
    v-model="value"
    label="Email"
    placeholder="Enter your email"
    helper-text="We'll never share your email"
    :error="errorMessage"
    size="md"
    :disabled="false"
    type="text"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';

const value = ref('');
const errorMessage = ref('');
</script>
```

**Props:**

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `modelValue` | `string` | `''` | Input value (use with `v-model`) |
| `label` | `string` | `undefined` | Input label |
| `placeholder` | `string` | `undefined` | Placeholder text |
| `helperText` | `string` | `undefined` | Helper text below input |
| `error` | `string` | `undefined` | Error message (shows red) |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Input size |
| `disabled` | `boolean` | `false` | Disable input |
| `type` | `string` | `'text'` | HTML input type |

**Events:**

- `update:modelValue` - Emitted when value changes (use with `v-model`)

**Examples:**

```vue
<template>
  <div class="flex flex-col gap-4">
    <!-- Basic input -->
    <Input 
      v-model="name" 
      label="Name" 
      placeholder="John Doe"
    />

    <!-- With helper text -->
    <Input 
      v-model="email" 
      label="Email"
      type="email"
      placeholder="you@example.com"
      helper-text="We'll never share your email"
    />

    <!-- With error -->
    <Input 
      v-model="password" 
      label="Password"
      type="password"
      :error="passwordError"
    />

    <!-- Disabled -->
    <Input 
      v-model="username" 
      label="Username"
      :disabled="true"
    />

    <!-- Different sizes -->
    <Input v-model="small" label="Small" size="sm" />
    <Input v-model="medium" label="Medium" size="md" />
    <Input v-model="large" label="Large" size="lg" />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { Input } from '@redrob-ui/vue';

const name = ref('');
const email = ref('');
const password = ref('');
const passwordError = ref('Password must be at least 8 characters');
const username = ref('locked_user');
const small = ref('');
const medium = ref('');
const large = ref('');
</script>
```

## Form Example

Complete form with validation:

```vue
<template>
  <Card>
    <h2 class="text-2xl font-bold mb-6">Sign Up</h2>
    
    <form @submit.prevent="handleSubmit" class="flex flex-col gap-4">
      <Input
        v-model="form.name"
        label="Full Name"
        placeholder="John Doe"
        :error="errors.name"
      />
      
      <Input
        v-model="form.email"
        label="Email"
        type="email"
        placeholder="you@example.com"
        :error="errors.email"
        helper-text="We'll never share your email"
      />
      
      <Input
        v-model="form.password"
        label="Password"
        type="password"
        :error="errors.password"
        helper-text="Must be at least 8 characters"
      />

      <div class="flex gap-2 mt-4">
        <Button 
          type="submit" 
          variant="primary" 
          :is-loading="isSubmitting"
          :full-width="true"
        >
          Sign Up
        </Button>
      </div>
    </form>
  </Card>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue';
import { Button, Card, Input } from '@redrob-ui/vue';

const form = reactive({
  name: '',
  email: '',
  password: '',
});

const errors = reactive({
  name: '',
  email: '',
  password: '',
});

const isSubmitting = ref(false);

const validateForm = () => {
  let isValid = true;
  
  // Reset errors
  errors.name = '';
  errors.email = '';
  errors.password = '';
  
  // Validate name
  if (!form.name.trim()) {
    errors.name = 'Name is required';
    isValid = false;
  }
  
  // Validate email
  if (!form.email.trim()) {
    errors.email = 'Email is required';
    isValid = false;
  } else if (!/\S+@\S+\.\S+/.test(form.email)) {
    errors.email = 'Email is invalid';
    isValid = false;
  }
  
  // Validate password
  if (!form.password) {
    errors.password = 'Password is required';
    isValid = false;
  } else if (form.password.length < 8) {
    errors.password = 'Password must be at least 8 characters';
    isValid = false;
  }
  
  return isValid;
};

const handleSubmit = async () => {
  if (!validateForm()) return;
  
  isSubmitting.value = true;
  
  try {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 2000));
    console.log('Form submitted:', form);
    
    // Reset form
    form.name = '';
    form.email = '';
    form.password = '';
  } catch (error) {
    console.error('Submission error:', error);
  } finally {
    isSubmitting.value = false;
  }
};
</script>
```

## TypeScript Support

Full TypeScript support with exported types:

```vue
<script setup lang="ts">
import type { ButtonProps, CardProps, InputProps } from '@redrob-ui/vue';
import { Button, Card, Input } from '@redrob-ui/vue';

// Use types for custom wrappers
const myButtonProps: ButtonProps = {
  variant: 'primary',
  size: 'lg',
  fullWidth: true,
};
</script>

<template>
  <Button v-bind="myButtonProps">
    Custom Button
  </Button>
</template>
```

## Customization

### Using with Icons

```bash
npm install @redrob-ui/icons
```

```vue
<template>
  <Button variant="primary">
    <template #leftIcon>
      <HomeIcon class="w-4 h-4" />
    </template>
    Go Home
  </Button>
</template>

<script setup lang="ts">
import { Button } from '@redrob-ui/vue';
import { HomeIcon } from '@redrob-ui/icons';
</script>
```

### Custom Styling

You can add custom classes to any component:

```vue
<template>
  <Button class="my-custom-class">
    Custom Button
  </Button>
  
  <Card class="border-2 border-primary-500">
    Custom Card
  </Card>
</template>
```

### Extending Tailwind Theme

```js
// tailwind.config.js
const redrobConfig = require('@redrob-ui/tailwind-config');

module.exports = {
  presets: [redrobConfig],
  theme: {
    extend: {
      colors: {
        brand: '#your-color',
      },
    },
  },
  content: [
    './src/**/*.{vue,js,ts,jsx,tsx}',
    './node_modules/@redrob-ui/vue/**/*.{vue,js,ts,jsx,tsx,mjs}',
  ],
};
```

## Troubleshooting

### Styles Not Working?

**Problem:** Components render but have no styling.

**Solutions:**

1. **Check Tailwind config** - Make sure you have the `node_modules/@redrob-ui/vue/**/*.{vue,js,ts,jsx,tsx,mjs}` path in your `content` array.

2. **Import styles** - Ensure you're importing `@redrob-ui/vue/styles.css` in your CSS file.

3. **Import CSS file** - Make sure your CSS file is imported in `main.ts`.

4. **Check package names** - Verify you're using `@redrob-ui/tailwind-config` (not `@redrob/tailwind-config`).

5. **Rebuild packages** - If working in a monorepo, rebuild the packages:
   ```bash
   npm run build --workspace=@redrob-ui/tailwind-config
   npm run build --workspace=@redrob-ui/vue
   ```

### TypeScript Errors?

Make sure you have Vue TypeScript support:

```bash
npm install -D @vue/tsconfig vue-tsc
```

### Vite Not Finding Modules?

If using Vite, make sure your `vite.config.ts` is properly configured:

```ts
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  optimizeDeps: {
    include: ['@redrob-ui/vue'],
  },
});
```

## Development

### Start dev server

```bash
npm run dev
```

This starts the Vite dev server at http://localhost:3001 with Hot Module Replacement (HMR).

### Build

```bash
npm run build
```

Builds the library to `dist/` folder with:
- ES modules (.mjs)
- CommonJS (.js)
- TypeScript declarations (.d.ts)
- Compiled CSS (styles.css)

### Preview

```bash
npm run preview
```

Preview the production build locally.

## Storybook

To see all components in action, check out the Storybook demo:

```bash
# From the repository root
npm run dev --workspace=@redrob-ui/demo
```

Visit http://localhost:6006 to browse all components with interactive examples.

## Best Practices

1. **Import styles once** - Only import `@redrob-ui/vue/styles.css` once in your main CSS file
2. **Use v-model** - For Input components, always use `v-model` for two-way binding
3. **Leverage variants** - Use component variants instead of custom styling when possible
4. **Type safety** - Use TypeScript for better developer experience
5. **Accessibility** - Components follow accessibility best practices by default

## Complete Minimal Setup Example

Here's a complete working example from scratch:

**1. Install dependencies:**

```bash
npm create vue@latest my-app
cd my-app
npm install @redrob-ui/vue @redrob-ui/tailwind-config tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

**2. Configure Tailwind (`tailwind.config.js`):**

```js
const redrobConfig = require('@redrob-ui/tailwind-config');

module.exports = {
  presets: [redrobConfig],
  content: [
    './index.html',
    './src/**/*.{vue,js,ts,jsx,tsx}',
    './node_modules/@redrob-ui/vue/**/*.{vue,js,ts,jsx,tsx,mjs}',
  ],
};
```

**3. Create CSS file (`src/style.css`):**

```css
@import '@redrob-ui/vue/styles.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
```

**4. Update main.ts (`src/main.ts`):**

```ts
import { createApp } from 'vue';
import './style.css';
import App from './App.vue';

createApp(App).mount('#app');
```

**5. Use components (`src/App.vue`):**

```vue
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Card, Input } from '@redrob-ui/vue';

const name = ref('');
</script>

<template>
  <div class="min-h-screen bg-neutral-50 p-8">
    <Card class="max-w-md mx-auto">
      <h1 class="text-2xl font-bold mb-4">Hello Redrob UI!</h1>
      <Input v-model="name" label="Your Name" placeholder="Enter name" />
      <Button variant="primary" class="mt-4">Submit</Button>
    </Card>
  </div>
</template>
```

**6. Run dev server:**

```bash
npm run dev
```

That's it! You should see styled components. ✅

## Related Packages

- [@redrob-ui/tokens](../tokens) - Design tokens
- [@redrob-ui/tailwind-config](../tailwind-config) - Tailwind preset
- [@redrob-ui/icons](../icons) - Icon library
- [@redrob-ui/react](../components) - React components

## Examples

See the [Storybook demo](../../packages/demo) for complete implementation examples.

## License

MIT