1- import React , { useState } from 'react' ;
1+ import React , { useState , useMemo , useCallback } from 'react' ;
22import {
33 CheckBox ,
44 Select ,
@@ -34,42 +34,65 @@ export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({
3434} ) => {
3535 const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
3636 const { t } = useTranslation ( ) ;
37- const handleSelectionChange = (
38- e : Ui5CustomEvent < CheckBoxDomRef , { checked : boolean } > ,
39- ) => {
40- const id = e . target ?. id ;
41- setComponentsList (
42- componentsList . map ( ( component ) =>
43- component . name === id
44- ? { ... component , isSelected : ! component . isSelected }
45- : component ,
46- ) ,
37+
38+ const selectedComponents = useMemo (
39+ ( ) => getSelectedComponents ( componentsList ) ,
40+ [ componentsList ] ,
41+ ) ;
42+
43+ const searchResults = useMemo ( ( ) => {
44+ const lowerSearch = searchTerm . toLowerCase ( ) ;
45+ return componentsList . filter ( ( { name } ) =>
46+ name . toLowerCase ( ) . includes ( lowerSearch ) ,
4747 ) ;
48- } ;
48+ } , [ componentsList , searchTerm ] ) ;
4949
50- const handleSearch = ( e : Ui5CustomEvent < InputDomRef , never > ) => {
50+ const handleSelectionChange = useCallback (
51+ ( e : Ui5CustomEvent < CheckBoxDomRef , { checked : boolean } > ) => {
52+ const id = e . target ?. id ;
53+ if ( ! id ) return ;
54+ setComponentsList (
55+ componentsList . map ( ( component ) =>
56+ component . name === id
57+ ? { ...component , isSelected : ! component . isSelected }
58+ : component ,
59+ ) ,
60+ ) ;
61+ } ,
62+ [ componentsList , setComponentsList ] ,
63+ ) ;
64+
65+ const handleSearch = useCallback ( ( e : Ui5CustomEvent < InputDomRef , never > ) => {
5166 setSearchTerm ( e . target . value . trim ( ) ) ;
52- } ;
67+ } , [ ] ) ;
5368
54- const handleVersionChange = (
55- e : Ui5CustomEvent < SelectDomRef , { selectedOption : HTMLElement } > ,
56- ) => {
57- const selectedOption = e . detail . selectedOption as HTMLElement ;
58- const name = selectedOption . dataset . name ;
59- const version = selectedOption . dataset . version ;
60- setComponentsList (
61- componentsList . map ( ( component ) =>
62- component . name === name
63- ? { ...component , selectedVersion : version || '' }
64- : component ,
65- ) ,
66- ) ;
67- } ;
69+ const handleVersionChange = useCallback (
70+ ( e : Ui5CustomEvent < SelectDomRef , { selectedOption : HTMLElement } > ) => {
71+ const selectedOption = e . detail . selectedOption as HTMLElement ;
72+ const name = selectedOption . dataset . name ;
73+ const version = selectedOption . dataset . version ;
74+ if ( ! name ) return ;
75+ setComponentsList (
76+ componentsList . map ( ( component ) =>
77+ component . name === name
78+ ? { ...component , selectedVersion : version || '' }
79+ : component ,
80+ ) ,
81+ ) ;
82+ } ,
83+ [ componentsList , setComponentsList ] ,
84+ ) ;
6885
69- const searchResults = componentsList . filter ( ( { name } ) =>
70- name . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ,
86+ const isProviderDisabled = useCallback (
87+ ( component : ComponentsListItem ) => {
88+ if ( ! component . name ?. includes ( 'provider' ) ) return false ;
89+ const crossplane = componentsList . find (
90+ ( { name } ) => name === 'crossplane' ,
91+ ) ;
92+ return crossplane ?. isSelected === false ;
93+ } ,
94+ [ componentsList ] ,
7195 ) ;
72- const selectedComponents = getSelectedComponents ( componentsList ) ;
7396
7497 return (
7598 < div >
@@ -80,62 +103,75 @@ export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({
80103 id = "search"
81104 showClearIcon
82105 icon = { < Icon name = "search" /> }
106+ value = { searchTerm }
107+ aria-label = { t ( 'common.search' ) }
83108 onInput = { handleSearch }
84109 />
85110
86111 < Grid >
87112 < div data-layout-span = "XL8 L8 M8 S8" >
88- { searchResults . map ( ( component ) => {
89- const isProviderDisabled =
90- component . name ?. includes ( 'provider' ) &&
91- componentsList ?. find ( ( { name } ) => name === 'crossplane' )
92- ?. isSelected === false ;
93- return (
94- < FlexBox
95- key = { component . name }
96- className = { styles . row }
97- gap = { 10 }
98- justifyContent = "SpaceBetween"
99- >
100- < CheckBox
101- valueState = "None"
102- text = { component . name }
103- id = { component . name }
104- checked = { component . isSelected }
105- disabled = { isProviderDisabled }
106- onChange = { handleSelectionChange }
107- />
113+ { searchResults . length > 0 ? (
114+ searchResults . map ( ( component ) => {
115+ const providerDisabled = isProviderDisabled ( component ) ;
116+ return (
108117 < FlexBox
118+ key = { component . name }
119+ className = { styles . row }
109120 gap = { 10 }
110121 justifyContent = "SpaceBetween"
111- alignItems = "Baseline"
122+ data-testid = { `component-row- ${ component . name } ` }
112123 >
113- { /*This button will be implemented later*/ }
114- { component . documentationUrl && (
115- < Button design = "Transparent" >
116- { t ( 'common.documentation' ) }
117- </ Button >
118- ) }
119- < Select
120- value = { component . selectedVersion }
121- disabled = { ! component . isSelected || isProviderDisabled }
122- onChange = { handleVersionChange }
124+ < CheckBox
125+ valueState = "None"
126+ text = { component . name }
127+ id = { component . name }
128+ checked = { component . isSelected }
129+ disabled = { providerDisabled }
130+ aria-label = { component . name }
131+ onChange = { handleSelectionChange }
132+ />
133+ < FlexBox
134+ gap = { 10 }
135+ justifyContent = "SpaceBetween"
136+ alignItems = "Baseline"
123137 >
124- { component . versions . map ( ( version ) => (
125- < Option
126- key = { version }
127- data-version = { version }
128- data-name = { component . name }
129- selected = { component . selectedVersion === version }
138+ { /* TODO: Add documentation link */ }
139+ { component . documentationUrl && (
140+ < Button
141+ design = "Transparent"
142+ rel = "noopener noreferrer"
143+ aria-label = { t ( 'common.documentation' ) }
144+ tabIndex = { 0 }
130145 >
131- { version }
132- </ Option >
133- ) ) }
134- </ Select >
146+ { t ( 'common.documentation' ) }
147+ </ Button >
148+ ) }
149+ < Select
150+ value = { component . selectedVersion }
151+ disabled = { ! component . isSelected || providerDisabled }
152+ aria-label = { `${ component . name } version` }
153+ onChange = { handleVersionChange }
154+ >
155+ { component . versions . map ( ( version ) => (
156+ < Option
157+ key = { version }
158+ data-version = { version }
159+ data-name = { component . name }
160+ selected = { component . selectedVersion === version }
161+ >
162+ { version }
163+ </ Option >
164+ ) ) }
165+ </ Select >
166+ </ FlexBox >
135167 </ FlexBox >
136- </ FlexBox >
137- ) ;
138- } ) }
168+ ) ;
169+ } )
170+ ) : (
171+ < Infobox fullWidth variant = "success" >
172+ < Text > { t ( 'componentsSelection.pleaseSelectComponents' ) } </ Text >
173+ </ Infobox >
174+ ) }
139175 </ div >
140176 < div data-layout-span = "XL4 L4 M4 S4" >
141177 { selectedComponents . length > 0 ? (
@@ -149,7 +185,7 @@ export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({
149185 ) ) }
150186 </ List >
151187 ) : (
152- < Infobox fullWidth variant = { ' success' } >
188+ < Infobox fullWidth variant = " success" >
153189 < Text > { t ( 'componentsSelection.pleaseSelectComponents' ) } </ Text >
154190 </ Infobox >
155191 ) }
0 commit comments