Claude Code Plugins

Community-maintained marketplace

Feedback

component-development

@doitsu2014/my-blogs-rsbuild
0
0

Create and modify React components following project patterns. Use when building UI components, forms, layouts, navigation, or implementing React hooks. Includes DaisyUI, Tailwind CSS, and lucide-react icons.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name component-development
description Create and modify React components following project patterns. Use when building UI components, forms, layouts, navigation, or implementing React hooks. Includes DaisyUI, Tailwind CSS, and lucide-react icons.

Component Development

Overview

This skill helps you create and modify React components following the project's established patterns and conventions. The project uses React 19, TypeScript, DaisyUI, Tailwind CSS, and lucide-react icons.

Project Structure

Component Organization

src/
├── app/
│   └── admin/
│       ├── components/          # Reusable admin components
│       │   ├── inputs/          # Form input components
│       │   ├── skeleton/        # Loading skeletons
│       │   ├── left-menu.tsx    # Navigation menu
│       │   ├── top-bar.tsx      # Top navigation bar
│       │   ├── menu-item.tsx    # Menu item component
│       │   └── my-breadcrumbs.tsx # Breadcrumb navigation
│       ├── blogs/               # Blog-related pages
│       ├── categories/          # Category-related pages
│       ├── layout.tsx           # Admin layout wrapper
│       ├── layoutContext.tsx    # Layout context
│       └── layoutMain.tsx       # Main layout component
├── auth/                        # Auth components
│   ├── AuthContext.tsx          # Auth context provider
│   └── ProtectedRoute.tsx       # Route protection
└── domains/                     # Domain models/types

Technology Stack

Core

  • React: 19.1.x (latest)
  • TypeScript: 5.9.x
  • React Router: 7.9.x for routing

UI Framework

  • DaisyUI: 5.0.x - Component library
  • Tailwind CSS: 4.0.x - Utility-first CSS
  • lucide-react: 0.476.x - Icon library

Form Components

  • Quill: 2.0.x - Rich text editor
  • Custom form components in src/app/admin/components/inputs/

Component Patterns

Functional Components with TypeScript

import { useState } from 'react';
import type { ComponentProps } from 'react';

interface MyComponentProps {
  title: string;
  onSubmit: (data: FormData) => void;
  optional?: string;
}

export default function MyComponent({
  title,
  onSubmit,
  optional
}: MyComponentProps) {
  const [state, setState] = useState('');

  return (
    <div className="container">
      <h1>{title}</h1>
      {/* Component JSX */}
    </div>
  );
}

Named Exports for Utilities

// For utility functions and constants
export function helperFunction() { }
export const CONSTANT_VALUE = 'value';

// Default export for main component
export default function MainComponent() { }

DaisyUI Components

Common DaisyUI Classes

Buttons

<button className="btn btn-primary">Primary</button>
<button className="btn btn-secondary">Secondary</button>
<button className="btn btn-accent">Accent</button>
<button className="btn btn-ghost">Ghost</button>
<button className="btn btn-link">Link</button>

Forms

<label className="form-control w-full">
  <div className="label">
    <span className="label-text">Label</span>
  </div>
  <input
    type="text"
    className="input input-bordered w-full"
    placeholder="Type here"
  />
</label>

<select className="select select-bordered w-full">
  <option disabled selected>Pick one</option>
  <option>Option 1</option>
</select>

<textarea
  className="textarea textarea-bordered w-full"
  placeholder="Enter text"
></textarea>

Cards

<div className="card bg-base-100 shadow-xl">
  <div className="card-body">
    <h2 className="card-title">Card Title</h2>
    <p>Card content goes here</p>
    <div className="card-actions justify-end">
      <button className="btn btn-primary">Action</button>
    </div>
  </div>
</div>

Navigation

<div className="navbar bg-base-100">
  <div className="navbar-start">
    <a className="btn btn-ghost text-xl">Brand</a>
  </div>
  <div className="navbar-center">
    {/* Menu items */}
  </div>
  <div className="navbar-end">
    <button className="btn">Button</button>
  </div>
</div>

Tables

