better-cmdk 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -90
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,18 +72,147 @@ Add the required CSS variables to your global styles:
|
|
|
72
72
|
|
|
73
73
|
## Usage
|
|
74
74
|
|
|
75
|
+
The recommended way to use better-cmdk is with the declarative `commands` prop. Define your commands as data and let the component handle rendering, grouping, and search.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
"use client";
|
|
79
|
+
|
|
80
|
+
import { useState, useEffect } from "react";
|
|
81
|
+
import { CalendarIcon, SearchIcon, UserIcon, SettingsIcon } from "lucide-react";
|
|
82
|
+
import { CommandMenu, type CommandDefinition } from "better-cmdk";
|
|
83
|
+
|
|
84
|
+
const commands: CommandDefinition[] = [
|
|
85
|
+
{
|
|
86
|
+
name: "calendar",
|
|
87
|
+
label: "Calendar",
|
|
88
|
+
icon: <CalendarIcon className="size-4" />,
|
|
89
|
+
group: "Suggestions",
|
|
90
|
+
onSelect: () => console.log("Calendar selected"),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "search",
|
|
94
|
+
label: "Search",
|
|
95
|
+
icon: <SearchIcon className="size-4" />,
|
|
96
|
+
group: "Suggestions",
|
|
97
|
+
onSelect: () => console.log("Search selected"),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "profile",
|
|
101
|
+
label: "Profile",
|
|
102
|
+
icon: <UserIcon className="size-4" />,
|
|
103
|
+
group: "Settings",
|
|
104
|
+
shortcut: "⌘P",
|
|
105
|
+
onSelect: () => console.log("Profile selected"),
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "settings",
|
|
109
|
+
label: "Settings",
|
|
110
|
+
icon: <SettingsIcon className="size-4" />,
|
|
111
|
+
group: "Settings",
|
|
112
|
+
shortcut: "⌘S",
|
|
113
|
+
onSelect: () => console.log("Settings selected"),
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
export function CommandPalette() {
|
|
118
|
+
const [open, setOpen] = useState(false);
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
const down = (e: KeyboardEvent) => {
|
|
122
|
+
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
setOpen((open) => !open);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
document.addEventListener("keydown", down);
|
|
129
|
+
return () => document.removeEventListener("keydown", down);
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<CommandMenu
|
|
134
|
+
open={open}
|
|
135
|
+
onOpenChange={setOpen}
|
|
136
|
+
commands={commands}
|
|
137
|
+
commandsPlaceholder="Search or ask AI..."
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### CommandDefinition
|
|
144
|
+
|
|
145
|
+
Each command in the `commands` array supports:
|
|
146
|
+
|
|
147
|
+
| Property | Type | Description |
|
|
148
|
+
|----------|------|-------------|
|
|
149
|
+
| `name` | `string` | **Required.** Unique key used for search matching |
|
|
150
|
+
| `label` | `string` | Display text (falls back to `name`) |
|
|
151
|
+
| `group` | `string` | Group heading — commands with the same group appear together |
|
|
152
|
+
| `icon` | `ReactNode` | Icon rendered before the label |
|
|
153
|
+
| `shortcut` | `string` | Keyboard shortcut hint (right-aligned) |
|
|
154
|
+
| `keywords` | `string[]` | Extra search terms |
|
|
155
|
+
| `disabled` | `boolean` | Grayed out, not selectable |
|
|
156
|
+
| `onSelect` | `() => void` | Called when the command is selected |
|
|
157
|
+
|
|
158
|
+
### CommandMenu Props
|
|
159
|
+
|
|
160
|
+
| Prop | Type | Default | Description |
|
|
161
|
+
|------|------|---------|-------------|
|
|
162
|
+
| `commands` | `CommandDefinition[]` | — | Declarative command definitions |
|
|
163
|
+
| `commandsPlaceholder` | `string` | `"Search or ask AI..."` | Input placeholder |
|
|
164
|
+
| `commandsAskAILabel` | `string` | `"Ask AI"` | Label for the AI trigger |
|
|
165
|
+
| `open` | `boolean` | — | Controlled open state |
|
|
166
|
+
| `onOpenChange` | `(open: boolean) => void` | — | Open state callback |
|
|
167
|
+
| `corners` | `"none" \| "sm" \| "md" \| "lg" \| "xl"` | `"xl"` | Border radius |
|
|
168
|
+
| `borderColor` | `string` | — | Custom ring color |
|
|
169
|
+
| `chatEndpoint` | `string` | — | API endpoint for built-in AI chat |
|
|
170
|
+
| `chat` | `ExternalChat` | — | External chat integration |
|
|
171
|
+
| `onModeChange` | `(mode: CommandMenuMode) => void` | — | Fires when switching between command/chat |
|
|
172
|
+
| `historyStorageKey` | `string` | — | localStorage key for chat history |
|
|
173
|
+
| `maxConversations` | `number` | — | Max saved chat conversations |
|
|
174
|
+
|
|
175
|
+
### AI Chat
|
|
176
|
+
|
|
177
|
+
Enable the built-in AI chat by providing either a `chatEndpoint` or an external `chat` object:
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
// Built-in chat with an API endpoint
|
|
181
|
+
<CommandMenu
|
|
182
|
+
commands={commands}
|
|
183
|
+
chatEndpoint="/api/chat"
|
|
184
|
+
open={open}
|
|
185
|
+
onOpenChange={setOpen}
|
|
186
|
+
/>
|
|
187
|
+
|
|
188
|
+
// External chat integration (e.g. Vercel AI SDK useChat)
|
|
189
|
+
<CommandMenu
|
|
190
|
+
commands={commands}
|
|
191
|
+
chat={externalChat}
|
|
192
|
+
open={open}
|
|
193
|
+
onOpenChange={setOpen}
|
|
194
|
+
/>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Users can switch to chat mode via `⌘ Enter` or by selecting the "Ask AI" item.
|
|
198
|
+
|
|
199
|
+
## Advanced: Custom Children
|
|
200
|
+
|
|
201
|
+
For full control over the command list rendering, you can pass children instead of `commands`. This approach is compatible with shadcn/ui patterns if you're migrating from an existing setup.
|
|
202
|
+
|
|
203
|
+
> **Note:** When both `commands` and `children` are provided, `commands` takes precedence.
|
|
204
|
+
|
|
75
205
|
```tsx
|
|
76
206
|
"use client";
|
|
77
207
|
|
|
78
208
|
import { useState, useEffect } from "react";
|
|
79
209
|
import {
|
|
80
|
-
|
|
210
|
+
CommandMenu,
|
|
211
|
+
CommandInput,
|
|
212
|
+
CommandList,
|
|
81
213
|
CommandEmpty,
|
|
82
214
|
CommandGroup,
|
|
83
|
-
CommandInput,
|
|
84
215
|
CommandItem,
|
|
85
|
-
CommandList,
|
|
86
|
-
CommandSeparator,
|
|
87
216
|
CommandShortcut,
|
|
88
217
|
} from "better-cmdk";
|
|
89
218
|
|
|
@@ -103,10 +232,9 @@ export function CommandPalette() {
|
|
|
103
232
|
}, []);
|
|
104
233
|
|
|
105
234
|
return (
|
|
106
|
-
<
|
|
107
|
-
<CommandInput placeholder="Type a command or search..." />
|
|
235
|
+
<CommandMenu open={open} onOpenChange={setOpen}>
|
|
236
|
+
<CommandInput placeholder="Type a command or search..." showSendButton />
|
|
108
237
|
<CommandList>
|
|
109
|
-
<CommandEmpty>No results found.</CommandEmpty>
|
|
110
238
|
<CommandGroup heading="Suggestions">
|
|
111
239
|
<CommandItem>
|
|
112
240
|
<span>Calendar</span>
|
|
@@ -115,7 +243,6 @@ export function CommandPalette() {
|
|
|
115
243
|
<span>Search</span>
|
|
116
244
|
</CommandItem>
|
|
117
245
|
</CommandGroup>
|
|
118
|
-
<CommandSeparator />
|
|
119
246
|
<CommandGroup heading="Settings">
|
|
120
247
|
<CommandItem>
|
|
121
248
|
<span>Profile</span>
|
|
@@ -126,107 +253,48 @@ export function CommandPalette() {
|
|
|
126
253
|
<CommandShortcut>⌘S</CommandShortcut>
|
|
127
254
|
</CommandItem>
|
|
128
255
|
</CommandGroup>
|
|
256
|
+
<CommandEmpty />
|
|
129
257
|
</CommandList>
|
|
130
|
-
</
|
|
258
|
+
</CommandMenu>
|
|
131
259
|
);
|
|
132
260
|
}
|
|
133
261
|
```
|
|
134
262
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
### CommandDialog
|
|
138
|
-
|
|
139
|
-
The main dialog wrapper. Opens as a modal command palette.
|
|
140
|
-
|
|
141
|
-
```tsx
|
|
142
|
-
<CommandDialog
|
|
143
|
-
open={open}
|
|
144
|
-
onOpenChange={setOpen}
|
|
145
|
-
title="Command Palette" // optional, for accessibility
|
|
146
|
-
description="Search commands" // optional, for accessibility
|
|
147
|
-
>
|
|
148
|
-
{children}
|
|
149
|
-
</CommandDialog>
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### CommandInput
|
|
153
|
-
|
|
154
|
-
Search input with built-in search icon.
|
|
155
|
-
|
|
156
|
-
```tsx
|
|
157
|
-
<CommandInput placeholder="Search..." />
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### CommandList
|
|
263
|
+
### Render Props
|
|
161
264
|
|
|
162
|
-
|
|
265
|
+
Children can also be a function to access internal state:
|
|
163
266
|
|
|
164
267
|
```tsx
|
|
165
|
-
<
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
### CommandItem
|
|
177
|
-
|
|
178
|
-
Individual selectable command item.
|
|
179
|
-
|
|
180
|
-
```tsx
|
|
181
|
-
<CommandItem onSelect={() => console.log("Selected!")}>
|
|
182
|
-
<Icon className="mr-2 h-4 w-4" />
|
|
183
|
-
<span>Label</span>
|
|
184
|
-
</CommandItem>
|
|
268
|
+
<CommandMenu open={open} onOpenChange={setOpen}>
|
|
269
|
+
{({ mode, messages, status, isEnabled }) => (
|
|
270
|
+
<>
|
|
271
|
+
<CommandInput placeholder="Search..." showSendButton />
|
|
272
|
+
<CommandList>
|
|
273
|
+
{/* Custom rendering based on mode/status */}
|
|
274
|
+
</CommandList>
|
|
275
|
+
</>
|
|
276
|
+
)}
|
|
277
|
+
</CommandMenu>
|
|
185
278
|
```
|
|
186
279
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
Displays keyboard shortcut hints.
|
|
190
|
-
|
|
191
|
-
```tsx
|
|
192
|
-
<CommandShortcut>⌘K</CommandShortcut>
|
|
193
|
-
```
|
|
280
|
+
## Styling
|
|
194
281
|
|
|
195
|
-
|
|
282
|
+
The component uses Tailwind CSS with the shadcn/ui design tokens. Customize by:
|
|
196
283
|
|
|
197
|
-
|
|
284
|
+
1. Overriding CSS variables
|
|
285
|
+
2. Passing `className` props to components
|
|
286
|
+
3. Using the `cn()` utility for conditional classes
|
|
198
287
|
|
|
199
|
-
|
|
200
|
-
<CommandSeparator />
|
|
201
|
-
```
|
|
288
|
+
## Telemetry
|
|
202
289
|
|
|
203
|
-
|
|
290
|
+
better-cmdk collects anonymous error and performance data via [Sentry](https://sentry.io) to help improve reliability. No personally identifiable information (PII) is collected — user data, cookies, headers, and breadcrumbs are stripped before transmission.
|
|
204
291
|
|
|
205
|
-
|
|
292
|
+
To opt out, set the environment variable:
|
|
206
293
|
|
|
207
|
-
```tsx
|
|
208
|
-
<CommandEmpty>No results found.</CommandEmpty>
|
|
209
294
|
```
|
|
210
|
-
|
|
211
|
-
## Subpath Exports
|
|
212
|
-
|
|
213
|
-
Import specific components directly:
|
|
214
|
-
|
|
215
|
-
```tsx
|
|
216
|
-
import { Command, CommandDialog } from "better-cmdk/command";
|
|
217
|
-
import { Dialog, DialogContent } from "better-cmdk/dialog";
|
|
218
|
-
import { Button } from "better-cmdk/button";
|
|
219
|
-
import { cn } from "better-cmdk/utils";
|
|
295
|
+
BETTER_CMDK_TELEMETRY_DISABLED=1
|
|
220
296
|
```
|
|
221
297
|
|
|
222
|
-
## Styling
|
|
223
|
-
|
|
224
|
-
The component uses Tailwind CSS with the shadcn/ui design tokens. Customize by:
|
|
225
|
-
|
|
226
|
-
1. Overriding CSS variables
|
|
227
|
-
2. Passing `className` props to components
|
|
228
|
-
3. Using the `cn()` utility for conditional classes
|
|
229
|
-
|
|
230
298
|
## License
|
|
231
299
|
|
|
232
300
|
MIT
|