Claude Code Plugins

Community-maintained marketplace

Feedback

React & shadcn/ui Components

@jhl-labs/sepilot_desktop
39
3

>

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 React & shadcn/ui Components
description React component development guidelines using TypeScript and shadcn/ui. Use when creating UI components, forms, dialogs, or interactive elements. Ensures consistent styling, accessibility, and follows SEPilot design patterns.

React & shadcn/ui Development Skill

Component Structure

SEPilot Desktop follows a clean component architecture:

components/
├── ui/                    # shadcn/ui primitives (don't edit directly)
│   ├── button.tsx
│   ├── card.tsx
│   ├── dialog.tsx
│   ├── input.tsx
│   ├── select.tsx
│   ├── tabs.tsx
│   ├── badge.tsx
│   ├── switch.tsx
│   ├── alert-dialog.tsx
│   └── collapsible.tsx
├── gallery/               # Feature-specific components
│   └── GalleryView.tsx
└── UpdateNotificationDialog.tsx  # Standalone features

Creating Components

Basic Component Pattern

// components/MyComponent.tsx
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';

interface MyComponentProps {
  title: string;
  onSubmit: (data: string) => Promise<void>;
  disabled?: boolean;
}

export function MyComponent({
  title,
  onSubmit,
  disabled = false
}: MyComponentProps): JSX.Element {
  const [loading, setLoading] = useState(false);
  const [value, setValue] = useState('');

  const handleSubmit = async (): Promise<void> => {
    setLoading(true);
    try {
      await onSubmit(value);
      setValue(''); // Clear on success
    } catch (error) {
      console.error('Submit failed:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <Card>
      <CardHeader>
        <CardTitle>{title}</CardTitle>
      </CardHeader>
      <CardContent>
        <input
          value={value}
          onChange={(e) => setValue(e.target.value)}
          disabled={disabled || loading}
        />
        <Button onClick={handleSubmit} disabled={disabled || loading}>
          {loading ? 'Submitting...' : 'Submit'}
        </Button>
      </CardContent>
    </Card>
  );
}

Available shadcn/ui Components

Buttons

import { Button } from '@/components/ui/button';

<Button variant="default">Default</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Cancel</Button>
<Button variant="ghost">Link</Button>
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>

Dialogs

import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog';

<Dialog>
  <DialogTrigger asChild>
    <Button>Open Dialog</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Confirmation</DialogTitle>
      <DialogDescription>
        Are you sure you want to proceed?
      </DialogDescription>
    </DialogHeader>
    {/* Dialog content */}
  </DialogContent>
</Dialog>

Alert Dialogs

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from '@/components/ui/alert-dialog';

<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Delete conversation?</AlertDialogTitle>
      <AlertDialogDescription>
        This action cannot be undone.
      </AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction onClick={handleDelete}>Delete</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>

Forms with Select

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';

<Select value={model} onValueChange={setModel}>
  <SelectTrigger>
    <SelectValue placeholder="Select a model" />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="gpt-4">GPT-4</SelectItem>
    <SelectItem value="claude-3">Claude 3</SelectItem>
  </SelectContent>
</Select>

Tabs

import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';

<Tabs defaultValue="chat">
  <TabsList>
    <TabsTrigger value="chat">Chat</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="chat">
    {/* Chat content */}
  </TabsContent>
  <TabsContent value="settings">
    {/* Settings content */}
  </TabsContent>
</Tabs>

Switch

import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';

<div className="flex items-center space-x-2">
  <Switch id="dark-mode" checked={isDark} onCheckedChange={setIsDark} />
  <Label htmlFor="dark-mode">Dark Mode</Label>
</div>

Badges

import { Badge } from '@/components/ui/badge';

<Badge variant="default">Active</Badge>
<Badge variant="secondary">Pending</Badge>
<Badge variant="destructive">Error</Badge>
<Badge variant="outline">Draft</Badge>

Styling with Tailwind CSS

Using cn() Utility

import { cn } from '@/lib/utils';

<button
  className={cn(
    'px-4 py-2 rounded',
    disabled && 'opacity-50 cursor-not-allowed',
    variant === 'primary' && 'bg-blue-500 text-white',
    variant === 'secondary' && 'bg-gray-200 text-gray-900'
  )}
/>

Common Patterns

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

// Flex layout
<div className="flex items-center justify-between">

// Spacing
<div className="space-y-4"> {/* Vertical spacing */}
<div className="space-x-2"> {/* Horizontal spacing */}

// Text styles
<h1 className="text-2xl font-bold">Title</h1>
<p className="text-sm text-muted-foreground">Description</p>

// Hover states
<button className="hover:bg-gray-100 transition-colors">

State Management

useState for Local State

const [isOpen, setIsOpen] = useState(false);
const [data, setData] = useState<MyType | null>(null);

useEffect for Side Effects

useEffect(() => {
  // Fetch data or setup listeners
  const fetchData = async (): Promise<void> => {
    const result = await window.electron.invoke('get-data');
    setData(result);
  };

  fetchData();
}, [dependency]);

Custom Hooks

function useConversation(id: string) {
  const [messages, setMessages] = useState<Message[]>([]);
  const [loading, setLoading] = useState(false);

  const sendMessage = async (content: string): Promise<void> => {
    setLoading(true);
    try {
      await window.electron.invoke('send-message', { id, content });
    } finally {
      setLoading(false);
    }
  };

  return { messages, loading, sendMessage };
}

IPC Integration

Invoking Backend

const handleSubmit = async (): Promise<void> => {
  try {
    const result = await window.electron.invoke('my-action', { data });
    if (result.success) {
      setData(result.data);
    }
  } catch (error) {
    console.error('Error:', error);
  }
};

Listening to Events

useEffect(() => {
  const handleUpdate = (data: UpdateData): void => {
    setStreamData((prev) => [...prev, data]);
  };

  window.electron.on('stream:data', handleUpdate);

  return () => {
    window.electron.off('stream:data', handleUpdate);
  };
}, []);

Accessibility

Always include:

  • ARIA labels for interactive elements
  • Keyboard navigation support
  • Focus management
  • Semantic HTML
<button
  aria-label="Close dialog"
  onClick={handleClose}
  onKeyDown={(e) => e.key === 'Escape' && handleClose()}
>
  <XIcon />
</button>

Component Composition

Break large components into smaller ones:

// ❌ Bad - monolithic component
export function ConversationView() {
  // 300 lines of code...
}

// ✅ Good - composed components
export function ConversationView() {
  return (
    <div>
      <ConversationHeader />
      <ConversationMessages />
      <ConversationInput />
    </div>
  );
}

Error Boundaries

import { Component, ReactNode } from 'react';

interface Props {
  children: ReactNode;
}

interface State {
  hasError: boolean;
}

export class ErrorBoundary extends Component<Props, State> {
  state = { hasError: false };

  static getDerivedStateFromError(): State {
    return { hasError: true };
  }

  render(): ReactNode {
    if (this.state.hasError) {
      return <div>Something went wrong.</div>;
    }
    return this.props.children;
  }
}

Real-World Example

See components/UpdateNotificationDialog.tsx for a complete example integrating:

  • shadcn/ui components (AlertDialog, Button)
  • IPC communication
  • TypeScript types
  • State management
  • Error handling