@@ -5,18 +5,20 @@ import {
55 DialogPanel ,
66 DialogTitle ,
77 Input ,
8- Menu ,
9- MenuButton ,
10- MenuItem ,
11- MenuItems ,
8+ Popover ,
9+ PopoverButton ,
10+ PopoverPanel ,
1211} from "@headlessui/react" ;
1312import { debounce , groupBy , isNil } from "lodash-es" ;
14- import { FC , useMemo , useState } from "react" ;
13+ import { FC , useEffect , useMemo , useRef , useState } from "react" ;
1514import dayjs from "dayjs" ;
1615import isSameOrAfter from "dayjs/plugin/isSameOrAfter" ;
1716import clsx from "clsx" ;
1817import { Ellipsis , Pencil , RefreshCcw , Search , Trash2 } from "lucide-react" ;
1918import { useTranslation } from "react-i18next" ;
19+ import VisibleKey from "../VisibleKey" ;
20+ import { HISTORY_PANEL_ID } from "@/constants" ;
21+ import { useKeyPress } from "ahooks" ;
2022
2123dayjs . extend ( isSameOrAfter ) ;
2224
@@ -36,6 +38,9 @@ const HistoryList: FC<HistoryListProps> = (props) => {
3638 const { t } = useTranslation ( ) ;
3739 const [ isEdit , setIsEdit ] = useState ( false ) ;
3840 const [ isOpen , setIsOpen ] = useState ( false ) ;
41+ const listRef = useRef < HTMLDivElement > ( null ) ;
42+ const searchInputRef = useRef < HTMLInputElement > ( null ) ;
43+ const moreButtonRef = useRef < HTMLButtonElement > ( null ) ;
3944
4045 const sortedList = useMemo ( ( ) => {
4146 if ( isNil ( list ) ) return { } ;
@@ -74,13 +79,15 @@ const HistoryList: FC<HistoryListProps> = (props) => {
7479 {
7580 label : "history_list.menu.rename" ,
7681 icon : Pencil ,
82+ shortcut : "R" ,
7783 onClick : ( ) => {
7884 setIsEdit ( true ) ;
7985 } ,
8086 } ,
8187 {
8288 label : "history_list.menu.delete" ,
8389 icon : Trash2 ,
90+ shortcut : "D" ,
8491 iconColor : "#FF2018" ,
8592 onClick : ( ) => {
8693 setIsOpen ( true ) ;
@@ -92,17 +99,54 @@ const HistoryList: FC<HistoryListProps> = (props) => {
9299 return debounce ( ( value : string ) => onSearch ( value ) , 500 ) ;
93100 } , [ onSearch ] ) ;
94101
102+ useKeyPress ( [ "uparrow" , "downarrow" ] , ( _ , key ) => {
103+ const index = list . findIndex ( ( item ) => item . _id === active ?. _id ) ;
104+ const length = list . length ;
105+
106+ let nextIndex = index ;
107+
108+ switch ( key ) {
109+ case "uparrow" :
110+ nextIndex = index === 0 ? length - 1 : index - 1 ;
111+ break ;
112+ case "downarrow" :
113+ nextIndex = index === length - 1 ? 0 : index + 1 ;
114+ break ;
115+ }
116+
117+ onSelect ( list [ nextIndex ] ) ;
118+ } ) ;
119+
120+ useEffect ( ( ) => {
121+ if ( ! active ?. _id || ! listRef . current ) return ;
122+
123+ const activeEl = listRef . current . querySelector ( `#${ active . _id } ` ) ;
124+
125+ activeEl ?. scrollIntoView ( { behavior : "smooth" , block : "center" } ) ;
126+ } , [ active ?. _id ] ) ;
127+
95128 return (
96129 < div
130+ ref = { listRef }
131+ id = { HISTORY_PANEL_ID }
97132 className = { clsx (
98133 "h-full overflow-auto px-3 py-2 text-sm bg-[#F3F4F6] dark:bg-[#1F2937]"
99134 ) }
100135 >
101136 < div className = "flex gap-1 children:h-8" >
102137 < div className = "flex-1 flex items-center gap-2 px-2 rounded-lg border transition border-[#E6E6E6] bg-[#F8F9FA] dark:bg-[#2B3444] dark:border-[#343D4D] focus-within:border-[#0061FF]" >
103- < Search className = "size-4 text-[#6B7280]" />
138+ < VisibleKey
139+ shortcut = "I"
140+ onKeyPress = { ( ) => {
141+ searchInputRef . current ?. focus ( ) ;
142+ } }
143+ >
144+ < Search className = "size-4 text-[#6B7280]" />
145+ </ VisibleKey >
104146
105147 < Input
148+ autoFocus
149+ ref = { searchInputRef }
106150 className = "w-full bg-transparent outline-none"
107151 placeholder = { t ( "history_list.search.placeholder" ) }
108152 onChange = { ( event ) => {
@@ -115,7 +159,9 @@ const HistoryList: FC<HistoryListProps> = (props) => {
115159 className = "size-8 flex items-center justify-center rounded-lg border text-[#0072FF] border-[#E6E6E6] bg-[#F3F4F6] dark:border-[#343D4D] dark:bg-[#1F2937] hover:bg-[#F8F9FA] dark:hover:bg-[#353F4D] cursor-pointer transition"
116160 onClick = { onRefresh }
117161 >
118- < RefreshCcw className = "size-4" />
162+ < VisibleKey shortcut = "R" onKeyPress = { onRefresh } >
163+ < RefreshCcw className = "size-4" />
164+ </ VisibleKey >
119165 </ div >
120166 </ div >
121167
@@ -135,6 +181,7 @@ const HistoryList: FC<HistoryListProps> = (props) => {
135181 return (
136182 < li
137183 key = { _id }
184+ id = { _id }
138185 className = { clsx (
139186 "flex items-center mt-1 h-10 rounded-lg cursor-pointer hover:bg-[#EDEDED] dark:hover:bg-[#353F4D] transition" ,
140187 {
@@ -178,48 +225,73 @@ const HistoryList: FC<HistoryListProps> = (props) => {
178225 < span className = "truncate" > { title } </ span >
179226 ) }
180227
181- < Menu >
228+ < div className = "flex items-center gap-2" >
182229 { isActive && ! isEdit && (
183- < MenuButton >
184- < Ellipsis className = "size-4 text-[#979797]" />
185- </ MenuButton >
230+ < VisibleKey
231+ shortcut = "↑↓"
232+ rootClassName = "w-6"
233+ shortcutClassName = "w-6"
234+ />
186235 ) }
187236
188- < MenuItems
189- anchor = "bottom"
190- className = "flex flex-col rounded-lg shadow-md z-100 bg-white dark:bg-[#202126] p-1 border border-black/2 dark:border-white/10"
191- onClick = { ( event ) => {
192- event . stopPropagation ( ) ;
193- } }
194- >
195- { menuItems . map ( ( menuItem ) => {
196- const {
197- label,
198- icon : Icon ,
199- iconColor,
200- onClick,
201- } = menuItem ;
202-
203- return (
204- < MenuItem key = { label } >
237+ < Popover >
238+ { isActive && ! isEdit && (
239+ < PopoverButton
240+ ref = { moreButtonRef }
241+ className = "flex gap-2"
242+ >
243+ < VisibleKey
244+ shortcut = "O"
245+ onKeyPress = { ( ) => {
246+ moreButtonRef . current ?. click ( ) ;
247+ } }
248+ >
249+ < Ellipsis className = "size-4 text-[#979797]" />
250+ </ VisibleKey >
251+ </ PopoverButton >
252+ ) }
253+
254+ < PopoverPanel
255+ anchor = "bottom"
256+ className = "flex flex-col rounded-lg shadow-md z-100 bg-white dark:bg-[#202126] p-1 border border-black/2 dark:border-white/10"
257+ onClick = { ( event ) => {
258+ event . stopPropagation ( ) ;
259+ } }
260+ >
261+ { menuItems . map ( ( menuItem ) => {
262+ const {
263+ label,
264+ icon : Icon ,
265+ shortcut,
266+ iconColor,
267+ onClick,
268+ } = menuItem ;
269+
270+ return (
205271 < button
272+ key = { label }
206273 className = "flex items-center gap-2 px-3 py-2 text-sm rounded-md hover:bg-[#EDEDED] dark:hover:bg-[#2B2C31] transition"
207- onClick = { ( ) => onClick ( ) }
274+ onClick = { onClick }
208275 >
209- < Icon
210- className = "size-4"
211- style = { {
212- color : iconColor ,
213- } }
214- />
276+ < VisibleKey
277+ shortcut = { shortcut }
278+ onKeyPress = { onClick }
279+ >
280+ < Icon
281+ className = "size-4"
282+ style = { {
283+ color : iconColor ,
284+ } }
285+ />
286+ </ VisibleKey >
215287
216288 < span > { t ( label ) } </ span >
217289 </ button >
218- </ MenuItem >
219- ) ;
220- } ) }
221- </ MenuItems >
222- </ Menu >
290+ ) ;
291+ } ) }
292+ </ PopoverPanel >
293+ </ Popover >
294+ </ div >
223295 </ div >
224296 </ li >
225297 ) ;
0 commit comments