npm package: @liaskos/command-palette
Description: A beautiful, accessible command palette component for React applications - inspired by VS Code and Linear
Author: Alexandros Liaskos
Latest version: 1.0.1

# @liaskos/command-palette

A beautiful, accessible command palette component for React applications, inspired by VS Code and Linear. Built with TypeScript and designed to match the v1 prototype exactly.

## Features

- 🎨 **Beautiful Design** - Clean, modern interface with HSL-based theming
- ⌨️ **Keyboard Navigation** - Full keyboard support with arrow keys and shortcuts
- 🔍 **Fuzzy Search** - Intelligent search with keyword matching
- 🎯 **Accessibility** - ARIA compliant with screen reader support
- 📱 **Responsive** - Works on all screen sizes
- 🎪 **Zero Dependencies** - Only requires React and Lucide React icons
- 🏗️ **TypeScript** - Fully typed with comprehensive interfaces
- 🎭 **Customizable** - Flexible theming and configuration options

## Installation

```bash
npm install @liaskos/command-palette
# or
yarn add @liaskos/command-palette
# or
pnpm add @liaskos/command-palette
```

## Quick Start

```tsx
import React, { useState } from 'react';
import { CommandPalette, CommandPaletteTrigger, useGlobalShortcuts } from '@liaskos/command-palette';
import { Home, Folder, CheckSquare, Users } from 'lucide-react';

const actions = [
  {
    id: 'home',
    label: 'Go to Home',
    description: 'Navigate to the home page',
    icon: Home,
    keywords: ['home', 'dashboard'],
    group: 'Navigation',
  },
  {
    id: 'projects',
    label: 'View Projects',
    description: 'See all your projects',
    icon: Folder,
    keywords: ['projects', 'work'],
    group: 'Navigation',
  },
  // ... more actions
];

function App() {
  const [open, setOpen] = useState(false);

  // Enable global keyboard shortcut (⌘K / Ctrl+K)
  useGlobalShortcuts(() => setOpen(true));

  const handleSelect = (action) => {
    console.log('Selected:', action);
    // Handle action selection
  };

  return (
    <div>
      {/* Trigger Button */}
      <CommandPaletteTrigger onClick={() => setOpen(true)} />

      {/* Command Palette */}
      <CommandPalette
        open={open}
        actions={actions}
        onSelect={handleSelect}
        onClose={() => setOpen(false)}
      />
    </div>
  );
}
```

## Advanced Usage

### Custom Actions

```tsx
import { Plus, Calendar, Settings } from 'lucide-react';

const customActions = [
  {
    id: 'create-project',
    label: 'Create New Project',
    description: 'Start a new project',
    icon: Plus,
    keywords: ['create', 'new', 'project'],
    group: 'Quick Actions',
    shortcut: '⌘N',
    data: { type: 'project' },
  },
  {
    id: 'schedule-meeting',
    label: 'Schedule Meeting',
    description: 'Create a new meeting',
    icon: Calendar,
    keywords: ['meeting', 'calendar'],
    group: 'Quick Actions',
    disabled: false,
  },
];
```

### Custom Styling

```tsx
const customStyle = {
  maxWidth: '600px',
  backgroundColor: 'hsl(0 0% 3%)',
  color: 'hsl(0 0% 98%)',
};

<CommandPalette
  open={open}
  actions={actions}
  style={customStyle}
  maxHeight={400}
  placeholder="Search anything..."
  emptyMessage="No commands found."
  onSelect={handleSelect}
  onClose={() => setOpen(false)}
/>
```

### Dynamic Actions

```tsx
function DynamicCommandPalette() {
  const [actions, setActions] = useState(defaultActions);

  const handleSelect = (action) => {
    switch (action.id) {
      case 'create-project':
        // Add new project action dynamically
        setActions(prev => [...prev, {
          id: 'new-project-' + Date.now(),
          label: 'New Project',
          description: 'Recently created project',
          icon: Folder,
          group: 'Recent',
        }]);
        break;
      // ... handle other actions
    }
  };

  return (
    <CommandPalette
      open={open}
      actions={actions}
      onSelect={handleSelect}
      onClose={() => setOpen(false)}
    />
  );
}
```

## API Reference

