Edit 1
Based on the OP's subsequent comments, I realized they also wanted the second selected item to depend on how close the item is to the click position. I made some updates.
See updated React Code Sandbox here
App.jsx
import React, { useState } from "react";
import "./styles.css";
const ElementsList = () => {
// 1. List of Items, they could be anything
const items = ["Elem 1", "Elem 2", "Elem 3", "Element 4", "Element 5"];
// 2. Track the selected items in state
const [selectedItems, setSelectedItems] = useState([]);
const handleClick = (e, index) => {
// 7. Extract the click position and element height from the event target.
const nextItems = getNextSelections({
currentIndex: index,
totalItems: items.length,
elementHeight: e.currentTarget.getBoundingClientRect().height,
distanceFromTop: e.clientY - e.currentTarget.getBoundingClientRect().top
});
// 4. Do whatever you want with the selected items :), then update state
setSelectedItems(nextItems);
};
return (
<div className="List">
{items.map((value, index) => (
<div
key={value}
className={`myClass ${
// 5. Conditionally set selected class if index is present in selected Items array
selectedItems.includes(index) ? "selected" : ""
}`}
// 6. Capture the click event and the index of the selected item
onClick={e => handleClick(e, index)}
>
{value}
</div>
))}
</div>
);
};
export default function App() {
return (
<div className="App">
<h1>2 Items Selector</h1>
<ElementsList />
</div>
);
}
getNextSelections helper
// Helper to get Select items based on mouse position
const getNextSelections = ({
currentIndex, // Index of the clicked Item
totalItems, // Total number of items in the list
elementHeight, // Height of the bounding rectangle of the element
distanceFromTop // Distance of Mouse click postion from top of boudning rectangle of the element
}) => {
// A. Return first and second item if first item is clicked, exit early
if (currentIndex <= 0) return [0, 1];
// B. Return last and second last if last item is clicked and exit early
if (currentIndex === totalItems - 1) return [currentIndex - 1, currentIndex];
// C. If clicked in top half of the element, return previous and current item
if (distanceFromTop < elementHeight / 2) {
console.log("Cicked Next to the Top of Element");
return [currentIndex - 1, currentIndex];
}
// D. Otherwise return current and next item indicies
return [currentIndex, currentIndex + 1];
};
styles.css
.App {
font-family: sans-serif;
text-align: center;
}
.List {
width: 80%;
margin: 0 auto;
text-align: center;
}
.myClass {
padding: 1rem;
border: solid 4px black;
border-bottom: none;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.myClass.selected {
background: rgb(236, 174, 174);
transition: background 1s;
}
.myClass:last-child {
border: solid 4px black;
}
I have added a helper to conditionally check the click position from the top of the selected element's bounding rectangle. Followed these assumptions.
- If the first item is clicked, just choose the first and second items.
[A]
- Choose the last and second last item if the last one is clicked.
[B]
- For elements in the middle of the list, choose the previous item as the second selection if the click happens in the top half of the clicked item's bounding rectangle
[C], otherwise choose the previous item. [D]
Code Sandbox: Multiple Items Select with Mouse Proximity
Edit 2
In your comments you say the list has rows and columns :( You can extend the helper to account for the horizontal proximity using getBoundingClientRect().left and width, and use the same approach to select the index of items to the side closest to the click.
Edit 3
Had removed the approach where the next selected item doesn't depend on the position of the mouse click, but the code can be found in this Revision 1 of this post here and the Codesandbox "Multiple Items Select without Mouse Proximity"