11import { useCallback , useEffect , useState } from 'react' ;
2- import { Search , Plus } from 'lucide-react' ;
2+ import { Search } from 'lucide-react' ;
33import { Input } from '@/components/ui/input' ;
4- import { Button } from '@/components/ui/button' ;
54import { BrowseTab } from '@/components/features/skills/BrowseTab' ;
5+ import { SkillGroupsBar } from '@/components/features/skills/SkillGroupsBar' ;
66import { useWorkspaceStore , useLayoutStore , usePluginStore } from '@/stores' ;
77import { useTrafficLightConfig } from '@/hooks' ;
88import { type SkillGroup , type SkillGroupsConfig , listCentralSkills , readSkillGroups , writeSkillGroups } from '@/services' ;
9- import { cn } from '@/lib/utils' ;
10- import { Dialog , DialogContent , DialogHeader , DialogTitle , DialogFooter } from '@/components/ui/dialog' ;
119import { toast } from '@/components/ui/use-toast' ;
1210import { v4 as uuidv4 } from 'uuid' ;
1311
@@ -22,8 +20,6 @@ export default function SkillsView() {
2220 const [ groupsConfig , setGroupsConfig ] = useState < SkillGroupsConfig > ( { groups : [ ] } ) ;
2321 const [ selectedGroupId , setSelectedGroupId ] = useState < string | null > ( null ) ;
2422
25- const [ addingGroup , setAddingGroup ] = useState ( false ) ;
26- const [ newGroupName , setNewGroupName ] = useState ( '' ) ;
2723
2824 useEffect ( ( ) => {
2925 listCentralSkills ( scope , cwd ?? undefined )
@@ -57,107 +53,35 @@ export default function SkillsView() {
5753 setInstalledRefreshKey ( ( k ) => k + 1 ) ;
5854 } , [ ] ) ;
5955
60- const handleAddGroup = async ( ) => {
61- const name = newGroupName . trim ( ) ;
62- if ( ! name ) { setAddingGroup ( false ) ; return ; }
56+ const handleAddGroup = async ( name : string ) => {
6357 const newGroupId = uuidv4 ( ) ;
6458 const newGroup : SkillGroup = { id : newGroupId , name, skillNames : [ ] } ;
6559 const updated = { groups : [ ...groupsConfig . groups , newGroup ] } ;
6660 await saveGroups ( updated ) ;
6761 setSelectedGroupId ( newGroupId ) ;
68- setNewGroupName ( '' ) ;
69- setAddingGroup ( false ) ;
70- } ;
71-
72- const handleGroupChipClick = ( groupId : string ) => {
73- setSelectedGroupId ( ( prev ) => ( prev === groupId ? null : groupId ) ) ;
7462 } ;
7563
7664 return (
7765 < div className = "flex flex-col h-full" >
7866
7967 < div >
80- < span className = "flex items-center justify-between gap-2 px-4 h-12" >
81- < div className = "relative" >
82- < Search className = "absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground pointer-events-none" />
83- < Input
84- placeholder = "Search skills…"
85- value = { searchQuery }
86- onChange = { ( e ) => setSearchQuery ( e . target . value ) }
87- className = "h-7 w-36 pl-8 text-xs bg-muted/30 border-none ring-offset-background focus-visible:ring-1"
88- />
89- </ div >
90- </ span >
91-
92- { /* Groups bar */ }
93- < div className = "flex items-center gap-1.5 px-4 py-1.5 min-h-[32px]" >
94- < Button
95- size = "icon-sm"
96- variant = "ghost"
97- onClick = { ( ) => setAddingGroup ( true ) }
98- title = "New group"
99- >
100- < Plus />
101- </ Button >
102- < div className = "flex flex-1 items-center gap-1 overflow-x-auto scrollbar-none" >
103- < button
104- type = "button"
105- onClick = { ( ) => setSelectedGroupId ( null ) }
106- className = { cn (
107- 'shrink-0 rounded-full border px-2.5 py-0.5 text-[11px] font-medium transition-colors whitespace-nowrap' ,
108- selectedGroupId === null
109- ? 'border-primary/50 bg-primary/10 text-primary'
110- : 'border-muted text-muted-foreground hover:border-muted-foreground/50 hover:text-foreground'
111- ) }
112- >
113- All groups
114- </ button >
115- { groupsConfig . groups . map ( ( g ) => (
116- < button
117- key = { g . id }
118- type = "button"
119- onClick = { ( ) => handleGroupChipClick ( g . id ) }
120- className = { cn (
121- 'shrink-0 rounded-full border px-2.5 py-0.5 text-[11px] font-medium transition-colors whitespace-nowrap' ,
122- selectedGroupId === g . id
123- ? 'border-primary/50 bg-primary/10 text-primary'
124- : 'border-muted text-muted-foreground hover:border-muted-foreground/50 hover:text-foreground'
125- ) }
126- >
127- { g . name }
128- { g . skillNames . length > 0 && (
129- < span className = "ml-1 opacity-50" > { g . skillNames . length } </ span >
130- ) }
131- </ button >
132- ) ) }
133- </ div >
68+ < div className = "relative mx-20 py-4" >
69+ < Search className = "absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground pointer-events-none" />
70+ < Input
71+ placeholder = "Search skills…"
72+ value = { searchQuery }
73+ onChange = { ( e ) => setSearchQuery ( e . target . value ) }
74+ className = "w-full pl-8 text-xs bg-muted/30 border-none ring-offset-background focus-visible:ring-1"
75+ />
13476 </ div >
13577
136- < Dialog open = { addingGroup } onOpenChange = { setAddingGroup } >
137- < DialogContent >
138- < DialogHeader >
139- < DialogTitle > New Skill Group</ DialogTitle >
140- </ DialogHeader >
141- < Input
142- id = "name"
143- value = { newGroupName }
144- onChange = { ( e ) => setNewGroupName ( e . target . value ) }
145- placeholder = "Group name..."
146- autoFocus
147- onKeyDown = { ( e ) => {
148- if ( e . key === 'Enter' ) void handleAddGroup ( ) ;
149- } }
150- />
151- < DialogFooter >
152- < Button variant = "ghost" onClick = { ( ) => { setAddingGroup ( false ) ; setNewGroupName ( '' ) ; } } >
153- Cancel
154- </ Button >
155- < Button onClick = { ( ) => void handleAddGroup ( ) } >
156- Save changes
157- </ Button >
158- </ DialogFooter >
159- </ DialogContent >
160- </ Dialog >
78+ { /* Groups bar */ }
79+ < SkillGroupsBar
80+ groupsConfig = { groupsConfig }
81+ selectedGroupId = { selectedGroupId }
82+ onSelectGroup = { setSelectedGroupId }
83+ onAddGroup = { handleAddGroup }
84+ />
16185 </ div >
16286
16387 { /* Content */ }
0 commit comments