@@ -110,6 +110,9 @@ export function CommandPalette() {
110
110
function DeleteProjectsPage ( { onClose } : { onClose : ( ) => void } ) {
111
111
const router = useRouter ( ) ;
112
112
const [ projectIds , setProjectIds ] = React . useState < number [ ] > ( [ ] ) ;
113
+ const [ lastSelectedIndex , setLastSelectedIndex ] = React . useState <
114
+ number | null
115
+ > ( null ) ;
113
116
114
117
const currentTeam = useCurrentTeam ( ) ;
115
118
const currentProject = useCurrentProject ( ) ;
@@ -137,6 +140,62 @@ function DeleteProjectsPage({ onClose }: { onClose: () => void }) {
137
140
}
138
141
} ;
139
142
143
+ const toggleProject = (
144
+ projectId : number ,
145
+ index : number ,
146
+ event : React . MouseEvent ,
147
+ ) => {
148
+ if ( event . nativeEvent ?. shiftKey && lastSelectedIndex !== null ) {
149
+ // Implement shift+click selection
150
+ const start = Math . min ( lastSelectedIndex , index ) ;
151
+ const end = Math . max ( lastSelectedIndex , index ) ;
152
+ const isSelected = projectIds . includes ( projectId ) ;
153
+ const newProjectIds = new Set ( projectIds ) ;
154
+
155
+ if ( isSelected ) {
156
+ // Unselect this row and all the next consecutive selected
157
+ for ( let i = start ; i <= end ; i ++ ) {
158
+ const id = projects ?. [ i ] ?. id ;
159
+ if ( id && projectIds . includes ( id ) ) {
160
+ newProjectIds . delete ( id ) ;
161
+ }
162
+ }
163
+ } else {
164
+ // If there are no rows selected above, first try to select from below
165
+ const firstSelected =
166
+ projects ?. findIndex ( ( p ) => projectIds . includes ( p . id ) ) ?? - 1 ;
167
+ if ( firstSelected > index ) {
168
+ for ( let i = index ; i < firstSelected ; i ++ ) {
169
+ const id = projects ?. [ i ] ?. id ;
170
+ if ( id ) {
171
+ newProjectIds . add ( id ) ;
172
+ }
173
+ }
174
+ } else {
175
+ // Select all rows from the first unselected row above
176
+ for ( let i = index ; i >= 0 ; i -- ) {
177
+ const id = projects ?. [ i ] ?. id ;
178
+ if ( id && ! projectIds . includes ( id ) ) {
179
+ newProjectIds . add ( id ) ;
180
+ } else {
181
+ break ;
182
+ }
183
+ }
184
+ }
185
+ }
186
+
187
+ setProjectIds ( Array . from ( newProjectIds ) ) ;
188
+ } else {
189
+ // Regular click behavior
190
+ setProjectIds (
191
+ projectIds . includes ( projectId )
192
+ ? projectIds . filter ( ( id ) => id !== projectId )
193
+ : [ ...projectIds , projectId ] ,
194
+ ) ;
195
+ }
196
+ setLastSelectedIndex ( index ) ;
197
+ } ;
198
+
140
199
return (
141
200
< Command . Group heading = "Select projects to delete" >
142
201
{ isSubmitting && (
@@ -148,28 +207,28 @@ function DeleteProjectsPage({ onClose }: { onClose: () => void }) {
148
207
</ Command . Loading >
149
208
) }
150
209
{ ! isSubmitting &&
151
- projects ?. map ( ( project ) => (
210
+ projects ?. map ( ( project , index ) => (
152
211
< Command . Item
153
212
key = { project . id }
154
213
className = "flex justify-between"
155
214
keywords = { [ project . name , project . slug ] }
156
- onSelect = { ( ) =>
157
- setProjectIds (
158
- projectIds . includes ( project . id )
159
- ? projectIds . filter ( ( id ) => id !== project . id )
160
- : [ ... projectIds , project . id ] ,
215
+ onSelect = { ( event ) =>
216
+ toggleProject (
217
+ project . id ,
218
+ index ,
219
+ event as unknown as React . MouseEvent ,
161
220
)
162
221
}
163
222
>
164
223
< div className = "flex items-center gap-1" >
165
224
< Checkbox
166
225
className = "mr-1"
167
226
checked = { projectIds . includes ( project . id ) }
168
- onChange = { ( ) =>
169
- setProjectIds (
170
- projectIds . includes ( project . id )
171
- ? projectIds . filter ( ( id ) => id !== project . id )
172
- : [ ... projectIds , project . id ] ,
227
+ onChange = { ( event ) =>
228
+ toggleProject (
229
+ project . id ,
230
+ index ,
231
+ event as unknown as React . MouseEvent ,
173
232
)
174
233
}
175
234
/>
0 commit comments