unnamed-ui
列表

History Item

Composed history list item with trailing slots and interactive states

"use client";

import * as React from "react";
import { HistoryItem } from "@/components/composed/history-item/history-item";
import { cn } from "@/lib/utils";
import * as Popover from "@radix-ui/react-popover";
import { Clock, Pin, Trash2, Ellipsis, Copy } from "lucide-react";

function HoverMorePopover({
  open,
  onOpenChange,
  children,
}: {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  children: React.ReactNode;
}) {
  const closeTimer = React.useRef<number | null>(null);

  const clearCloseTimer = () => {
    if (closeTimer.current != null) {
      window.clearTimeout(closeTimer.current);
      closeTimer.current = null;
    }
  };

  const scheduleClose = () => {
    clearCloseTimer();
    closeTimer.current = window.setTimeout(() => onOpenChange(false), 120);
  };

  React.useEffect(() => {
    return () => clearCloseTimer();
  }, []);

  return (
    <Popover.Root open={open} onOpenChange={onOpenChange}>
      <Popover.Trigger asChild>
        <span
          className="inline-flex items-center"
          onMouseEnter={() => {
            clearCloseTimer();
            onOpenChange(true);
          }}
          onMouseLeave={() => {
            scheduleClose();
          }}
        >
          {children}
        </span>
      </Popover.Trigger>
      <Popover.Portal>
        <Popover.Content
          side="bottom"
          align="end"
          sideOffset={8}
          onMouseEnter={() => {
            clearCloseTimer();
            onOpenChange(true);
          }}
          onMouseLeave={() => {
            scheduleClose();
          }}
          className={cn(
            "z-50",
            "min-w-[160px]",
            "rounded-[var(--radius-lg)]",
            "border border-[var(--Border-border-neutral)]",
            "bg-[var(--Container-bg-container)]",
            "shadow-[var(--shadow-basic)]",
            "p-[var(--Padding-padding-com-sm)]",
          )}
        >
          <div className="flex flex-col">
            <button
              type="button"
              className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-[var(--Container-bg-neutral-light)]"
            >
              <Pin className="size-4 text-[var(--Text-text-secondary)]" />
              <span className="text-[var(--Text-text-primary)] font-size-1 leading-[var(--line-height-1)]">
                Pin
              </span>
            </button>
            <button
              type="button"
              className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-[var(--Container-bg-neutral-light)]"
            >
              <Copy className="size-4 text-[var(--Text-text-secondary)]" />
              <span className="text-[var(--Text-text-primary)] font-size-1 leading-[var(--line-height-1)]">
                Duplicate
              </span>
            </button>
            <button
              type="button"
              className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-[var(--Container-bg-neutral-light)]"
            >
              <Trash2 className="size-4 text-[var(--Text-text-secondary)]" />
              <span className="text-[var(--Text-text-primary)] font-size-1 leading-[var(--line-height-1)]">
                Delete
              </span>
            </button>
          </div>
        </Popover.Content>
      </Popover.Portal>
    </Popover.Root>
  );
}

export function HistoryItemDemo() {
  const [moreOpen1, setMoreOpen1] = React.useState(false);
  const [moreOpen2, setMoreOpen2] = React.useState(false);

  return (
    <div className="flex flex-col gap-[var(--Gap-gap-md)]">
      <HistoryItem
        active={moreOpen1}
        title="默认历史记录项"
        trailing={
          <div className="inline-flex items-center gap-[var(--Gap-gap-xs)]">
            <HoverMorePopover open={moreOpen1} onOpenChange={setMoreOpen1}>
              <span className="inline-flex items-center" aria-label="More">
                <Ellipsis className="size-4" />
              </span>
            </HoverMorePopover>
          </div>
        }
      />

      <HistoryItem
        selected
        title="选中状态历史记录项"
        trailing={
          <div className="inline-flex items-center gap-[var(--Gap-gap-xs)]">
            <Clock className="size-4" />
          </div>
        }
      />

      <HistoryItem
        active={moreOpen2}
        title="Hover 展示操作:删除 + 更多(popover)"
        hoverTrailing={
          <div className="inline-flex items-center gap-[var(--Gap-gap-xs)]">
            <HoverMorePopover open={moreOpen2} onOpenChange={setMoreOpen2}>
              <span className="inline-flex items-center" aria-label="More">
                <Ellipsis className="size-4" />
              </span>
            </HoverMorePopover>
          </div>
        }
      />
    </div>
  );
}