<div className="overflow-x-auto">
  <table className="table">
    <thead>
      <tr>
        <th>Name</th>
        <th>Job</th>
        <th>Company</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Cy Ganderton</td>
        <td>Quality Control Specialist</td>
        <td>Littel, Schaden and Vandervort</td>
      </tr>
    </tbody>
  </table>
</div>

Loading States

<span className="loading loading-spinner loading-xs"></span>
<span className="loading loading-spinner loading-sm"></span>
<span className="loading loading-spinner loading-md"></span>
<span className="loading loading-spinner loading-lg"></span>

Tailwind CSS Utilities

Layout

// Flexbox
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">

// Grid
<div className="grid grid-cols-3 gap-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">

// Spacing
<div className="p-4 m-4">        // padding, margin
<div className="px-4 py-2">      // horizontal, vertical padding
<div className="space-y-4">      // gap between children

Responsive Design

// Mobile-first approach
<div className="w-full md:w-1/2 lg:w-1/3">
<div className="text-sm md:text-base lg:text-lg">
<div className="hidden md:block">  // Hide on mobile

Icons with lucide-react

Available Icons

import {
  Info, ImagePlus, Tag, BookOpen, Save, FileText,
  Home, Settings, Users, LogOut, Menu, X,
  Edit, Trash, Plus, Check, AlertCircle
} from 'lucide-react';

// Usage
<Save className="w-4 h-4" />
<Edit className="w-5 h-5 text-blue-500" />
<Trash className="w-6 h-6 hover:text-red-500" />

Icon Sizes

  • w-4 h-4 - Small (16px)
  • w-5 h-5 - Medium (20px)
  • w-6 h-6 - Large (24px)
  • w-8 h-8 - Extra Large (32px)

React Hooks

useState

const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);

useEffect

useEffect(() => {
  // Effect logic
  return () => {
    // Cleanup
  };
}, [dependencies]);

Custom Hooks

function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
}

useNavigate (React Router)

import { useNavigate } from 'react-router-dom';

function MyComponent() {
  const navigate = useNavigate();

  const handleClick = () => {
    navigate('/admin/blogs');
  };
}

Form Handling

Controlled Inputs

const [value, setValue] = useState('');

<input
  type="text"
  value={value}
  onChange={(e) => setValue(e.target.value)}
  className="input input-bordered"
/>

Form Submission

const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();

  try {
    // Submit logic
  } catch (error) {
    console.error('Submit error:', error);
  }
};

<form onSubmit={handleSubmit}>
  {/* Form fields */}
  <button type="submit" className="btn btn-primary">
    Submit
  </button>
</form>

Loading States

Conditional Rendering

if (loading) {
  return <LoadingSkeleton />;
}

if (error) {
  return <ErrorMessage error={error} />;
}

return <Content data={data} />;

Skeleton Components

Use skeletons from src/app/admin/components/skeleton/:

import TableSkeleton from '../components/skeleton/table-skeleton';

{loading ? <TableSkeleton /> : <DataTable data={data} />}

Context Providers

Creating Context

import { createContext, useContext, useState } from 'react';

interface MyContextType {
  value: string;
  setValue: (value: string) => void;
}

const MyContext = createContext<MyContextType | undefined>(undefined);

export function MyProvider({ children }: { children: React.ReactNode }) {
  const [value, setValue] = useState('');

  return (
    <MyContext.Provider value={{ value, setValue }}>
      {children}
    </MyContext.Provider>
  );
}

export function useMyContext() {
  const context = useContext(MyContext);
  if (!context) {
    throw new Error('useMyContext must be used within MyProvider');
  }
  return context;
}

Routing

Route Configuration

import { BrowserRouter, Routes, Route } from 'react-router-dom';

<BrowserRouter>
  <Routes>
    <Route path="/" element={<Home />} />
    <Route path="/admin" element={<AdminLayout />}>
      <Route path="blogs" element={<BlogList />} />
      <Route path="blogs/create" element={<BlogCreate />} />
      <Route path="blogs/edit/:id" element={<BlogEdit />} />
    </Route>
  </Routes>
</BrowserRouter>

Protected Routes

<ProtectedRoute>
  <AdminDashboard />
</ProtectedRoute>

TypeScript Best Practices

Props Types

// Interface for props
interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

