@@ -11,8 +11,6 @@ import {
1111 MdPushPin ,
1212 MdMoreVert ,
1313 MdSearch ,
14- MdArrowDownward ,
15- MdArrowUpward ,
1614} from 'react-icons/md'
1715import { keepPreviousData , queryOptions , useQuery } from '@tanstack/react-query'
1816import * as Plot from '@observablehq/plot'
@@ -726,10 +724,17 @@ function NpmStatsChart({
726724 )
727725}
728726
729- function PackageSearch ( ) {
727+ function PackageSearch ( {
728+ onSelect,
729+ placeholder = 'Search for a package...' ,
730+ autoFocus = false ,
731+ } : {
732+ onSelect : ( packageName : string ) => void
733+ placeholder ?: string
734+ autoFocus ?: boolean
735+ } ) {
730736 const [ inputValue , setInputValue ] = React . useState ( '' )
731737 const [ open , setOpen ] = React . useState ( false )
732- const navigate = Route . useNavigate ( )
733738 const containerRef = React . useRef < HTMLDivElement > ( null )
734739
735740 const [ debouncedInputValue ] = useDebouncedValue ( inputValue , {
@@ -791,19 +796,7 @@ function PackageSearch() {
791796 const selectedItem = searchQuery . data ?. find ( ( item ) => item . name === value )
792797 if ( ! selectedItem ) return
793798
794- navigate ( {
795- to : '.' ,
796- search : ( prev ) => ( {
797- ...prev ,
798- packageGroups : [
799- ...prev . packageGroups ,
800- {
801- packages : [ { name : selectedItem . name } ] ,
802- } ,
803- ] ,
804- } ) ,
805- resetScroll : false ,
806- } )
799+ onSelect ( selectedItem . name )
807800 setInputValue ( '' )
808801 setOpen ( false )
809802 }
@@ -815,11 +808,12 @@ function PackageSearch() {
815808 < div className = "flex items-center gap-1" >
816809 < MdSearch className = "text-lg" />
817810 < Command . Input
818- placeholder = "Search for a package..."
811+ placeholder = { placeholder }
819812 className = "w-full bg-gray-500/10 rounded-md px-2 py-1 min-w-[200px] text-sm"
820813 value = { inputValue }
821814 onValueChange = { handleInputChange }
822815 onFocus = { ( ) => setOpen ( true ) }
816+ autoFocus = { autoFocus }
823817 />
824818 </ div >
825819 { searchQuery . isLoading && (
@@ -925,10 +919,6 @@ function RouteComponent() {
925919 const [ combiningPackage , setCombiningPackage ] = React . useState < string | null > (
926920 null
927921 )
928- const [ combineSearchResults , setCombineSearchResults ] = React . useState <
929- NpmPackage [ ]
930- > ( [ ] )
931- const [ isCombining , setIsCombining ] = React . useState ( false )
932922 const navigate = Route . useNavigate ( )
933923 const [ colorPickerPackage , setColorPickerPackage ] = React . useState <
934924 string | null
@@ -1066,7 +1056,6 @@ function RouteComponent() {
10661056 }
10671057
10681058 setCombiningPackage ( null )
1069- setCombineSearchResults ( [ ] )
10701059 }
10711060
10721061 const handleRemoveFromGroup = ( mainPackage : string , subPackage : string ) => {
@@ -1145,27 +1134,6 @@ function RouteComponent() {
11451134
11461135 const handleCombinePackage = ( packageName : string ) => {
11471136 setCombiningPackage ( packageName )
1148- setCombineSearchResults ( [ ] )
1149- }
1150-
1151- const handleCombineSearch = async ( query : string ) => {
1152- if ( ! query || query . length < 2 ) {
1153- setCombineSearchResults ( [ ] )
1154- return
1155- }
1156-
1157- setIsCombining ( true )
1158- try {
1159- const response = await fetch (
1160- `https://api.npms.io/v2/search?q=${ encodeURIComponent ( query ) } &size=10`
1161- )
1162- const data = await response . json ( )
1163- setCombineSearchResults ( data . results . map ( ( r : any ) => r . package ) )
1164- } catch ( error ) {
1165- console . error ( 'Error searching packages:' , error )
1166- } finally {
1167- setIsCombining ( false )
1168- }
11691137 }
11701138
11711139 const handleColorClick = ( packageName : string , event : React . MouseEvent ) => {
@@ -1249,6 +1217,69 @@ function RouteComponent() {
12491217 } )
12501218 }
12511219
1220+ const handleAddPackage = ( packageName : string ) => {
1221+ navigate ( {
1222+ to : '.' ,
1223+ search : ( prev ) => ( {
1224+ ...prev ,
1225+ packageGroups : [
1226+ ...prev . packageGroups ,
1227+ {
1228+ packages : [ { name : packageName } ] ,
1229+ } ,
1230+ ] ,
1231+ } ) ,
1232+ resetScroll : false ,
1233+ } )
1234+ }
1235+
1236+ const handleAddToGroup = ( packageName : string ) => {
1237+ if ( ! combiningPackage ) return
1238+
1239+ // Find the package group that contains the combining package
1240+ const packageGroup = packageGroups . find ( ( pkg ) =>
1241+ pkg . packages . some ( ( p ) => p . name === combiningPackage )
1242+ )
1243+
1244+ if ( packageGroup ) {
1245+ // Update existing package group
1246+ const newPackages = packageGroups . map ( ( pkg ) =>
1247+ pkg === packageGroup
1248+ ? {
1249+ ...pkg ,
1250+ packages : [ ...pkg . packages , { name : packageName } ] ,
1251+ }
1252+ : pkg
1253+ )
1254+
1255+ navigate ( {
1256+ to : '.' ,
1257+ search : ( prev ) => ( {
1258+ ...prev ,
1259+ packageGroups : newPackages ,
1260+ } ) ,
1261+ resetScroll : false ,
1262+ } )
1263+ } else {
1264+ // Create new package group
1265+ navigate ( {
1266+ to : '.' ,
1267+ search : ( prev ) => ( {
1268+ ...prev ,
1269+ packageGroups : [
1270+ ...packageGroups ,
1271+ {
1272+ packages : [ { name : combiningPackage } , { name : packageName } ] ,
1273+ } ,
1274+ ] ,
1275+ } ) ,
1276+ resetScroll : false ,
1277+ } )
1278+ }
1279+
1280+ setCombiningPackage ( null )
1281+ }
1282+
12521283 return (
12531284 < div className = "min-h-dvh p-2 sm:p-4 space-y-2 sm:space-y-4" >
12541285 < div className = "bg-white dark:bg-black/50 rounded-lg p-2 sm:p-4 flex items-center gap-2 text-lg sm:text-xl shadow-xl" >
@@ -1271,7 +1302,7 @@ function RouteComponent() {
12711302 < div className = "flex gap-4" >
12721303 < div className = "flex-1 bg-white dark:bg-black/50 rounded-lg space-y-4 p-4 shadow-xl max-w-full" >
12731304 < div className = "flex gap-2 flex-wrap" >
1274- < PackageSearch />
1305+ < PackageSearch onSelect = { handleAddPackage } />
12751306 < DropdownMenu >
12761307 < Tooltip content = "Select time range" >
12771308 < DropdownMenuTrigger asChild >
@@ -1770,41 +1801,11 @@ function RouteComponent() {
17701801 < MdClose className = "w-4 h-4 sm:w-5 sm:h-5" />
17711802 </ button >
17721803 </ div >
1773- < div className = "relative" >
1774- < input
1775- type = "text"
1776- placeholder = "Search for packages..."
1777- className = "w-full bg-gray-500/10 rounded-md px-2 sm:px-3 py-1.5 sm:py-2 text-sm sm:text-base"
1778- onChange = { ( e ) => handleCombineSearch ( e . target . value ) }
1779- autoFocus
1780- />
1781- { isCombining && (
1782- < div className = "absolute right-2 top-0 bottom-0 flex items-center justify-center" >
1783- < FaSpinner className = "w-3 h-3 sm:w-4 sm:h-4 animate-spin" />
1784- </ div >
1785- ) }
1786- </ div >
1787- < div className = "mt-2 sm:mt-4 max-h-40 sm:max-h-60 overflow-auto" >
1788- { combineSearchResults . map ( ( pkg ) => (
1789- < button
1790- key = { pkg . name }
1791- onClick = { ( ) => handleCombineSelect ( pkg ) }
1792- className = "w-full text-left px-2 sm:px-3 py-1.5 sm:py-2 hover:bg-gray-500/20 rounded-md"
1793- >
1794- < div className = "font-medium text-sm sm:text-base" >
1795- { pkg . name }
1796- </ div >
1797- < div className = "text-xs sm:text-sm text-gray-500 dark:text-gray-400" >
1798- { pkg . description }
1799- </ div >
1800- </ button >
1801- ) ) }
1802- { combineSearchResults . length === 0 && (
1803- < div className = "px-2 sm:px-3 py-1.5 sm:py-2 text-sm text-gray-500" >
1804- No matching packages found
1805- </ div >
1806- ) }
1807- </ div >
1804+ < PackageSearch
1805+ onSelect = { handleAddToGroup }
1806+ placeholder = "Search for packages to add..."
1807+ autoFocus = { true }
1808+ />
18081809 </ div >
18091810 </ div >
18101811 ) }
0 commit comments