History Item 组件用于展示历史记录列表中的单个条目,支持选中、激活状态管理,并提供常驻和悬停时的尾部操作区,适用于聊天历史、浏览记录、文档历史等场景。

概述

  • 多状态支持:支持选中(selected)和激活(active)两种独立状态
  • 双尾部插槽:常驻尾部内容 + 悬停时显示的操作按钮
  • 无障碍访问:完整的 ARIA 属性支持,提升可访问性
  • 交互优化:悬停、点击等交互状态清晰可见
  • 灵活扩展:基于原语组件构建,支持深度定制

快速开始

import { HistoryItem } from "@/registry/wuhan/composed/history-item";
import { Trash2 } from "lucide-react";

export function Example() {
  return (
    <HistoryItem
      title="今天的对话"
      selected={true}
      trailing={<span className="text-xs text-muted-foreground">10:30</span>}
      hoverTrailing={
        <button className="p-1 hover:bg-muted rounded">
          <Trash2 className="w-4 h-4" />
        </button>
      }
      onClick={() => console.log("clicked")}
    />
  );
}

特性

  • 选中状态(selected):用于标识当前激活的历史记录项
  • 活动状态(active):用于标识鼠标悬停或键盘焦点的项
  • 常驻尾部内容:始终显示的尾部信息(如时间戳、徽章等)
  • 悬停操作区:仅在悬停时显示的操作按钮(如删除、编辑等)
  • ARIA 支持:自动添加 aria-selected 和 aria-current 属性
  • 可点击:支持点击事件处理

安装

pnpm dlx shadcn@latest add http://localhost:3000/r/wuhan/history-item.json

代码演示

基本

基础的历史记录项展示。

"use client";

import * as React from "react";
import { HistoryItem } from "@/components/composed/history-item/history-item";
import { cn } from "@/lib/utils";
import * as Popover from "@radix-ui/react-popover";
import { Clock, Pin, Trash2, Ellipsis, Copy } from "lucide-react";

function HoverMorePopover({
  open,
  onOpenChange,
  children,
}: {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  children: React.ReactNode;
}) {
  const closeTimer = React.useRef<number | null>(null);

  const clearCloseTimer = () => {
    if (closeTimer.current != null) {
      window.clearTimeout(closeTimer.current);
      closeTimer.current = null;
    }
  };

  const scheduleClose = () => {
    clearCloseTimer();
    closeTimer.current = window.setTimeout(() => onOpenChange(false), 120);
  };

  React.useEffect(() => {
    return () => clearCloseTimer();
  }, []);

  return (
    <Popover.Root open={open} onOpenChange={onOpenChange}>
      <Popover.Trigger asChild>
        <span
          className="inline-flex items-center"
          onMouseEnter={() => {
            clearCloseTimer();
            onOpenChange(true);
          }}
          onMouseLeave={() => {
            scheduleClose();
          }}
        >
          {children}
        </span>
      </Popover.Trigger>
      <Popover.Portal>
        <Popover.Content
          side="bottom"
          align="end"
          sideOffset={8}
          onMouseEnter={() => {
            clearCloseTimer();
            onOpenChange(true);
          }}
          onMouseLeave={() => {
            scheduleClose();
          }}
          className={cn(
            "z-50",
            "min-w-[160px]",
            "rounded-[var(--radius-lg)]",
            "border border-[var(--Border-border-neutral)]",
            "bg-[var(--Container-bg-container)]",
            "shadow-[var(--shadow-basic)]",
            "p-[var(--Padding-padding-com-sm)]",
          )}
        >
          <div className="flex flex-col">
            <button
              type="button"
              className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-[var(--Container-bg-neutral-light)]"
            >
              <Pin className="size-4 text-[var(--Text-text-secondary)]" />
              <span className="text-[var(--Text-text-primary)] font-size-1 leading-[var(--line-height-1)]">
                Pin
              </span>
            </button>
            <button
              type="button"
              className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-[var(--Container-bg-neutral-light)]"
            >
              <Copy className="size-4 text-[var(--Text-text-secondary)]" />
              <span className="text-[var(--Text-text-primary)] font-size-1 leading-[var(--line-height-1)]">
                Duplicate
              </span>
            </button>
            <button
              type="button"
              className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-[var(--Container-bg-neutral-light)]"
            >
              <Trash2 className="size-4 text-[var(--Text-text-secondary)]" />
              <span className="text-[var(--Text-text-primary)] font-size-1 leading-[var(--line-height-1)]">
                Delete
              </span>
            </button>
          </div>
        </Popover.Content>
      </Popover.Portal>
    </Popover.Root>
  );
}

