Skip to content

Commit 628d9bd

Browse files
nipunn1313Convex, Inc.
authored andcommitted
Add shift-click to command palette (#36681)
Add shift-click to command palette delete. https://github.com/user-attachments/assets/b28e7ace-f260-41d7-a8a2-9dd0c983f5ce GitOrigin-RevId: 9751c714248f42b587f30e6d91a14396137b1204
1 parent a7f9548 commit 628d9bd

File tree

1 file changed

+70
-11
lines changed

1 file changed

+70
-11
lines changed

npm-packages/dashboard/src/elements/CommandPalette.tsx

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ export function CommandPalette() {
110110
function DeleteProjectsPage({ onClose }: { onClose: () => void }) {
111111
const router = useRouter();
112112
const [projectIds, setProjectIds] = React.useState<number[]>([]);
113+
const [lastSelectedIndex, setLastSelectedIndex] = React.useState<
114+
number | null
115+
>(null);
113116

114117
const currentTeam = useCurrentTeam();
115118
const currentProject = useCurrentProject();
@@ -137,6 +140,62 @@ function DeleteProjectsPage({ onClose }: { onClose: () => void }) {
137140
}
138141
};
139142

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+
140199
return (
141200
<Command.Group heading="Select projects to delete">
142201
{isSubmitting && (
@@ -148,28 +207,28 @@ function DeleteProjectsPage({ onClose }: { onClose: () => void }) {
148207
</Command.Loading>
149208
)}
150209
{!isSubmitting &&
151-
projects?.map((project) => (
210+
projects?.map((project, index) => (
152211
<Command.Item
153212
key={project.id}
154213
className="flex justify-between"
155214
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,
161220
)
162221
}
163222
>
164223
<div className="flex items-center gap-1">
165224
<Checkbox
166225
className="mr-1"
167226
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,
173232
)
174233
}
175234
/>

0 commit comments

Comments
 (0)