@@ -6,26 +6,22 @@ import {
66 Modal ,
77 Paragraph ,
88 Pill ,
9- Select ,
9+ Multiselect ,
1010 Spinner ,
1111} from '@contentful/f36-components' ;
1212import { PageAppSDK } from '@contentful/app-sdk' ;
1313import { ContentTypeProps } from 'contentful-management' ;
14-
15- export interface SelectedContentType {
16- id : string ;
17- name : string ;
18- }
14+ import { css } from '@emotion/css' ;
1915
2016interface ContentTypePickerModalProps {
2117 sdk : PageAppSDK ;
2218 isOpen : boolean ;
2319 onClose : ( ) => void ;
24- onSelect : ( contentTypes : SelectedContentType [ ] ) => void ;
20+ onSelect : ( contentTypes : ContentTypeProps [ ] ) => void ;
2521 isSubmitting : boolean ;
26- selectedContentTypes : SelectedContentType [ ] ;
22+ selectedContentTypes : ContentTypeProps [ ] ;
2723 setSelectedContentTypes : (
28- contentTypes : SelectedContentType [ ] | ( ( prev : SelectedContentType [ ] ) => SelectedContentType [ ] )
24+ contentTypes : ContentTypeProps [ ] | ( ( prev : ContentTypeProps [ ] ) => ContentTypeProps [ ] )
2925 ) => void ;
3026}
3127
@@ -39,6 +35,7 @@ export const ContentTypePickerModal = ({
3935 setSelectedContentTypes,
4036} : ContentTypePickerModalProps ) => {
4137 const [ contentTypes , setContentTypes ] = useState < ContentTypeProps [ ] > ( [ ] ) ;
38+ const [ filteredContentTypes , setFilteredContentTypes ] = useState < ContentTypeProps [ ] > ( [ ] ) ;
4239 const [ isLoading , setIsLoading ] = useState < boolean > ( false ) ;
4340 const [ hasAttemptedSubmit , setHasAttemptedSubmit ] = useState < boolean > ( false ) ;
4441 const [ hasFetchError , setHasFetchError ] = useState < boolean > ( false ) ;
@@ -68,6 +65,7 @@ export const ContentTypePickerModal = ({
6865 environmentId : environment . sys . id ,
6966 } ) ;
7067 setContentTypes ( contentTypesResponse . items || [ ] ) ;
68+ setFilteredContentTypes ( contentTypesResponse . items || [ ] ) ;
7169 } catch ( error ) {
7270 console . error ( 'Failed to fetch content types:' , error ) ;
7371 setHasFetchError ( true ) ;
@@ -87,23 +85,35 @@ export const ContentTypePickerModal = ({
8785 }
8886 } , [ isOpen ] ) ;
8987
90- const handleAddContentType = ( contentTypeId : string ) => {
91- if ( ! contentTypeId || isSubmitting ) return ;
88+ const onSearchValueChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
89+ const searchTerm = e . target . value . toLowerCase ( ) . trim ( ) ;
90+ if ( ! searchTerm ) {
91+ setFilteredContentTypes ( contentTypes ) ;
92+ return ;
93+ }
94+ const filtered = contentTypes . filter ( ( ct ) => ct . name . toLowerCase ( ) . includes ( searchTerm ) ) ;
95+ setFilteredContentTypes ( filtered ) ;
96+ } ;
9297
93- const contentType = contentTypes . find ( ( ct ) => ct . sys . id === contentTypeId ) ;
94- if ( contentType && ! selectedContentTypes . some ( ( ct ) => ct . id === contentTypeId ) ) {
98+ const getPlaceholderText = ( ) => {
99+ if ( isLoading ) return 'Loading content types...' ;
100+ if ( contentTypes . length === 0 ) return 'No content types in space' ;
101+ if ( selectedContentTypes . length === 0 ) return 'Select one or more' ;
102+ return `${ selectedContentTypes . length } selected` ;
103+ } ;
104+
105+ const handleSelectContentType = ( e : React . ChangeEvent < HTMLInputElement > ) => {
106+ const { checked, value } = e . target ;
107+ if ( checked ) {
95108 setSelectedContentTypes ( [
96109 ...selectedContentTypes ,
97- { id : contentType . sys . id , name : contentType . name } ,
110+ contentTypes . find ( ( ct ) => ct . sys . id === value ) as ContentTypeProps ,
98111 ] ) ;
112+ } else {
113+ setSelectedContentTypes ( selectedContentTypes . filter ( ( selected ) => selected . sys . id !== value ) ) ;
99114 }
100115 } ;
101116
102- const handleRemoveContentType = ( contentTypeId : string ) => {
103- if ( isSubmitting ) return ;
104- setSelectedContentTypes ( selectedContentTypes . filter ( ( ct ) => ct . id !== contentTypeId ) ) ;
105- } ;
106-
107117 const handleClose = ( ) => {
108118 if ( isSubmitting ) return ; // Prevent closing during submission
109119 onClose ( ) ;
@@ -118,14 +128,6 @@ export const ContentTypePickerModal = ({
118128 onSelect ( selectedContentTypes ) ;
119129 } ;
120130
121- const availableContentTypes = useMemo (
122- ( ) =>
123- contentTypes . filter (
124- ( ct ) => ! selectedContentTypes . some ( ( selected ) => selected . id === ct . sys . id )
125- ) ,
126- [ contentTypes , selectedContentTypes ]
127- ) ;
128-
129131 return (
130132 < Modal title = "Select content type(s)" isShown = { isOpen } onClose = { handleClose } size = "medium" >
131133 { ( ) => (
@@ -137,23 +139,28 @@ export const ContentTypePickerModal = ({
137139 </ Paragraph >
138140 < FormControl isRequired isInvalid = { isInvalidSelectionError || showFetchError } >
139141 < FormControl . Label > Content type</ FormControl . Label >
140- < Select
141- id = "content-type-select"
142- name = "content-type-select"
143- value = ""
144- onChange = { ( e ) => {
145- handleAddContentType ( e . target . value ) ;
142+ < Multiselect
143+ searchProps = { {
144+ searchPlaceholder : 'Search content types' ,
145+ onSearchValueChange,
146146 } }
147- isDisabled = { isLoading || availableContentTypes . length === 0 || isSubmitting } >
148- < Select . Option value = "" isDisabled >
149- { isLoading ? 'Loading content types...' : 'Select one or more' }
150- </ Select . Option >
151- { availableContentTypes . map ( ( ct ) => (
152- < Select . Option key = { ct . sys . id } value = { ct . sys . id } >
147+ currentSelection = { selectedContentTypes . map ( ( ct ) => ct . sys . id ) }
148+ placeholder = { getPlaceholderText ( ) } >
149+ { filteredContentTypes . map ( ( ct ) => (
150+ < Multiselect . Option
151+ className = { css ( { padding : `0.25rem` } ) }
152+ key = { ct . sys . id }
153+ value = { ct . sys . id }
154+ itemId = { ct . sys . id }
155+ isChecked = { selectedContentTypes . some (
156+ ( selected ) => selected . sys . id === ct . sys . id
157+ ) }
158+ isDisabled = { isLoading || isSubmitting }
159+ onSelectItem = { handleSelectContentType } >
153160 { ct . name }
154- </ Select . Option >
161+ </ Multiselect . Option >
155162 ) ) }
156- </ Select >
163+ </ Multiselect >
157164 { showFetchError && (
158165 < FormControl . ValidationMessage >
159166 Unable to load content types.
@@ -170,9 +177,18 @@ export const ContentTypePickerModal = ({
170177 < Flex flexWrap = "wrap" gap = "spacingXs" marginTop = "spacingS" >
171178 { selectedContentTypes . map ( ( ct ) => (
172179 < Pill
173- key = { ct . id }
180+ key = { ct . sys . id }
174181 label = { ct . name }
175- onClose = { isSubmitting ? undefined : ( ) => handleRemoveContentType ( ct . id ) }
182+ onClose = {
183+ isSubmitting
184+ ? undefined
185+ : ( ) =>
186+ setSelectedContentTypes (
187+ selectedContentTypes . filter (
188+ ( selected ) => selected . sys . id !== ct . sys . id
189+ )
190+ )
191+ }
176192 />
177193 ) ) }
178194 </ Flex >
0 commit comments