export function HistoryItemDemo() {
  const [moreOpen1, setMoreOpen1] = React.useState(false);
  const [moreOpen2, setMoreOpen2] = React.useState(false);

  return (
    <div className="flex flex-col gap-[var(--Gap-gap-md)]">
      <HistoryItem
        active={moreOpen1}
        title="默认历史记录项"
        trailing={
          <div className="inline-flex items-center gap-[var(--Gap-gap-xs)]">
            <HoverMorePopover open={moreOpen1} onOpenChange={setMoreOpen1}>
              <span className="inline-flex items-center" aria-label="More">
                <Ellipsis className="size-4" />
              </span>
            </HoverMorePopover>
          </div>
        }
      />

      <HistoryItem
        selected
        title="选中状态历史记录项"
        trailing={
          <div className="inline-flex items-center gap-[var(--Gap-gap-xs)]">
            <Clock className="size-4" />
          </div>
        }
      />

      <HistoryItem
        active={moreOpen2}
        title="Hover 展示操作:删除 + 更多(popover)"
        hoverTrailing={
          <div className="inline-flex items-center gap-[var(--Gap-gap-xs)]">
            <HoverMorePopover open={moreOpen2} onOpenChange={setMoreOpen2}>
              <span className="inline-flex items-center" aria-label="More">
                <Ellipsis className="size-4" />
              </span>
            </HoverMorePopover>
          </div>
        }
      />
    </div>
  );
}

选中状态

基础历史列表,带时间戳和选中状态。

"use client";

import * as React from "react";
import { HistoryItem } from "@/components/composed/history-item/history-item";

export function HistoryItemBasic() {
  const [selected, setSelected] = React.useState("item-1");

  return (
    <div className="w-[240px] space-y-1">
      <HistoryItem
        title="如何学习 React?"
        trailing={<span className="text-xs text-muted-foreground">10:30</span>}
        selected={selected === "item-1"}
        onClick={() => setSelected("item-1")}
      />
      <HistoryItem
        title="TypeScript 最佳实践"
        trailing={<span className="text-xs text-muted-foreground">09:15</span>}
        selected={selected === "item-2"}
        onClick={() => setSelected("item-2")}
      />
      <HistoryItem
        title="CSS Grid 布局指南"
        trailing={<span className="text-xs text-muted-foreground">昨天</span>}
        selected={selected === "item-3"}
        onClick={() => setSelected("item-3")}
      />
    </div>
  );
}

操作项

带悬停操作按钮的历史项,支持编辑和删除。

"use client";

import * as React from "react";
import { HistoryItem } from "@/components/composed/history-item/history-item";
import { Trash2, Edit } from "lucide-react";

export function HistoryItemWithActions() {
  const [items, setItems] = React.useState([
    { id: "1", title: "项目需求分析文档", time: "14:30" },
    { id: "2", title: "UI 设计稿 v2.0", time: "11:20" },
    { id: "3", title: "API 接口文档", time: "昨天" },
  ]);
  const [selected, setSelected] = React.useState("1");

  const handleDelete = (id: string) => {
    setItems((prev) => prev.filter((item) => item.id !== id));
    if (selected === id) {
      setSelected(items[0]?.id || "");
    }
  };

  const handleEdit = (id: string) => {
    console.log("编辑:", id);
  };

  return (
    <div className="w-[240px] space-y-1">
      {items.map((item) => (
        <HistoryItem
          key={item.id}
          title={item.title}
          trailing={
            <span className="text-xs text-muted-foreground">{item.time}</span>
          }
          hoverTrailing={
            <div className="flex gap-1">
              <div
                role="button"
                tabIndex={0}
                onClick={(e) => {
                  e.stopPropagation();
                  handleEdit(item.id);
                }}
                onKeyDown={(e) => {
                  if (e.key === "Enter" || e.key === " ") {
                    e.preventDefault();
                    e.stopPropagation();
                    handleEdit(item.id);
                  }
                }}
                className="p-1 hover:bg-muted rounded cursor-pointer"
              >
                <Edit className="w-3.5 h-3.5" />
              </div>
              <div
                role="button"
                tabIndex={0}
                onClick={(e) => {
                  e.stopPropagation();
                  handleDelete(item.id);
                }}
                onKeyDown={(e) => {
                  if (e.key === "Enter" || e.key === " ") {
                    e.preventDefault();
                    e.stopPropagation();
                    handleDelete(item.id);
                  }
                }}
                className="p-1 hover:bg-destructive/10 rounded cursor-pointer"
              >
                <Trash2 className="w-3.5 h-3.5" />
              </div>
            </div>
          }
          selected={selected === item.id}
          onClick={() => setSelected(item.id)}
        />
      ))}
    </div>
  );
}

