1- import React , { useState } from 'react' ;
1+ import React , { useState , useMemo , useCallback } from 'react' ;
22import {
33 CheckBox ,
44 Select ,
@@ -20,58 +20,78 @@ import {
2020import styles from './ComponentsSelection.module.css' ;
2121import { Infobox } from '../Ui/Infobox/Infobox.tsx' ;
2222import { useTranslation } from 'react-i18next' ;
23- import { ComponentSelectionItem } from '../../lib/api/types/crate/createManagedControlPlane.ts' ;
23+ import { ComponentsListItem } from '../../lib/api/types/crate/createManagedControlPlane.ts' ;
24+ import { getSelectedComponents } from './ComponentsSelectionContainer.tsx' ;
2425
2526export interface ComponentsSelectionProps {
26- components : ComponentSelectionItem [ ] ;
27- setSelectedComponents : React . Dispatch <
28- React . SetStateAction < ComponentSelectionItem [ ] >
29- > ;
27+ componentsList : ComponentsListItem [ ] ;
28+ setComponentsList : ( components : ComponentsListItem [ ] ) => void ;
3029}
3130
3231export const ComponentsSelection : React . FC < ComponentsSelectionProps > = ( {
33- components ,
34- setSelectedComponents ,
32+ componentsList ,
33+ setComponentsList ,
3534} ) => {
3635 const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
3736 const { t } = useTranslation ( ) ;
38- const handleSelectionChange = (
39- e : Ui5CustomEvent < CheckBoxDomRef , { checked : boolean } > ,
40- ) => {
41- const id = e . target ?. id ;
42- setSelectedComponents ( ( prev ) =>
43- prev . map ( ( component ) =>
44- component . name === id
45- ? { ...component , isSelected : ! component . isSelected }
46- : component ,
47- ) ,
48- ) ;
49- } ;
5037
51- const handleSearch = ( e : Ui5CustomEvent < InputDomRef , never > ) => {
52- setSearchTerm ( e . target . value . trim ( ) ) ;
53- } ;
38+ const selectedComponents = useMemo (
39+ ( ) => getSelectedComponents ( componentsList ) ,
40+ [ componentsList ] ,
41+ ) ;
5442
55- const handleVersionChange = (
56- e : Ui5CustomEvent < SelectDomRef , { selectedOption : HTMLElement } > ,
57- ) => {
58- const selectedOption = e . detail . selectedOption as HTMLElement ;
59- const name = selectedOption . dataset . name ;
60- const version = selectedOption . dataset . version ;
61- setSelectedComponents ( ( prev ) =>
62- prev . map ( ( component ) =>
63- component . name === name
64- ? { ...component , selectedVersion : version || '' }
65- : component ,
66- ) ,
43+ const searchResults = useMemo ( ( ) => {
44+ const lowerSearch = searchTerm . toLowerCase ( ) ;
45+ return componentsList . filter ( ( { name } ) =>
46+ name . toLowerCase ( ) . includes ( lowerSearch ) ,
6747 ) ;
68- } ;
48+ } , [ componentsList , searchTerm ] ) ;
6949
70- const filteredComponents = components . filter ( ( { name } ) =>
71- name . includes ( searchTerm ) ,
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 ] ,
7263 ) ;
73- const selectedComponents = components . filter (
74- ( component ) => component . isSelected ,
64+
65+ const handleSearch = useCallback ( ( e : Ui5CustomEvent < InputDomRef , never > ) => {
66+ setSearchTerm ( e . target . value . trim ( ) ) ;
67+ } , [ ] ) ;
68+
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+ ) ;
85+
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 ] ,
7595 ) ;
7696
7797 return (
@@ -83,54 +103,75 @@ export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({
83103 id = "search"
84104 showClearIcon
85105 icon = { < Icon name = "search" /> }
106+ value = { searchTerm }
107+ aria-label = { t ( 'common.search' ) }
86108 onInput = { handleSearch }
87109 />
88110
89111 < Grid >
90112 < div data-layout-span = "XL8 L8 M8 S8" >
91- { filteredComponents . map ( ( component ) => (
92- < FlexBox
93- key = { component . name }
94- className = { styles . row }
95- gap = { 10 }
96- justifyContent = "SpaceBetween"
97- >
98- < CheckBox
99- valueState = "None"
100- text = { component . name }
101- id = { component . name }
102- checked = { component . isSelected }
103- onChange = { handleSelectionChange }
104- />
105- < FlexBox
106- gap = { 10 }
107- justifyContent = "SpaceBetween"
108- alignItems = "Baseline"
109- >
110- { /*This button will be implemented later*/ }
111- { component . documentationUrl && (
112- < Button design = "Transparent" >
113- { t ( 'common.documentation' ) }
114- </ Button >
115- ) }
116- < Select
117- value = { component . selectedVersion }
118- onChange = { handleVersionChange }
113+ { searchResults . length > 0 ? (
114+ searchResults . map ( ( component ) => {
115+ const providerDisabled = isProviderDisabled ( component ) ;
116+ return (
117+ < FlexBox
118+ key = { component . name }
119+ className = { styles . row }
120+ gap = { 10 }
121+ justifyContent = "SpaceBetween"
122+ data-testid = { `component-row-${ component . name } ` }
119123 >
120- { component . versions . map ( ( version ) => (
121- < Option
122- key = { version }
123- data-version = { version }
124- data-name = { component . name }
125- selected = { component . selectedVersion === version }
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"
137+ >
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 }
145+ >
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 }
126154 >
127- { version }
128- </ Option >
129- ) ) }
130- </ Select >
131- </ FlexBox >
132- </ FlexBox >
133- ) ) }
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 >
167+ </ FlexBox >
168+ ) ;
169+ } )
170+ ) : (
171+ < Infobox fullWidth variant = "success" >
172+ < Text > { t ( 'componentsSelection.pleaseSelectComponents' ) } </ Text >
173+ </ Infobox >
174+ ) }
134175 </ div >
135176 < div data-layout-span = "XL4 L4 M4 S4" >
136177 { selectedComponents . length > 0 ? (
@@ -144,7 +185,7 @@ export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({
144185 ) ) }
145186 </ List >
146187 ) : (
147- < Infobox fullWidth variant = { ' success' } >
188+ < Infobox fullWidth variant = " success" >
148189 < Text > { t ( 'componentsSelection.pleaseSelectComponents' ) } </ Text >
149190 </ Infobox >
150191 ) }
0 commit comments