// Or use type
type ButtonProps = {
  label: string;
  onClick: () => void;
}

Event Types

const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => { };
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { };
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { };

Children Props

interface LayoutProps {
  children: React.ReactNode;
}

Common Patterns

List Rendering

{items.map((item) => (
  <div key={item.id}>
    {item.name}
  </div>
))}

Conditional Classes

<button
  className={`btn ${isActive ? 'btn-primary' : 'btn-ghost'}`}
>
  Button
</button>

// Or use template literals
className={`btn ${loading ? 'loading' : ''}`}

Error Boundaries

try {
  // Risky operation
} catch (error) {
  console.error('Error:', error);
  // Show error to user
}

Best Practices

  1. Use TypeScript types for all props and state
  2. Destructure props in function parameters
  3. Use meaningful variable names
  4. Keep components small and focused
  5. Extract reusable logic into custom hooks
  6. Use DaisyUI classes instead of custom CSS
  7. Follow Tailwind conventions for styling
  8. Add loading states for async operations
  9. Handle errors gracefully
  10. Use path aliases (@/) for imports

Component Checklist

When creating a new component:

  • Define TypeScript interface for props
  • Use functional component syntax
  • Add proper imports (React, icons, utilities)
  • Implement loading states if fetching data
  • Add error handling
  • Use DaisyUI components where possible
  • Apply responsive Tailwind classes
  • Export component as default
  • Add JSDoc comments for complex components
  • Test component in browser

Example Component

import { useState, useEffect } from 'react';
import { Save, AlertCircle } from 'lucide-react';
import { useAuth } from '@/auth/AuthContext';
import { getApiUrl, authenticatedFetch } from '@/config/api.config';

interface BlogFormProps {
  id?: string;
  onSuccess?: () => void;
}

export default function BlogForm({ id, onSuccess }: BlogFormProps) {
  const { token } = useAuth();
  const [title, setTitle] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (id) {
      fetchBlog();
    }
  }, [id]);

  const fetchBlog = async () => {
    setLoading(true);
    try {
      const response = await authenticatedFetch(
        getApiUrl(`/posts/${id}`),
        token
      );
      if (response.ok) {
        const { data } = await response.json();
        setTitle(data.title);
      }
    } catch (err) {
      setError('Failed to load blog');
    } finally {
      setLoading(false);
    }
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setError(null);

    try {
      const response = await authenticatedFetch(
        getApiUrl('/posts'),
        token,
        {
          method: 'POST',
          body: JSON.stringify({ title }),
        }
      );

      if (response.ok) {
        onSuccess?.();
      } else {
        setError('Failed to save blog');
      }
    } catch (err) {
      setError('Network error');
    } finally {
      setLoading(false);
    }
  };

  if (loading && id) {
    return (
      <div className="flex justify-center p-8">
        <span className="loading loading-spinner loading-lg"></span>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      {error && (
        <div className="alert alert-error">
          <AlertCircle className="w-5 h-5" />
          <span>{error}</span>
        </div>
      )}

      <label className="form-control w-full">
        <div className="label">
          <span className="label-text">Blog Title</span>
        </div>
        <input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          className="input input-bordered w-full"
          placeholder="Enter title"
          required
        />
      </label>

      <button
        type="submit"
        className={`btn btn-primary ${loading ? 'loading' : ''}`}
        disabled={loading}
      >
        {!loading && <Save className="w-4 h-4" />}
        {loading ? 'Saving...' : 'Save Blog'}
      </button>
    </form>
  );
}

This example demonstrates:

  • TypeScript props interface
  • State management with useState
  • Data fetching with useEffect
  • Form handling
  • Loading states
  • Error handling
  • DaisyUI components
  • Tailwind CSS classes
  • lucide-react icons
  • Authentication integration

Form Handling with react-hook-form + zod

The project uses react-hook-form for form state management and zod for schema validation.

Setup

import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

// Define schema
const formSchema = z.object({
  title: z.string().min(1, 'Title is required').max(200),
  email: z.string().email('Invalid email'),
  published: z.boolean().default(false),
  tags: z.array(z.string()).default([]),
});

type FormData = z.infer<typeof formSchema>;

Basic Form Component

