| import { memo, useEffect, useRef } from 'react'; |
| import { IconButton } from '~/components/ui/IconButton'; |
| import type { PreviewInfo } from '~/lib/stores/previews'; |
|
|
| interface PortDropdownProps { |
| activePreviewIndex: number; |
| setActivePreviewIndex: (index: number) => void; |
| isDropdownOpen: boolean; |
| setIsDropdownOpen: (value: boolean) => void; |
| setHasSelectedPreview: (value: boolean) => void; |
| previews: PreviewInfo[]; |
| } |
|
|
| export const PortDropdown = memo( |
| ({ |
| activePreviewIndex, |
| setActivePreviewIndex, |
| isDropdownOpen, |
| setIsDropdownOpen, |
| setHasSelectedPreview, |
| previews, |
| }: PortDropdownProps) => { |
| const dropdownRef = useRef<HTMLDivElement>(null); |
|
|
| |
| const sortedPreviews = previews |
| .map((previewInfo, index) => ({ ...previewInfo, index })) |
| .sort((a, b) => a.port - b.port); |
|
|
| |
| useEffect(() => { |
| const handleClickOutside = (event: MouseEvent) => { |
| if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { |
| setIsDropdownOpen(false); |
| } |
| }; |
|
|
| if (isDropdownOpen) { |
| window.addEventListener('mousedown', handleClickOutside); |
| } else { |
| window.removeEventListener('mousedown', handleClickOutside); |
| } |
|
|
| return () => { |
| window.removeEventListener('mousedown', handleClickOutside); |
| }; |
| }, [isDropdownOpen]); |
|
|
| return ( |
| <div className="relative z-port-dropdown" ref={dropdownRef}> |
| <IconButton icon="i-ph:plug" onClick={() => setIsDropdownOpen(!isDropdownOpen)} /> |
| {isDropdownOpen && ( |
| <div className="absolute right-0 mt-2 bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor rounded shadow-sm min-w-[140px] dropdown-animation"> |
| <div className="px-4 py-2 border-b border-bolt-elements-borderColor text-sm font-semibold text-bolt-elements-textPrimary"> |
| Ports |
| </div> |
| {sortedPreviews.map((preview) => ( |
| <div |
| key={preview.port} |
| className="flex items-center px-4 py-2 cursor-pointer hover:bg-bolt-elements-item-backgroundActive" |
| onClick={() => { |
| setActivePreviewIndex(preview.index); |
| setIsDropdownOpen(false); |
| setHasSelectedPreview(true); |
| }} |
| > |
| <span |
| className={ |
| activePreviewIndex === preview.index |
| ? 'text-bolt-elements-item-contentAccent' |
| : 'text-bolt-elements-item-contentDefault group-hover:text-bolt-elements-item-contentActive' |
| } |
| > |
| {preview.port} |
| </span> |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| ); |
| }, |
| ); |
|
|