带图标

带图标和徽章的历史项。

"use client";

import * as React from "react";
import { HistoryItem } from "@/components/composed/history-item/history-item";
import {
  MessageSquare,
  Code,
  FileText,
  Image as ImageIcon,
} from "lucide-react";

export function HistoryItemWithIcons() {
  const [selected, setSelected] = React.useState("1");

  const items = [
    {
      id: "1",
      title: "聊天对话记录",
      icon: <MessageSquare className="w-4 h-4" />,
      badge: (
        <span className="px-1.5 py-0.5 text-xs bg-blue-100 text-blue-700 rounded">
          3
        </span>
      ),
    },
    {
      id: "2",
      title: "代码片段收藏",
      icon: <Code className="w-4 h-4" />,
      badge: (
        <span className="px-1.5 py-0.5 text-xs bg-green-100 text-green-700 rounded">

        </span>
      ),
    },
    {
      id: "3",
      title: "文档草稿",
      icon: <FileText className="w-4 h-4" />,
      badge: null,
    },
    {
      id: "4",
      title: "设计稿",
      icon: <ImageIcon className="w-4 h-4" />,
      badge: (
        <span className="px-1.5 py-0.5 text-xs bg-purple-100 text-purple-700 rounded">
          5
        </span>
      ),
    },
  ];

  return (
    <div className="w-[240px] space-y-1">
      {items.map((item) => (
        <HistoryItem
          key={item.id}
          title={
            <div className="flex items-center gap-2">
              <span className="text-muted-foreground">{item.icon}</span>
              <span className="truncate">{item.title}</span>
            </div>
          }
          trailing={item.badge}
          selected={selected === item.id}
          onClick={() => setSelected(item.id)}
        />
      ))}
    </div>
  );
}

状态

展示不同状态下的历史项样式。

"use client";

import * as React from "react";
import { HistoryItem } from "@/components/composed/history-item/history-item";

export function HistoryItemStates() {
  const [selected, setSelected] = React.useState("item-2");
  const [active, setActive] = React.useState<string | null>(null);

  return (
    <div className="w-[240px] space-y-1">
      <HistoryItem
        title="普通状态"
        trailing={<span className="text-xs text-muted-foreground">10:30</span>}
        onClick={() => setSelected("item-1")}
        onMouseEnter={() => setActive("item-1")}
        onMouseLeave={() => setActive(null)}
      />
      <HistoryItem
        title="选中状态"
        trailing={<span className="text-xs text-muted-foreground">09:15</span>}
        selected={selected === "item-2"}
        onClick={() => setSelected("item-2")}
        onMouseEnter={() => setActive("item-2")}
        onMouseLeave={() => setActive(null)}
      />
      <HistoryItem
        title="活动状态"
        trailing={<span className="text-xs text-muted-foreground">昨天</span>}
        active={active === "item-3"}
        onClick={() => setSelected("item-3")}
        onMouseEnter={() => setActive("item-3")}
        onMouseLeave={() => setActive(null)}
      />
      <HistoryItem
        title="选中且活动"
        trailing={<span className="text-xs text-muted-foreground">2天前</span>}
        selected={selected === "item-4"}
        active={active === "item-4"}
        onClick={() => setSelected("item-4")}
        onMouseEnter={() => setActive("item-4")}
        onMouseLeave={() => setActive(null)}
      />
    </div>
  );
}