export default function MyForm() {
  const {
    register,
    handleSubmit,
    control,
    reset,
    formState: { errors, isSubmitting },
  } = useForm<FormData>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      title: '',
      email: '',
      published: false,
      tags: [],
    },
  });

  const onSubmit = async (data: FormData) => {
    try {
      // Submit logic
      toast.success('Saved successfully');
    } catch (error) {
      toast.error('Failed to save');
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* Simple input with register */}
      <input
        {...register('title')}
        className={`input input-bordered ${errors.title ? 'input-error' : ''}`}
      />
      {errors.title && (
        <span className="text-error">{errors.title.message}</span>
      )}

      {/* Complex input with Controller */}
      <Controller
        name="published"
        control={control}
        render={({ field }) => (
          <input
            type="checkbox"
            checked={field.value}
            onChange={field.onChange}
          />
        )}
      />

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Saving...' : 'Save'}
      </button>
    </form>
  );
}

useFieldArray for Dynamic Lists

import { useFieldArray } from 'react-hook-form';

const { fields, append, remove } = useFieldArray({
  control,
  name: 'translations',
});

// Render dynamic fields
{fields.map((field, index) => (
  <div key={field.id}>
    <input {...register(`translations.${index}.value`)} />
    <button onClick={() => remove(index)}>Remove</button>
  </div>
))}
<button onClick={() => append({ value: '' })}>Add</button>

Zod Schema Patterns

// Location: src/schemas/

// Basic string validation
title: z.string().min(1, 'Required').max(200, 'Too long')

// Enum validation
categoryType: z.nativeEnum(CategoryTypeEnum)

// Optional with default
published: z.boolean().default(false)

// Array validation
tags: z.array(z.string()).default([])

// Nested object
translation: z.object({
  id: z.string().optional(),
  languageCode: z.string().min(2),
  displayName: z.string().min(1),
})

Toast Notifications with Sonner

The project uses sonner for toast notifications.

Basic Usage

import { toast } from 'sonner';

// Success
toast.success('Operation completed successfully');

// Error
toast.error('Something went wrong');

// Info
toast.info('Here is some information');

// Warning
toast.warning('Please check your input');

With Options

toast.success('Saved!', {
  duration: 5000,           // 5 seconds
  description: 'Your changes have been saved',
});

toast.error('Failed to save', {
  duration: Infinity,       // Won't auto-dismiss
  action: {
    label: 'Retry',
    onClick: () => handleRetry(),
  },
});

Promise Toast

toast.promise(saveData(), {
  loading: 'Saving...',
  success: 'Data saved successfully',
  error: 'Failed to save data',
});

Custom Toast

toast.custom((t) => (
  <div className="bg-base-100 p-4 rounded-lg shadow-lg">
    <h3>Custom Toast</h3>
    <button onClick={() => toast.dismiss(t)}>Close</button>
  </div>
));

DaisyUI v5 CSS Variables

DaisyUI v5 uses new CSS variable naming. Use these when writing custom CSS:

Color Variables

/* Old DaisyUI v4 */
oklch(var(--p))           /* Primary */
oklch(var(--b1))          /* Base 100 */
oklch(var(--bc))          /* Base content */

/* New DaisyUI v5 */
var(--color-primary)      /* Primary */
var(--color-base-100)     /* Base 100 */
var(--color-base-content) /* Base content */

Available Color Variables

/* Base colors */
--color-base-100
--color-base-200
--color-base-300
--color-base-content

/* Semantic colors */
--color-primary
--color-primary-content
--color-secondary
--color-secondary-content
--color-accent
--color-accent-content
--color-neutral
--color-neutral-content

/* State colors */
--color-info
--color-success
--color-warning
--color-error

Using with Opacity (color-mix)

/* 20% opacity primary color */
background-color: color-mix(in oklch, var(--color-primary) 20%, transparent);

/* 50% opacity base content */
color: color-mix(in oklch, var(--color-base-content) 50%, transparent);

Theme Configuration

/* In App.css */
@plugin 'daisyui' {
  themes: emerald --default, dark;
}
/* In rsbuild.config.ts */
html: {
  htmlAttrs: {
    'data-theme': 'emerald',
  },
},