11import {
22 Select ,
33 Option ,
4- IconButton ,
5- Icon ,
64 Body ,
75 spacing ,
86 css ,
97 ComboboxWithCustomOption ,
8+ ListEditor ,
109} from '@mongodb-js/compass-components' ;
11- import React , { useEffect , useMemo , useState } from 'react' ;
10+ import React , { useMemo , useState } from 'react' ;
1211import { SORT_DIRECTION_OPTIONS , mapSortDataToStageValue } from '../utils' ;
1312
1413import type { WizardComponentProps } from '..' ;
@@ -23,6 +22,8 @@ const containerStyles = css({
2322 display : 'flex' ,
2423 flexDirection : 'column' ,
2524 gap : spacing [ 2 ] ,
25+ width : 'max-content' ,
26+ maxWidth : '100%' ,
2627} ) ;
2728
2829const formGroupStyles = css ( {
@@ -47,6 +48,72 @@ const mapSortFormDataToStageValue = (
4748 return mapSortDataToStageValue ( formData ) ;
4849} ;
4950
51+ const SortFormGroup = ( {
52+ index,
53+ comboboxClassName,
54+ fields,
55+ sortField,
56+ sortDirection,
57+ onChange,
58+ } : {
59+ index : number ;
60+ comboboxClassName : string ;
61+ fields : string [ ] ;
62+ sortField : string ;
63+ sortDirection : SortDirection ;
64+ onChange : < T extends keyof SortFieldState > (
65+ property : T ,
66+ value : SortFieldState [ T ]
67+ ) => void ;
68+ } ) => {
69+ return (
70+ < div className = { formGroupStyles } key = { `sort-form-${ index } ` } >
71+ < Body className = { labelStyles } >
72+ { index === 0 ? 'Sort documents by' : 'and' }
73+ </ Body >
74+ < div data-testid = { `sort-form-${ index } -field` } >
75+ < ComboboxWithCustomOption
76+ className = { comboboxClassName }
77+ aria-label = "Select a field"
78+ size = "default"
79+ clearable = { false }
80+ value = { sortField }
81+ onChange = { ( value : string | null ) => {
82+ if ( value ) {
83+ onChange ( 'field' , value ) ;
84+ }
85+ } }
86+ options = { fields }
87+ optionLabel = "Field:"
88+ // Used for testing to access the popover for a stage
89+ popoverClassName = { `sort-form-${ index } -field-combobox` }
90+ />
91+ </ div >
92+ < Body > in</ Body >
93+ < div data-testid = { `sort-form-${ index } -direction` } >
94+ { /* @ts -expect-error leafygreen unresonably expects a labelledby here */ }
95+ < Select
96+ className = { sortDirectionStyles }
97+ allowDeselect = { false }
98+ aria-label = "Select direction"
99+ value = { sortDirection }
100+ onChange = { ( value : string ) =>
101+ onChange ( 'direction' , value as SortDirection )
102+ }
103+ >
104+ { SORT_DIRECTION_OPTIONS . map ( ( sort , index ) => {
105+ return (
106+ < Option key = { index } value = { sort . value } >
107+ { sort . label }
108+ </ Option >
109+ ) ;
110+ } ) }
111+ </ Select >
112+ </ div >
113+ </ div >
114+ ) ;
115+ } ;
116+
50117export const SortForm = ( { fields, onChange } : WizardComponentProps ) => {
51118 const fieldNames = useMemo ( ( ) => fields . map ( ( { name } ) => name ) , [ fields ] ) ;
52119 const [ formData , setFormData ] = useState < SortFieldState [ ] > ( [
@@ -56,110 +123,71 @@ export const SortForm = ({ fields, onChange }: WizardComponentProps) => {
56123 } ,
57124 ] ) ;
58125
59- useEffect ( ( ) => {
60- const stageValue = mapSortFormDataToStageValue ( formData ) ;
126+ const onSetFormData = ( data : SortFieldState [ ] ) => {
127+ const stageValue = mapSortFormDataToStageValue ( data ) ;
61128 onChange (
62129 JSON . stringify ( stageValue ) ,
63130 Object . keys ( stageValue ) . length === 0
64131 ? new Error ( 'No field selected' )
65132 : null
66133 ) ;
67- } , [ formData , onChange ] ) ;
68134
69- const onSelectField = ( index : number , value : string | null ) => {
70- if ( ! value ) return ;
71- const newFormData = [ ...formData ] ;
72- newFormData [ index ] . field = value ;
73- setFormData ( newFormData ) ;
135+ setFormData ( data ) ;
74136 } ;
75137
76- const onSelectDirection = ( index : number , value : SortDirection ) => {
138+ const onChangeProperty = < T extends keyof SortFieldState > (
139+ index : number ,
140+ property : T ,
141+ value : SortFieldState [ T ]
142+ ) => {
77143 const newFormData = [ ...formData ] ;
78- newFormData [ index ] . direction = value ;
79- setFormData ( newFormData ) ;
144+ newFormData [ index ] [ property ] = value ;
145+ onSetFormData ( newFormData ) ;
80146 } ;
81147
82- const addItem = ( at : number ) => {
148+ const onAddItem = ( at : number ) => {
83149 const newData = [ ...formData ] ;
84150 newData . splice ( at + 1 , 0 , {
85151 field : '' ,
86152 direction : 'Asc' ,
87153 } ) ;
88- setFormData ( newData ) ;
154+ onSetFormData ( newData ) ;
89155 } ;
90156
91- const removeItem = ( at : number ) => {
157+ const onRemoveItem = ( at : number ) => {
92158 const newData = [ ...formData ] ;
93159 newData . splice ( at , 1 ) ;
94- setFormData ( newData ) ;
160+ onSetFormData ( newData ) ;
95161 } ;
96162
97- const comboboxStyles = useMemo ( ( ) => {
98- return {
163+ const comboboxClassName = useMemo ( ( ) => {
164+ return css ( {
99165 width : `calc(${ String (
100166 Math . max ( ...fieldNames . map ( ( label ) => label . length ) , 10 )
101167 ) } ch)`,
102- } ;
168+ } ) ;
103169 } , [ fieldNames ] ) ;
104170
105171 return (
106172 < div className = { containerStyles } >
107- { formData . map ( ( sort , index : number ) => (
108- < div
109- className = { formGroupStyles }
110- key = { `sort-form-${ index } ` }
111- data-testid = { `sort-form-${ index } ` }
112- >
113- < Body className = { labelStyles } >
114- { index === 0 ? 'Sort documents by' : 'and' }
115- </ Body >
116- < div data-testid = { `sort-form-${ index } -field` } >
117- < ComboboxWithCustomOption
118- style = { comboboxStyles }
119- aria-label = "Select a field"
120- size = "default"
121- clearable = { false }
122- value = { sort . field }
123- onChange = { ( value : string | null ) => onSelectField ( index , value ) }
124- options = { fieldNames }
125- optionLabel = "Field:"
126- // Used for testing to access the popover for a stage
127- popoverClassName = { `sort-form-${ index } -field-combobox` }
173+ < ListEditor
174+ items = { formData }
175+ onAddItem = { ( index ) => onAddItem ( index ) }
176+ onRemoveItem = { ( index ) => onRemoveItem ( index ) }
177+ itemTestId = { ( index ) => `sort-form-${ index } ` }
178+ renderItem = { ( item , index ) => {
179+ return (
180+ < SortFormGroup
181+ comboboxClassName = { comboboxClassName }
182+ index = { index }
183+ sortField = { item . field }
184+ sortDirection = { item . direction }
185+ fields = { fieldNames }
186+ onChange = { ( prop , value ) => onChangeProperty ( index , prop , value ) }
128187 />
129- </ div >
130- < Body > in</ Body >
131- < div data-testid = { `sort-form-${ index } -direction` } >
132- { /* @ts -expect-error leafygreen unresonably expects a labelledby here */ }
133- < Select
134- className = { sortDirectionStyles }
135- allowDeselect = { false }
136- aria-label = "Select direction"
137- usePortal = { false }
138- value = { sort . direction }
139- onChange = { ( value : string ) =>
140- onSelectDirection ( index , value as SortDirection )
141- }
142- >
143- { SORT_DIRECTION_OPTIONS . map ( ( sort , index ) => {
144- return (
145- < Option key = { index } value = { sort . value } >
146- { sort . label }
147- </ Option >
148- ) ;
149- } ) }
150- </ Select >
151- </ div >
152- < Body > order</ Body >
153- < IconButton aria-label = "Add" onClick = { ( ) => addItem ( index ) } >
154- < Icon glyph = "Plus" />
155- </ IconButton >
156- { formData . length > 1 && (
157- < IconButton aria-label = "Remove" onClick = { ( ) => removeItem ( index ) } >
158- < Icon glyph = "Minus" />
159- </ IconButton >
160- ) }
161- </ div >
162- ) ) }
188+ ) ;
189+ } }
190+ />
163191 </ div >
164192 ) ;
165193} ;
0 commit comments