列表示例

完整的历史列表示例,带收藏功能。

最近访问
"use client";

import * as React from "react";
import { HistoryItem } from "@/components/composed/history-item/history-item";
import { Star, StarOff } from "lucide-react";

export function HistoryItemList() {
  const [items, setItems] = React.useState([
    { id: "1", title: "周报总结 - 第12周", time: "2小时前", starred: true },
    { id: "2", title: "产品需求评审会议纪要", time: "5小时前", starred: false },
    { id: "3", title: "技术方案设计文档 v1.0", time: "昨天", starred: true },
    { id: "4", title: "UI/UX 设计规范", time: "2天前", starred: false },
    { id: "5", title: "数据库设计方案", time: "3天前", starred: false },
  ]);
  const [selected, setSelected] = React.useState("1");

  const toggleStar = (id: string) => {
    setItems((prev) =>
      prev.map((item) =>
        item.id === id ? { ...item, starred: !item.starred } : item,
      ),
    );
  };

  return (
    <div className="w-[260px] border rounded-lg p-2">
      <div className="text-sm font-medium text-muted-foreground mb-2 px-3">
        最近访问
      </div>
      <div className="space-y-0.5">
        {items.map((item) => (
          <HistoryItem
            key={item.id}
            title={item.title}
            trailing={
              <span className="text-xs text-muted-foreground">{item.time}</span>
            }
            hoverTrailing={
              <div
                role="button"
                tabIndex={0}
                onClick={(e) => {
                  e.stopPropagation();
                  toggleStar(item.id);
                }}
                onKeyDown={(e) => {
                  if (e.key === "Enter" || e.key === " ") {
                    e.preventDefault();
                    e.stopPropagation();
                    toggleStar(item.id);
                  }
                }}
                className="p-1 hover:bg-muted rounded cursor-pointer"
              >
                {item.starred ? (
                  <Star className="w-3.5 h-3.5 fill-yellow-400 text-yellow-400" />
                ) : (
                  <StarOff className="w-3.5 h-3.5" />
                )}
              </div>
            }
            selected={selected === item.id}
            onClick={() => setSelected(item.id)}
          />
        ))}
      </div>
    </div>
  );
}

API

HistoryItem

历史记录列表项组件,支持多种状态和交互。

Props

PropTypeDefaultDescription
titlestring-历史记录项的标题文本(必填)
trailingReactNode-常驻的尾部内容,始终显示
hoverTrailingReactNode-仅在悬停时显示的尾部内容(通常用于操作按钮)
selectedbooleanfalse是否为选中状态
activebooleanfalse是否为激活状态(悬停或焦点)
onClick() => void-点击事件处理函数
classNamestring-额外的样式类名

Example

import { HistoryItem } from "@/registry/wuhan/composed/history-item";
import { Clock, Trash2, Pin } from "lucide-react";

function ChatHistory() {
  const [selectedId, setSelectedId] = React.useState("1");
  
  return (
    <div className="w-64 space-y-1">
      {/* 基础用法 */}
      <HistoryItem
        title="关于 React 的讨论"
        selected={selectedId === "1"}
        onClick={() => setSelectedId("1")}
      />
      
      {/* 带时间戳的历史项 */}
      <HistoryItem
        title="TypeScript 最佳实践"
        selected={selectedId === "2"}
        trailing={
          <span className="text-xs text-muted-foreground">
            2小时前
          </span>
        }
        onClick={() => setSelectedId("2")}
      />
      
      {/* 带悬停操作的历史项 */}
      <HistoryItem
        title="如何学习 Next.js"
        selected={selectedId === "3"}
        trailing={<Clock className="w-3 h-3 text-muted-foreground" />}
        hoverTrailing={
          <div className="flex gap-1">
            <button className="p-1 hover:bg-accent rounded">
              <Pin className="w-3 h-3" />
            </button>
            <button className="p-1 hover:bg-accent rounded text-destructive">
              <Trash2 className="w-3 h-3" />
            </button>
          </div>
        }
        onClick={() => setSelectedId("3")}
      />
    </div>
  );
}

