diff --git a/src/components/item/FolderContent.tsx b/src/components/item/FolderContent.tsx index 5d01d9590..3d2a03563 100644 --- a/src/components/item/FolderContent.tsx +++ b/src/components/item/FolderContent.tsx @@ -1,4 +1,4 @@ -import { Alert, Box, Stack, Typography } from '@mui/material'; +import { Alert, Box, Stack, Typography, useTheme } from '@mui/material'; import { PackedItem, @@ -27,6 +27,7 @@ import { SelectionContextProvider, useSelectionContext, } from '../main/list/SelectionContext'; +import { useDragSelection } from '../main/list/useDragSelection'; import { DesktopMap } from '../map/DesktopMap'; import NoItemFilters from '../pages/NoItemFilters'; import SortingSelect from '../table/SortingSelect'; @@ -49,6 +50,10 @@ const Content = ({ item, searchText, items, sortBy }: Props) => { const { itemTypes } = useFilterItemsContext(); const { selectedIds, clearSelection, toggleSelection } = useSelectionContext(); + const theme = useTheme(); + const DragSelection = useDragSelection({ + adjustments: { marginTop: 70, marginLeft: theme.spacing(3) }, + }); const enableEditing = item.permission ? PermissionLevelCompare.lte(PermissionLevel.Write, item.permission) @@ -86,6 +91,7 @@ const Content = ({ item, searchText, items, sortBy }: Props) => { /> )} + ); } diff --git a/src/components/main/list/SelectionContext.tsx b/src/components/main/list/SelectionContext.tsx index 61051cf61..4101847a5 100644 --- a/src/components/main/list/SelectionContext.tsx +++ b/src/components/main/list/SelectionContext.tsx @@ -7,34 +7,24 @@ import { useState, } from 'react'; -import { PRIMARY_COLOR } from '@graasp/ui'; - -import { - Box, - boxesIntersect, - useSelectionContainer, -} from '@air/react-drag-to-select'; - -import { ITEM_CARD_CLASS } from '@/config/selectors'; - type SelectionContextValue = { selectedIds: string[]; toggleSelection: (id: string) => void; + addToSelection: (id: string) => void; clearSelection: () => void; }; export const SelectionContext = createContext({ selectedIds: [], toggleSelection: () => {}, + addToSelection: () => {}, clearSelection: () => {}, }); export const SelectionContextProvider = ({ children, - elementClass = ITEM_CARD_CLASS, }: { children: JSX.Element; - elementClass?: string; }): JSX.Element => { const [selection, setSelection] = useState(new Set()); const elementsContainerRef = useRef(null); @@ -55,58 +45,15 @@ export const SelectionContextProvider = ({ [selection], ); - const { DragSelection } = useSelectionContainer({ - eventsElement: document.getElementById('root'), - onSelectionChange: (box) => { - /** - * Here we make sure to adjust the box's left and top with the scroll position of the window - * @see https://github.com/AirLabsTeam/react-drag-to-select/#scrolling - */ - const scrollAwareBox: Box = { - ...box, - top: box.top + window.scrollY, - left: box.left + window.scrollX, - }; - - Array.from(document.getElementsByClassName(elementClass)).forEach( - (item) => { - const bb = item.getBoundingClientRect(); - if ( - boxesIntersect(scrollAwareBox, bb) && - item.parentNode instanceof HTMLElement - ) { - const itemId = item.parentNode.dataset.id; - if (itemId) { - selection.add(itemId); - } - } - }, - ); - - setSelection(new Set(selection)); - }, - shouldStartSelecting: (e) => { - // does not trigger drag selection if mousedown on card - if (e instanceof HTMLElement) { - return !e?.closest(`.${ITEM_CARD_CLASS}`); + const addToSelection = useCallback( + (id: string) => { + if (!selection.has(id)) { + selection.add(id); + setSelection(new Set(selection)); } - return true; - }, - onSelectionStart: () => { - // clear selection on new dragging action - clearSelection(); }, - onSelectionEnd: () => {}, - selectionProps: { - style: { - border: `2px dashed ${PRIMARY_COLOR}`, - borderRadius: 4, - backgroundColor: 'lightblue', - opacity: 0.5, - }, - }, - isEnabled: true, - }); + [selection], + ); const value: SelectionContextValue = useMemo( () => ({ @@ -114,13 +61,19 @@ export const SelectionContextProvider = ({ toggleSelection, clearSelection, elementsContainerRef, + addToSelection, }), - [selection, toggleSelection, clearSelection, elementsContainerRef], + [ + selection, + toggleSelection, + clearSelection, + addToSelection, + elementsContainerRef, + ], ); return ( -
{children}
); diff --git a/src/components/main/list/useDragSelection.tsx b/src/components/main/list/useDragSelection.tsx new file mode 100644 index 000000000..a0e7612d3 --- /dev/null +++ b/src/components/main/list/useDragSelection.tsx @@ -0,0 +1,76 @@ +import { ReactElement } from 'react'; + +import { PRIMARY_COLOR } from '@graasp/ui'; + +import { + Box, + boxesIntersect, + useSelectionContainer, +} from '@air/react-drag-to-select'; + +import { ITEM_CARD_CLASS } from '@/config/selectors'; + +import { useSelectionContext } from './SelectionContext'; + +export const useDragSelection = ({ + elementClass = ITEM_CARD_CLASS, + adjustments = {}, +} = {}): (() => ReactElement) => { + const { addToSelection, clearSelection } = useSelectionContext(); + + const { DragSelection } = useSelectionContainer({ + eventsElement: document.getElementById('root'), + onSelectionChange: (box) => { + /** + * Here we make sure to adjust the box's left and top with the scroll position of the window + * @see https://github.com/AirLabsTeam/react-drag-to-select/#scrolling + */ + const scrollAwareBox: Box = { + ...box, + top: box.top + window.scrollY, + left: box.left + window.scrollX, + }; + + Array.from(document.getElementsByClassName(elementClass)).forEach( + (item) => { + const bb = item.getBoundingClientRect(); + if ( + boxesIntersect(scrollAwareBox, bb) && + item.parentNode instanceof HTMLElement + ) { + const itemId = item.parentNode.dataset.id; + if (itemId) { + addToSelection(itemId); + } + } + }, + ); + }, + shouldStartSelecting: (e) => { + // does not trigger drag selection if mousedown on card + if (e instanceof HTMLElement) { + return !e?.closest(`.${elementClass}`); + } + return true; + }, + onSelectionStart: () => { + // clear selection on new dragging action + clearSelection(); + }, + onSelectionEnd: () => {}, + selectionProps: { + style: { + // adjustement + // https://github.com/AirLabsTeam/react-drag-to-select/issues/30 + ...adjustments, + border: `2px dashed ${PRIMARY_COLOR}`, + borderRadius: 4, + backgroundColor: 'lightblue', + opacity: 0.5, + }, + }, + isEnabled: true, + }); + + return DragSelection; +}; diff --git a/src/components/pages/RecycledItemsScreen.tsx b/src/components/pages/RecycledItemsScreen.tsx index d32b42b77..40f567522 100644 --- a/src/components/pages/RecycledItemsScreen.tsx +++ b/src/components/pages/RecycledItemsScreen.tsx @@ -1,4 +1,4 @@ -import { Alert, Stack } from '@mui/material'; +import { Alert, Stack, useTheme } from '@mui/material'; import { Ordering } from '@/enums'; @@ -21,6 +21,7 @@ import { SelectionContextProvider, useSelectionContext, } from '../main/list/SelectionContext'; +import { useDragSelection } from '../main/list/useDragSelection'; import ItemCard from '../table/ItemCard'; import SortingSelect from '../table/SortingSelect'; import { SortingOptions } from '../table/types'; @@ -37,6 +38,7 @@ const RecycledItemsScreenContent = ({ const { data: recycledItems, isLoading, isError } = hooks.useRecycledItems(); const options = useTranslatedSortingOptions(); const { shouldDisplayItem } = useFilterItemsContext(); + const theme = useTheme(); const { sortBy, setSortBy, ordering, setOrdering, sortFn } = useSorting({ sortBy: SortingOptions.ItemUpdatedAt, @@ -51,70 +53,77 @@ const RecycledItemsScreenContent = ({ ?.sort(sortFn); const { selectedIds, toggleSelection } = useSelectionContext(); + const DragSelection = useDragSelection({ + adjustments: { marginTop: 70, marginLeft: theme.spacing(3) }, + }); + // render this when there is data from the query if (recycledItems?.length) { const hasSelection = selectedIds.length && filteredData?.length; return ( - - - {hasSelection ? ( - - ) : ( - - - - {sortBy && setSortBy && ( - - )} - + <> + + + {hasSelection ? ( + + ) : ( + + + + {sortBy && setSortBy && ( + + )} + + - - )} + )} + + { + // render the filtered data and when it is empty display that nothing matches the search + filteredData?.length ? ( + filteredData.map((item) => ( + toggleSelection(item.id)} + isSelected={selectedIds.includes(item.id)} + showThumbnail={false} + allowNavigation={false} + footer={ + + + + + } + /> + )) + ) : ( + + {translateBuilder(BUILDER.TRASH_NO_ITEM_SEARCH, { + search: searchText, + })} + + ) + } - { - // render the filtered data and when it is empty display that nothing matches the search - filteredData?.length ? ( - filteredData.map((item) => ( - toggleSelection(item.id)} - isSelected={selectedIds.includes(item.id)} - showThumbnail={false} - allowNavigation={false} - footer={ - - - - - } - /> - )) - ) : ( - - {translateBuilder(BUILDER.TRASH_NO_ITEM_SEARCH, { - search: searchText, - })} - - ) - } - + + ); } diff --git a/src/components/pages/home/HomeScreen.tsx b/src/components/pages/home/HomeScreen.tsx index 0ad0f113b..f175f489b 100644 --- a/src/components/pages/home/HomeScreen.tsx +++ b/src/components/pages/home/HomeScreen.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; -import { Alert, Box, LinearProgress, Stack } from '@mui/material'; +import { Alert, Box, LinearProgress, Stack, useTheme } from '@mui/material'; import { Button } from '@graasp/ui'; @@ -9,6 +9,7 @@ import { SelectionContextProvider, useSelectionContext, } from '@/components/main/list/SelectionContext'; +import { useDragSelection } from '@/components/main/list/useDragSelection'; import { ITEM_PAGE_SIZE } from '@/config/constants'; import { ShowOnlyMeChangeType } from '@/config/types'; import { ItemLayoutMode, Ordering } from '@/enums'; @@ -43,6 +44,7 @@ const HomeScreenContent = ({ searchText }: { searchText: string }) => { const { data: currentMember } = hooks.useCurrentMember(); const { itemTypes } = useFilterItemsContext(); const [showOnlyMe, setShowOnlyMe] = useState(false); + const theme = useTheme(); const { selectedIds, toggleSelection, clearSelection } = useSelectionContext(); @@ -66,6 +68,10 @@ const HomeScreenContent = ({ searchText }: { searchText: string }) => { { pageSize: ITEM_PAGE_SIZE }, ); + const DragSelection = useDragSelection({ + adjustments: { marginTop: 100, marginLeft: theme.spacing(3) }, + }); + const onShowOnlyMeChange: ShowOnlyMeChangeType = (checked) => { setShowOnlyMe(checked); }; @@ -129,6 +135,7 @@ const HomeScreenContent = ({ searchText }: { searchText: string }) => { return ( <> +