npm.io
1.1.0 • Published 7 months ago

@darksnow-ui/commander

Licence
MIT
Version
1.1.0
Deps
0
Size
550 kB
Vulns
0
Weekly
0

@darksnow-ui/commander

Enterprise-grade command system for React applications with Command Palette integration

npm version TypeScript License: MIT

Transform your React app into a power-user's dream with a VS Code-style command system. Register commands from anywhere, execute them programmatically or via Command Palette, and watch your UX reach new levels.

Why Commander?

  • Scattered actions across your app? → Centralize with Commander
  • No programmatic execution? → Full API + keyboard shortcuts
  • Manual Command Palette maintenance? → Auto-register/cleanup
  • Poor discoverability? → Intelligent search with fuzzy matching
// 1. Register commands anywhere
useCustomCommand({
  key: "file:save",
  label: "Save File",
  shortcut: "ctrl+s",
  handle: async () => saveCurrentFile(),
});

// 2. Execute with type safety
const saveFile = useInvoker<SaveInput, SaveResult>("file:save");
await saveFile({ filename: "document.txt" });

// 3. Users find it in Command Palette (Ctrl+Shift+P)
// 4. Auto-cleanup on component unmount

Installation

pnpm add @darksnow-ui/commander
# or
npm install @darksnow-ui/commander

Quick Start

1. Setup Provider
import { CommanderProvider, Commander } from "@darksnow-ui/commander";

const commander = new Commander();

function App() {
  return (
    <CommanderProvider commander={commander} enableDevTools>
      <MyApp />
    </CommanderProvider>
  );
}
2. Register Commands
import { useCustomCommand } from "@darksnow-ui/commander";

function FileEditor({ file }) {
  useCustomCommand({
    key: `file:save:${file.id}`,
    label: `Save ${file.name}`,
    shortcut: "ctrl+s",
    when: () => file.isDirty,
    handle: async () => {
      await saveFile(file);
      return { saved: true };
    },
  });

  return <Editor />;
}
3. Execute Commands
import { useInvoker } from "@darksnow-ui/commander";

function SaveButton() {
  const saveFile = useInvoker("file:save");

  return <button onClick={() => saveFile()}>Save</button>;
}

Hooks API

Core
Hook Purpose
useCommander() Access Commander instance and methods
useCustomCommand(config) Register temporary command (auto-cleanup)
Execution
Hook Purpose
useCommand(key, options) Full-featured with state tracking
useInvoker(key, options) Direct function execution
useAction(key) For commands without parameters
useSafeInvoker(key) Non-throwing, returns ExecutionResult
Specialized
Hook Purpose
useBoundInvoker(key, defaults) Pre-configured parameters
useToggleInvoker(key) Boolean toggle commands
useBatchInvoker(keys, options) Sequential execution
useParallelInvoker(keys) Parallel execution

Command Options

useCustomCommand({
  // Required
  key: "namespace:action",
  label: "Human Readable Name",
  handle: async (input) => result,

  // Optional
  description: "What this command does",
  category: "file" | "edit" | "view" | "tools" | "debug" | "system" | "custom",
  icon: "💾",
  shortcut: "ctrl+s",
  tags: ["save", "file"],
  priority: 10,
  timeout: 5000,
  owner: "my-component",

  // Conditional availability
  when: () => canExecute,

  // Input validation (works with Zod, Yup, etc.)
  inputValidator: (input) => {
    if (!input?.email) {
      return [{ path: "email", message: "Required", code: "required" }];
    }
    return true;
  },
});

Input Validation

Commands support input validation with any library:

// With Zod
import { z } from "zod";

const schema = z.object({
  email: z.string().email(),
  name: z.string().min(2),
});

useCustomCommand({
  key: "user:create",
  label: "Create User",
  inputValidator: (input) => {
    const result = schema.safeParse(input);
    if (result.success) return true;
    return result.error.issues.map((i) => ({
      path: i.path.join("."),
      message: i.message,
      code: i.code,
    }));
  },
  handle: async (input) => createUser(input),
});

Handle validation errors:

import { isInputValidationError } from "@darksnow-ui/commander";

try {
  await commander.invoke("user:create", { email: "" });
} catch (error) {
  if (isInputValidationError(error)) {
    console.log(error.getMissingFields()); // ["name"]
    console.log(error.errors); // Full error details
  }
}

useCommand vs useInvoker

// useInvoker - Simple, direct execution
const save = useInvoker("file:save");
await save({ filename: "doc.txt" });

// useCommand - Full state tracking
const cmd = useCommand("file:save");
cmd.isLoading;      // boolean
cmd.lastResult;     // last successful result
cmd.lastError;      // last error
cmd.executionCount; // number of executions
await cmd.invoke({ filename: "doc.txt" });

Documentation

Document Description
Architecture Core internals, types, algorithms
useInvoker Guide Deep dive into execution hooks
useCustomCommand Examples 30+ real-world examples

Comparison

Feature Commander cmdk kbar
TypeScript generics Full Basic Good
React hooks 10 specialized Limited
Auto cleanup Manual Manual
Conditional commands when()
Input validation Built-in
State tracking
Event system Full lifecycle Limited

License

MIT Anderson Rosa


Built with by Anderson Rosa

Part of the DarkSnow UI ecosystem

Star on GitHub Architecture Examples