### CommandPalette Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `open` | `boolean` | `false` | Whether the command palette is open |
| `actions` | `CommandAction[]` | `[]` | Array of actions to display |
| `placeholder` | `string` | `"Search projects, tasks..."` | Placeholder text for search input |
| `emptyMessage` | `string` | `"No results found."` | Message when no results |
| `onSelect` | `(action: CommandAction) => void` | `undefined` | Callback when action is selected |
| `onClose` | `() => void` | `undefined` | Callback when palette is closed |
| `style` | `React.CSSProperties` | `undefined` | Custom styles |
| `className` | `string` | `undefined` | CSS class name |
| `maxHeight` | `number` | `320` | Maximum height of results area |
| `fuzzySearch` | `boolean` | `true` | Enable fuzzy search |
| `searchDelay` | `number` | `150` | Search debounce delay in ms |

### CommandAction Interface

```tsx
interface CommandAction {
  readonly id: string;                    // Unique identifier
  readonly label: string;                 // Display name
  readonly description?: string;          // Optional description
  readonly icon?: LucideIcon;            // Optional Lucide icon
  readonly keywords?: readonly string[]; // Search keywords
  readonly group?: string;               // Group category
  readonly shortcut?: string;            // Keyboard shortcut
  readonly disabled?: boolean;           // Whether action is disabled
  readonly data?: Record<string, any>;   // Custom data
}
```

### Hooks

#### useGlobalShortcuts

Enables global keyboard shortcuts (⌘K / Ctrl+K) to open the command palette.

```tsx
import { useGlobalShortcuts } from '@liaskos/command-palette';

function App() {
  const [open, setOpen] = useState(false);
  
  useGlobalShortcuts(() => setOpen(true));
  
  return (/* ... */);
}
```

#### useCommandPalette

Provides state management for the command palette.

```tsx
import { useCommandPalette } from '@liaskos/command-palette';

const {
  open,
  query,
  selectedIndex,
  setQuery,
  setSelectedIndex,
  openPalette,
  closePalette,
  selectAction,
} = useCommandPalette(actions, onSelect, onClose);
```

#### useSearch

Handles search functionality with debouncing and fuzzy matching.

```tsx
import { useSearch } from '@liaskos/command-palette';

const results = useSearch(actions, query, fuzzySearch, delay);
```

## Keyboard Shortcuts

- **⌘K / Ctrl+K** - Open command palette
- **Escape** - Close command palette
- **↑/↓ Arrow Keys** - Navigate through results
- **Enter** - Select highlighted action

## Theming

The component uses an HSL-based color system for consistent theming:

```tsx
const customTheme = {
  colors: {
    background: 'hsl(0 0% 100%)',
    foreground: 'hsl(222.2 84% 4.9%)',
    muted: 'hsl(210 40% 96%)',
    mutedForeground: 'hsl(215.4 16.3% 46.9%)',
    border: 'hsl(214.3 31.8% 91.4%)',
    accent: 'hsl(210 40% 96%)',
    accentForeground: 'hsl(222.2 84% 4.9%)',
    overlay: 'hsla(0, 0%, 0%, 0.5)',
  },
  // ... more theme options
};
```

## Examples

### With React Router

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

function App() {
  const navigate = useNavigate();
  
  const actions = [
    {
      id: 'dashboard',
      label: 'Dashboard',
      icon: Home,
      data: { route: '/dashboard' },
    },
    {
      id: 'projects',
      label: 'Projects',
      icon: Folder,
      data: { route: '/projects' },
    },
  ];

  const handleSelect = (action) => {
    if (action.data?.route) {
      navigate(action.data.route);
    }
  };

  return (
    <CommandPalette
      actions={actions}
      onSelect={handleSelect}
    />
  );
}
```

### With Next.js

```tsx
import { useRouter } from 'next/router';

function App() {
  const router = useRouter();
  
  const handleSelect = (action) => {
    if (action.data?.route) {
      router.push(action.data.route);
    }
  };

  return (
    <CommandPalette
      actions={actions}
      onSelect={handleSelect}
    />
  );
}
```

## Browser Support

- Chrome/Edge 88+
- Firefox 85+
- Safari 14+
- iOS Safari 14+
- Android Chrome 88+

## License

MIT License - see LICENSE file for details.

## Contributing

Contributions are welcome! Please read our contributing guidelines and code of conduct.

---

**May the force be with you** - Alexandros Liaskos