message-nav.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { UserMessage } from "@opencode-ai/sdk/v2"
  2. import { HoverCard } from "@kobalte/core/hover-card"
  3. import { ComponentProps, For, Match, Show, createSignal, splitProps, Switch } from "solid-js"
  4. import { DiffChanges } from "./diff-changes"
  5. import { useI18n } from "../context/i18n"
  6. export function MessageNav(
  7. props: ComponentProps<"ul"> & {
  8. messages: UserMessage[]
  9. current?: UserMessage
  10. size: "normal" | "compact"
  11. onMessageSelect: (message: UserMessage) => void
  12. getLabel?: (message: UserMessage) => string | undefined
  13. },
  14. ) {
  15. const i18n = useI18n()
  16. const [local, others] = splitProps(props, ["messages", "current", "size", "onMessageSelect", "getLabel", "class"])
  17. const [hovercardOpen, setHovercardOpen] = createSignal(false)
  18. const selectMessage = (message: UserMessage) => {
  19. setHovercardOpen(false)
  20. local.onMessageSelect(message)
  21. }
  22. const content = (className?: string) => (
  23. <ul role="list" data-component="message-nav" data-size={local.size} class={className} {...others}>
  24. <For each={local.messages}>
  25. {(message) => {
  26. const handleClick = () => selectMessage(message)
  27. const handleKeyPress = (event: KeyboardEvent) => {
  28. if (event.key !== "Enter" && event.key !== " ") return
  29. event.preventDefault()
  30. selectMessage(message)
  31. }
  32. return (
  33. <li data-slot="message-nav-item">
  34. <Switch>
  35. <Match when={local.size === "compact"}>
  36. <div
  37. data-slot="message-nav-tick-button"
  38. data-active={message.id === local.current?.id || undefined}
  39. role="button"
  40. tabindex={0}
  41. onClick={handleClick}
  42. onKeyDown={handleKeyPress}
  43. >
  44. <div data-slot="message-nav-tick-line" />
  45. </div>
  46. </Match>
  47. <Match when={local.size === "normal"}>
  48. <button data-slot="message-nav-message-button" onClick={handleClick} onKeyDown={handleKeyPress}>
  49. <DiffChanges changes={message.summary?.diffs ?? []} variant="bars" />
  50. <div
  51. data-slot="message-nav-title-preview"
  52. data-active={message.id === local.current?.id || undefined}
  53. >
  54. <Show
  55. when={local.getLabel?.(message) ?? message.summary?.title}
  56. fallback={i18n.t("ui.messageNav.newMessage")}
  57. >
  58. {local.getLabel?.(message) ?? message.summary?.title}
  59. </Show>
  60. </div>
  61. </button>
  62. </Match>
  63. </Switch>
  64. </li>
  65. )
  66. }}
  67. </For>
  68. </ul>
  69. )
  70. return (
  71. <Switch>
  72. <Match when={local.size === "compact"}>
  73. <HoverCard
  74. open={hovercardOpen()}
  75. onOpenChange={setHovercardOpen}
  76. openDelay={0}
  77. closeDelay={120}
  78. placement="right-start"
  79. gutter={8}
  80. overflowPadding={24}
  81. fitViewport
  82. >
  83. <HoverCard.Trigger as="div" data-component="message-nav-hovercard" class={local.class}>
  84. {content()}
  85. </HoverCard.Trigger>
  86. <HoverCard.Portal>
  87. <HoverCard.Content data-slot="message-nav-hovercard-content">
  88. <MessageNav {...props} size="normal" class="" onMessageSelect={selectMessage} />
  89. </HoverCard.Content>
  90. </HoverCard.Portal>
  91. </HoverCard>
  92. </Match>
  93. <Match when={local.size === "normal"}>{content(local.class)}</Match>
  94. </Switch>
  95. )
  96. }