没有 Todo 列表的示例什么也不是!让我们在 Raycast 中将其合在一起。此示例将展示如何展示列表、导航到表单,也包括创建新元素以及更新列表。
渲染 todo 列表
让我们从一组 todo 项开始,并简单地将它们渲染为 Raycast 中的列表:
import { List } from "@raycast/api";
import { useState } from "react";
interface Todo {
title: string;
isCompleted: boolean;
}
export default function Command() {
const [todos, setTodos] = useState<Todo[]>([
{ title: "Write a todo list extension", isCompleted: false },
{ title: "Explain it to others", isCompleted: false },
]);
return (
<List>
{todos.map((todo, index) => (
<List.Item key={index} title={todo.title} />
))}
</List>
);
}
为此,我们定义了一个 TypeScript 接口来描述带有 title
和 isCompleted
标志的 Todo,稍后我们将使用该标志来完成 todo。我们使用 React 的 useState
钩子来创建 todo 的本地状态。我们可以稍后更新它们并且列表将被重新渲染,最后我们呈现所有 todo 的列表。
创建一个 todo
静态的 todo 列表并不是那么有趣,让我们用表单创建一个新的。为此,我们创建一个新的 React 组件来展示表单:
function CreateTodoForm(props: { onCreate: (todo: Todo) => void }) {
const { pop } = useNavigation();
function handleSubmit(values: { title: string }) {
props.onCreate({ title: values.title, isCompleted: false });
pop();
}
return (
<Form
actions={
<ActionPanel>
<Action.SubmitForm title="Create Todo" onSubmit={handleSubmit} />
</ActionPanel>
}
>
<Form.TextField id="title" title="Title" />
</Form>
);
}
function CreateTodoAction(props: { onCreate: (todo: Todo) => void }) {
return (
<Action.Push
icon={Icon.Pencil}
title="Create Todo"
shortcut={{ modifiers: ["cmd"], key: "n" }}
target={<CreateTodoForm onCreate={props.onCreate} />}
/>
);
}
<CreateTodoForm>
显示标题的单个文本字段。提交表单后,它会调用 onCreate
回调并自行关闭。
要使用该操作,我们将其添加到 <List>
组件中。这使得该操作在列表为空时可用,这正是我们想要创建的第一个 todo。
export default function Command() {
const [todos, setTodos] = useState<Todo[]>([]);
function handleCreate(todo: Todo) {
const newTodos = [...todos, todo];
setTodos(newTodos);
}
return (
<List
actions={
<ActionPanel>
<CreateTodoAction onCreate={handleCreate} />
</ActionPanel>
}
>
{todos.map((todo, index) => (
<List.Item key={index} title={todo.title} />
))}
</List>
);
}
完成一个 todo
现在我们可以创建新的 todo,我们还想确保可以勾选 todo 列表上的某些内容。为此,我们创建一个 <ToggleTodoAction>
并将其分配给 <List.Item>
:
export default function Command() {
const [todos, setTodos] = useState<Todo[]>([]);
// ...
function handleToggle(index: number) {
const newTodos = [...todos];
newTodos[index].isCompleted = !newTodos[index].isCompleted;
setTodos(newTodos);
}
return (
<List
actions={
<ActionPanel>
<CreateTodoAction onCreate={handleCreate} />
</ActionPanel>
}
>
{todos.map((todo, index) => (
<List.Item
key={index}
icon={todo.isCompleted ? Icon.Checkmark : Icon.Circle}
title={todo.title}
actions={
<ActionPanel>
<ActionPanel.Section>
<ToggleTodoAction todo={todo} onToggle={() => handleToggle(index)} />
</ActionPanel.Section>
</ActionPanel>
}
/>
))}
</List>
);
}
function ToggleTodoAction(props: { todo: Todo; onToggle: () => void }) {
return (
<Action
icon={props.todo.isCompleted ? Icon.Circle : Icon.Checkmark}
title={props.todo.isCompleted ? "Uncomplete Todo" : "Complete Todo"}
onAction={props.onToggle}
/>
);
}
在本例中,我们将 <ToggleTodoAction>
添加到列表项。通过这样做,我们可以使用 index
来切换适当的 todo。我们还在 todo 中添加了一个反映 isCompleted
状态的图标。
删除一个 todo
与切换 todo 类似,我们还添加了删除 todo 的功能。您可以按照相同的步骤创建一个新的 <DeleteTodoAction>
并将其添加到 <List.Item>
中。
export default function Command() {
const [todos, setTodos] = useState<Todo[]>([]);
// ...
function handleDelete(index: number) {
const newTodos = [...todos];
newTodos.splice(index, 1);
setTodos(newTodos);
}
return (
<List
actions={
<ActionPanel>
<CreateTodoAction onCreate={handleCreate} />
</ActionPanel>
}
>
{todos.map((todo, index) => (
<List.Item
key={index}
icon={todo.isCompleted ? Icon.Checkmark : Icon.Circle}
title={todo.title}
actions={
<ActionPanel>
<ActionPanel.Section>
<ToggleTodoAction todo={todo} onToggle={() => handleToggle(index)} />
</ActionPanel.Section>
<ActionPanel.Section>
<CreateTodoAction onCreate={handleCreate} />
<DeleteTodoAction onDelete={() => handleDelete(index)} />
</ActionPanel.Section>
</ActionPanel>
}
/>
))}
</List>
);
}
// ...
function DeleteTodoAction(props: { onDelete: () => void }) {
return (
<Action
icon={Icon.Trash}
title="Delete Todo"
shortcut={{ modifiers: ["ctrl"], key: "x" }}
onAction={props.onDelete}
/>
);
}
我们还为 <DeleteTodoAction>
提供了键盘快捷键。这样用户可以更快地删除 todo。此外,我们还将 <CreateTodoAction>
添加到 <List.Item>
中。这确保用户在已有 todo 时也可以创建新的 todo。
最后,我们的命令如下所示:
import { Action, ActionPanel, Form, Icon, List, useNavigation } from "@raycast/api";
import { useState } from "react";
interface Todo {
title: string;
isCompleted: boolean;
}
export default function Command() {
const [todos, setTodos] = useState<Todo[]>([
{ title: "Write a todo list extension", isCompleted: false },
{ title: "Explain it to others", isCompleted: false },
]);
function handleCreate(todo: Todo) {
const newTodos = [...todos, todo];
setTodos(newTodos);
}
function handleToggle(index: number) {
const newTodos = [...todos];
newTodos[index].isCompleted = !newTodos[index].isCompleted;
setTodos(newTodos);
}
function handleDelete(index: number) {
const newTodos = [...todos];
newTodos.splice(index, 1);
setTodos(newTodos);
}
return (
<List
actions={
<ActionPanel>
<CreateTodoAction onCreate={handleCreate} />
</ActionPanel>
}
>
{todos.map((todo, index) => (
<List.Item
key={index}
icon={todo.isCompleted ? Icon.Checkmark : Icon.Circle}
title={todo.title}
actions={
<ActionPanel>
<ActionPanel.Section>
<ToggleTodoAction todo={todo} onToggle={() => handleToggle(index)} />
</ActionPanel.Section>
<ActionPanel.Section>
<CreateTodoAction onCreate={handleCreate} />
<DeleteTodoAction onDelete={() => handleDelete(index)} />
</ActionPanel.Section>
</ActionPanel>
}
/>
))}
</List>
);
}
function CreateTodoForm(props: { onCreate: (todo: Todo) => void }) {
const { pop } = useNavigation();
function handleSubmit(values: { title: string }) {
props.onCreate({ title: values.title, isCompleted: false });
pop();
}
return (
<Form
actions={
<ActionPanel>
<Action.SubmitForm title="Create Todo" onSubmit={handleSubmit} />
</ActionPanel>
}
>
<Form.TextField id="title" title="Title" />
</Form>
);
}
function CreateTodoAction(props: { onCreate: (todo: Todo) => void }) {
return (
<Action.Push
icon={Icon.Pencil}
title="Create Todo"
shortcut={{ modifiers: ["cmd"], key: "n" }}
target={<CreateTodoForm onCreate={props.onCreate} />}
/>
);
}
function ToggleTodoAction(props: { todo: Todo; onToggle: () => void }) {
return (
<Action
icon={props.todo.isCompleted ? Icon.Circle : Icon.Checkmark}
title={props.todo.isCompleted ? "Uncomplete Todo" : "Complete Todo"}
onAction={props.onToggle}
/>
);
}
function DeleteTodoAction(props: { onDelete: () => void }) {
return (
<Action
icon={Icon.Trash}
title="Delete Todo"
shortcut={{ modifiers: ["ctrl"], key: "x" }}
onAction={props.onDelete}
/>
);
}
这就是一个示例。您在 Raycast 中创建了一个 todo 列表,就是这么简单。作为后续步骤,您可以将 <CreateTodoForm>
提取到单独的命令中。然后,您还可以从 Raycast 的根搜索创建 todo,甚至可以分配全局热键来打开表单。此外,todo 不会保留。如果您关闭命令并重新打开它,它们就会消失。想要保留的话,您可以使用 storage 或将其 写入文件 中。