@@ -19,6 +19,7 @@ import {
1919} from "lucide-react" ;
2020import { useEffect , useMemo , useState } from "react" ;
2121import { ToolVariationBadge } from "../tool-variation-badge" ;
22+ import { SimpleTooltip } from "../ui/tooltip" ;
2223import { Type } from "../ui/type" ;
2324import { MethodBadge } from "./MethodBadge" ;
2425import { SubtoolsBadge } from "./SubtoolsBadge" ;
@@ -442,37 +443,73 @@ function ToolGroupHeader({
442443 isExpanded,
443444 onToggle,
444445 isFirstGroup = false ,
446+ allSelected,
447+ onSelectAll,
445448} : {
446449 group : ToolGroup ;
447450 isExpanded : boolean ;
448451 onToggle : ( ) => void ;
449452 isFirstGroup ?: boolean ;
453+ allSelected : boolean ;
454+ onSelectAll : ( ) => void ;
450455} ) {
451456 const Icon = getIcon ( group . icon ) ;
452457
453458 return (
454- < button
455- onClick = { onToggle }
456- aria-expanded = { isExpanded }
457- aria-label = { `${ isExpanded ? "Collapse" : "Expand" } ${ group . title } group` }
459+ < div
458460 className = { cn (
459- "bg-surface-secondary-default flex items-center justify-between pl-4 pr-3 py-4 w-full hover:bg-active transition-colors " ,
461+ "group/header bg-surface-secondary-default flex items-center justify-between pl-4 pr-3 py-4 w-full" ,
460462 isExpanded && "border-b border-neutral-softest" ,
461463 ! isFirstGroup && "border-t border-neutral-softest" ,
462464 ) }
463465 >
464- < div className = "flex gap-4 items-center" >
465- < Icon className = "size-4 shrink-0" strokeWidth = { 1.5 } />
466+ < button
467+ onClick = { onToggle }
468+ aria-expanded = { isExpanded }
469+ aria-label = { `${ isExpanded ? "Collapse" : "Expand" } ${ group . title } group` }
470+ className = "flex gap-4 items-center hover:opacity-70 transition-opacity"
471+ >
472+ < div className = "relative size-4 shrink-0" >
473+ < Icon
474+ className = { cn (
475+ "size-4 absolute inset-0 transition-opacity" ,
476+ "group-hover/header:opacity-0" ,
477+ ) }
478+ strokeWidth = { 1.5 }
479+ />
480+ < SimpleTooltip
481+ tooltip = { `${ allSelected ? "Deselect" : "Select" } ${ group . tools . length } tools` }
482+ >
483+ < Checkbox
484+ checked = { allSelected }
485+ onCheckedChange = { onSelectAll }
486+ onClick = { ( e ) => {
487+ e . stopPropagation ( ) ;
488+ } }
489+ className = { cn (
490+ "absolute inset-0 transition-opacity opacity-0" ,
491+ "group-hover/header:opacity-100" ,
492+ ) }
493+ />
494+ </ SimpleTooltip >
495+ </ div >
466496 < p className = "text-sm leading-6 text-foreground" > { group . title } </ p >
467- </ div >
468- < ChevronDown
469- className = { cn (
470- "size-4 transition-transform" ,
471- isExpanded ? "rotate-180" : "rotate-0" ,
472- ) }
473- strokeWidth = { 1.5 }
474- />
475- </ button >
497+ </ button >
498+ < button
499+ onClick = { onToggle }
500+ aria-expanded = { isExpanded }
501+ aria-label = { `${ isExpanded ? "Collapse" : "Expand" } ${ group . title } group` }
502+ className = "hover:opacity-70 transition-opacity"
503+ >
504+ < ChevronDown
505+ className = { cn (
506+ "size-4 transition-transform" ,
507+ isExpanded ? "rotate-180" : "rotate-0" ,
508+ ) }
509+ strokeWidth = { 1.5 }
510+ />
511+ </ button >
512+ </ div >
476513 ) ;
477514}
478515
@@ -741,6 +778,39 @@ export function ToolList({
741778 setSelectedForRemoval ( new Set ( ) ) ;
742779 } ;
743780
781+ const handleSelectAllInGroup = ( group : ToolGroup ) => {
782+ const groupToolIds = group . tools . map ( getToolIdentifier ) ;
783+ const currentSelection =
784+ selectionMode === "add" ? selectedSet : selectedForRemoval ;
785+ const allSelected = groupToolIds . every ( ( id ) => currentSelection . has ( id ) ) ;
786+
787+ if ( selectionMode === "add" && onSelectionChange ) {
788+ // For selection mode, update parent state
789+ const next = new Set ( selectedUrns ) ;
790+ if ( allSelected ) {
791+ // Deselect all in group
792+ groupToolIds . forEach ( ( id ) => next . delete ( id ) ) ;
793+ } else {
794+ // Select all in group
795+ groupToolIds . forEach ( ( id ) => next . add ( id ) ) ;
796+ }
797+ onSelectionChange ( Array . from ( next ) ) ;
798+ } else {
799+ // For normal mode, update local state
800+ setSelectedForRemoval ( ( prev ) => {
801+ const next = new Set ( prev ) ;
802+ if ( allSelected ) {
803+ // Deselect all in group
804+ groupToolIds . forEach ( ( id ) => next . delete ( id ) ) ;
805+ } else {
806+ // Select all in group
807+ groupToolIds . forEach ( ( id ) => next . add ( id ) ) ;
808+ }
809+ return next ;
810+ } ) ;
811+ }
812+ } ;
813+
744814 return (
745815 < div className = "relative w-full" >
746816 < div
@@ -749,52 +819,66 @@ export function ToolList({
749819 className ,
750820 ) }
751821 >
752- { groups . map ( ( group , index ) => (
753- < div key = { `${ group . type } -${ group . title } -${ index } ` } className = "w-full" >
754- < ToolGroupHeader
755- group = { group }
756- isExpanded = { expandedGroups . has ( index ) }
757- onToggle = { ( ) => toggleGroup ( index ) }
758- isFirstGroup = { index === 0 }
759- />
760- { expandedGroups . has ( index ) && (
761- < div className = "w-full" >
762- { group . tools . map ( ( tool ) => {
763- const toolId = getToolIdentifier ( tool ) ;
764- const toolIndex = toolIndexMap . get ( toolId ) ?? - 1 ;
765-
766- return (
767- < ToolRow
768- key = { tool . canonicalName }
769- groupName = { group . title }
770- availableToolUrns = { toolset ?. tools
771- ?. map ( ( t ) => t . toolUrn )
772- . concat ( selectionMode === "add" ? selectedUrns : [ ] )
773- . filter ( ( urn ) => ! selectedForRemoval . has ( urn ) ) }
774- tool = { tool }
775- onUpdate = { ( updates ) => onToolUpdate ?.( tool , updates ) }
776- isSelected = {
777- selectionMode === "add"
778- ? selectedSet . has ( toolId )
779- : selectedForRemoval . has ( toolId )
780- }
781- isFocused = { toolIndex === focusedToolIndex }
782- onCheckboxChange = { ( checked ) =>
783- handleCheckboxChange ( toolId , checked )
784- }
785- onTestInPlayground = { onTestInPlayground }
786- onRemove = {
787- selectionMode !== "add" && onToolsRemove
788- ? ( ) => onToolsRemove ( [ toolId ] )
789- : undefined
790- }
791- />
792- ) ;
793- } ) }
794- </ div >
795- ) }
796- </ div >
797- ) ) }
822+ { groups . map ( ( group , index ) => {
823+ const groupToolIds = group . tools . map ( getToolIdentifier ) ;
824+ const currentSelection =
825+ selectionMode === "add" ? selectedSet : selectedForRemoval ;
826+ const allSelected = groupToolIds . every ( ( id ) =>
827+ currentSelection . has ( id ) ,
828+ ) ;
829+
830+ return (
831+ < div
832+ key = { `${ group . type } -${ group . title } -${ index } ` }
833+ className = "w-full"
834+ >
835+ < ToolGroupHeader
836+ group = { group }
837+ isExpanded = { expandedGroups . has ( index ) }
838+ onToggle = { ( ) => toggleGroup ( index ) }
839+ isFirstGroup = { index === 0 }
840+ allSelected = { allSelected }
841+ onSelectAll = { ( ) => handleSelectAllInGroup ( group ) }
842+ />
843+ { expandedGroups . has ( index ) && (
844+ < div className = "w-full" >
845+ { group . tools . map ( ( tool ) => {
846+ const toolId = getToolIdentifier ( tool ) ;
847+ const toolIndex = toolIndexMap . get ( toolId ) ?? - 1 ;
848+
849+ return (
850+ < ToolRow
851+ key = { tool . canonicalName }
852+ groupName = { group . title }
853+ availableToolUrns = { toolset ?. tools
854+ ?. map ( ( t ) => t . toolUrn )
855+ . concat ( selectionMode === "add" ? selectedUrns : [ ] )
856+ . filter ( ( urn ) => ! selectedForRemoval . has ( urn ) ) }
857+ tool = { tool }
858+ onUpdate = { ( updates ) => onToolUpdate ?.( tool , updates ) }
859+ isSelected = {
860+ selectionMode === "add"
861+ ? selectedSet . has ( toolId )
862+ : selectedForRemoval . has ( toolId )
863+ }
864+ isFocused = { toolIndex === focusedToolIndex }
865+ onCheckboxChange = { ( checked ) =>
866+ handleCheckboxChange ( toolId , checked )
867+ }
868+ onTestInPlayground = { onTestInPlayground }
869+ onRemove = {
870+ selectionMode !== "add" && onToolsRemove
871+ ? ( ) => onToolsRemove ( [ toolId ] )
872+ : undefined
873+ }
874+ />
875+ ) ;
876+ } ) }
877+ </ div >
878+ ) }
879+ </ div >
880+ ) ;
881+ } ) }
798882 </ div >
799883
800884 { hasChanges && ! selectionMode && (
0 commit comments