Z ZPI 文档 v0.3.8
介绍页

源码

packages/ui/src/components/TaskFindBar.tsx

tsx79 行查看原文
// Geometry, labels and Lucide assets follow ZCode TaskFindDialog (Apache-2.0).
import { ArrowDown, ArrowUp, FileDiff, MessageCircle, Search, X } from "lucide-react";
import { type ReactNode, useEffect, useRef } from "react";
import { type FindRequest, type FindState, findKeyDirection, moveFindIndex } from "../find.ts";
import { ActionHint } from "./MessageActions.tsx";

export function TaskFindBar({
  request,
  state,
  scope,
  focusRequestId,
  onChange,
  onNavigate,
  onToggleScope,
  onClose,
}: {
  request: FindRequest;
  state: FindState;
  scope: "conversation" | "changes";
  focusRequestId: number;
  onChange: (query: string) => void;
  onNavigate: (index: number) => void;
  onToggleScope: () => void;
  onClose: () => void;
}) {
  const input = useRef<HTMLInputElement>(null);
  useEffect(() => {
    const frame = requestAnimationFrame(() => input.current?.focus());
    return () => cancelAnimationFrame(frame);
  }, [focusRequestId]);
  const move = (direction: "previous" | "next") => {
    if (state.total) onNavigate(moveFindIndex(state, direction));
  };
  const button = (label: string, icon: ReactNode, action: () => void, disabled = false, hint = label) => (
    <ActionHint label={hint}>
      <span className="task-find-action">
        <button type="button" aria-label={label} disabled={disabled} onClick={action}>
          {icon}
        </button>
      </span>
    </ActionHint>
  );
  return (
    <div role="dialog" aria-modal="false" aria-label="在任务中查找" className="task-find-bar">
      <Search size={14} aria-hidden="true" />
      <input
        ref={input}
        aria-label={scope === "conversation" ? "搜索消息" : "搜索文件变更"}
        placeholder={scope === "conversation" ? "搜索消息..." : "搜索文件变更..."}
        value={request.query}
        onChange={(event) => onChange(event.target.value)}
        onKeyDown={(event) => {
          if (event.nativeEvent.isComposing || event.keyCode === 229) return;
          const direction = findKeyDirection(event.key, event.shiftKey);
          if (direction) {
            event.preventDefault();
            move(direction);
          }
        }}
      />
      <span className="task-find-count" role="status" aria-live="polite">
        {state.total ? `${state.activeIndex + 1}/${state.total}` : "0/0"}
      </span>
      <div className="task-find-controls">
        {button("上一个结果", <ArrowUp size={14} />, () => move("previous"), !state.total)}
        {button("下一个结果", <ArrowDown size={14} />, () => move("next"), !state.total)}
        {button(
          scope === "conversation" ? "搜索文件变更" : "搜索消息",
          scope === "conversation" ? <MessageCircle size={14} /> : <FileDiff size={14} />,
          onToggleScope,
          false,
          "切换搜索范围消息/文件",
        )}
      </div>
      <div className="task-find-close">{button("关闭查找", <X size={14} />, onClose)}</div>
    </div>
  );
}