使用场景

  • 聊天历史:显示历史对话列表,支持选中当前会话、删除会话等操作
  • 浏览记录:展示用户的浏览历史,支持快速访问和管理
  • 文档历史:显示最近打开的文档列表
  • 搜索历史:展示用户的搜索记录,支持删除和重新搜索
  • 操作历史:记录用户的操作轨迹,支持回溯
  • 收藏列表:展示收藏的内容,支持管理操作

最佳实践

  1. 状态管理:使用 selected 标识当前激活项,active 由组件自动管理悬停状态
  2. 操作按钮:将破坏性操作(如删除)放在 hoverTrailing 中,避免误触
  3. 视觉层次:使用 trailing 显示次要信息,保持标题的视觉优先级
  4. 交互反馈:确保点击和悬停有明确的视觉反馈
  5. 键盘导航:配合键盘事件实现上下键导航
  6. 无障碍性:组件已内置 ARIA 属性,无需手动添加

注意事项

  • selectedactive 是两个独立的状态,可以同时存在
  • hoverTrailing 会在悬停时替换 trailing 的显示
  • 点击 hoverTrailing 中的按钮时需要阻止事件冒泡,避免触发 onClick
  • 建议在父组件中管理选中状态,而非在 HistoryItem 内部
  • 标题文本过长时会自动截断并显示省略号

原语组件

History Item 基于以下原语组件构建:

  • HistoryItemPrimitive - 历史项容器原语
  • HistoryItemTitlePrimitive - 标题文本原语
  • HistoryItemTrailingPrimitive - 尾部内容原语
  • HistoryItemHoverTrailingPrimitive - 悬停尾部原语

原语组件提供了基础的样式和结构,可以在 registry/wuhan/blocks/history-item/history-item-01.tsx 中找到。

样式定制

组件使用 Tailwind CSS,可以通过以下方式定制:

// 通过 className 添加额外样式
<HistoryItem 
  title="自定义样式项"
  className="border-l-4 border-primary"
  selected={true}
/>

// 自定义 trailing 样式
<HistoryItem
  title="带徽章的项"
  trailing={
    <span className="px-2 py-0.5 text-xs bg-primary text-primary-foreground rounded-full">

    </span>
  }
/>

扩展示例

带图标的历史项

import { HistoryItem } from "@/registry/wuhan/composed/history-item";
import { MessageSquare, FileText, Code } from "lucide-react";

function HistoryWithIcons() {
  return (
    <div className="w-64 space-y-1">
      <div className="flex items-center gap-2 px-2 py-1.5">
        <MessageSquare className="w-4 h-4 text-muted-foreground" />
        <HistoryItem title="聊天记录" selected={true} />
      </div>
      <div className="flex items-center gap-2 px-2 py-1.5">
        <FileText className="w-4 h-4 text-muted-foreground" />
        <HistoryItem title="文档编辑" />
      </div>
    </div>
  );
}

分组历史列表

import { HistoryItem } from "@/registry/wuhan/composed/history-item";

function GroupedHistory() {
  return (
    <div className="w-64">
      <div className="px-2 py-1 text-xs font-medium text-muted-foreground">
        今天
      </div>
      <div className="space-y-1">
        <HistoryItem title="React 性能优化" selected={true} />
        <HistoryItem title="TypeScript 类型体操" />
      </div>
      
      <div className="px-2 py-1 mt-2 text-xs font-medium text-muted-foreground">
        昨天
      </div>
      <div className="space-y-1">
        <HistoryItem title="Next.js 路由配置" />
        <HistoryItem title="Tailwind CSS 技巧" />
      </div>
    </div>
  );
}

键盘导航支持

import { HistoryItem } from "@/registry/wuhan/composed/history-item";
import { useState } from "react";

function KeyboardNavigableHistory() {
  const [selectedIndex, setSelectedIndex] = useState(0);
  const items = ["项目 1", "项目 2", "项目 3"];
  
  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "ArrowDown") {
      setSelectedIndex((prev) => Math.min(prev + 1, items.length - 1));
    } else if (e.key === "ArrowUp") {
      setSelectedIndex((prev) => Math.max(prev - 1, 0));
    }
  };
  
  return (
    <div className="w-64 space-y-1" onKeyDown={handleKeyDown} tabIndex={0}>
      {items.map((item, index) => (
        <HistoryItem
          key={index}
          title={item}
          selected={selectedIndex === index}
          onClick={() => setSelectedIndex(index)}
        />
      ))}
    </div>
  );
}