|
- "use client";
-
- import * as React from "react";
- import List from "@mui/material/List";
- import ListItem from "@mui/material/ListItem";
- import ListItemText from "@mui/material/ListItemText";
- import ListItemIcon from "@mui/material/ListItemIcon";
- import Checkbox from "@mui/material/Checkbox";
- import IconButton from "@mui/material/Fab";
- import Divider from "@mui/material/Divider";
- import ChevronLeft from "@mui/icons-material/ChevronLeft";
- import ChevronRight from "@mui/icons-material/ChevronRight";
- import intersection from "lodash/intersection";
- import difference from "lodash/difference";
- import Stack from "@mui/material/Stack";
- import Paper from "@mui/material/Paper";
- import Typography from "@mui/material/Typography";
- import ListSubheader from "@mui/material/ListSubheader";
-
- export interface LabelWithId {
- id: string;
- label: string;
- }
-
- export interface TransferListProps {
- allItems: LabelWithId[];
- initiallySelectedItems: LabelWithId[];
- onChange: () => void;
- allItemsLabel: string;
- selectedItemsLabel: string;
- }
-
- interface ItemListProps {
- items: LabelWithId[];
- checkedItems: LabelWithId[];
- label: string;
- handleToggleAll: (
- items: LabelWithId[],
- checkedItems: LabelWithId[],
- ) => React.MouseEventHandler;
- handleToggle: (item: LabelWithId) => React.MouseEventHandler;
- }
-
- const ItemList: React.FC<ItemListProps> = ({
- items,
- checkedItems,
- label,
- handleToggle,
- handleToggleAll,
- }) => {
- return (
- <Paper sx={{ width: "100%" }} variant="outlined">
- <List
- sx={{
- height: 400,
- bgcolor: "background.paper",
- overflow: "auto",
- }}
- disablePadding
- dense
- component="ul"
- subheader={
- <ListSubheader
- disableGutters
- component="li"
- onClick={handleToggleAll(items, checkedItems)}
- >
- <Stack direction="row" paddingY={1} paddingX={2}>
- <ListItemIcon>
- <Checkbox
- checked={
- checkedItems.length === items.length && items.length !== 0
- }
- indeterminate={
- checkedItems.length !== items.length &&
- checkedItems.length !== 0
- }
- disabled={items.length === 0}
- />
- </ListItemIcon>
- <Stack>
- <Typography variant="subtitle2">{label}</Typography>
- <Typography variant="caption">{`${checkedItems.length}/${items.length} selected`}</Typography>
- </Stack>
- </Stack>
- <Divider />
- </ListSubheader>
- }
- >
- {items.map((item) => {
- return (
- <ListItem key={item.id} onClick={handleToggle(item)}>
- <ListItemIcon>
- <Checkbox checked={checkedItems.includes(item)} tabIndex={-1} />
- </ListItemIcon>
- <ListItemText primary={item.label} />
- </ListItem>
- );
- })}
- </List>
- </Paper>
- );
- };
-
- const TransferList: React.FC<TransferListProps> = ({
- allItems,
- initiallySelectedItems,
- allItemsLabel,
- selectedItemsLabel,
- onChange,
- }) => {
- // Keep a map for the original order of items
- const sortMap = React.useMemo(() => {
- return allItems.reduce<{ [id: string]: number }>(
- (acc, item, index) => ({ ...acc, [item.id]: index }),
- {},
- );
- }, [allItems]);
- const compareFn = React.useCallback(
- (a: LabelWithId, b: LabelWithId) => sortMap[a.id] - sortMap[b.id],
- [sortMap],
- );
-
- const [checkedList, setCheckedList] = React.useState<LabelWithId[]>([]);
- const [leftList, setLeftList] = React.useState<LabelWithId[]>(
- difference(allItems, initiallySelectedItems),
- );
- const [rightList, setRightList] = React.useState<LabelWithId[]>(
- initiallySelectedItems,
- );
-
- const leftListChecked = intersection(checkedList, leftList);
- const rightListChecked = intersection(checkedList, rightList);
-
- const handleToggle = React.useCallback(
- (value: LabelWithId) => () => {
- const isChecked = checkedList.includes(value);
- const newCheckedList = isChecked
- ? difference(checkedList, [value])
- : [...checkedList, value];
-
- setCheckedList(newCheckedList);
- },
- [checkedList],
- );
-
- const handleToggleAll = React.useCallback(
- (items: LabelWithId[], checkedItems: LabelWithId[]) => () => {
- if (checkedItems.length === items.length) {
- setCheckedList(difference(checkedList, checkedItems));
- } else {
- setCheckedList([...checkedList, ...items]);
- }
- },
- [checkedList],
- );
-
- const handleCheckedRight = () => {
- setRightList([...rightList, ...leftListChecked].sort(compareFn));
- setLeftList(difference(leftList, leftListChecked).sort(compareFn));
- setCheckedList(difference(checkedList, leftListChecked));
- };
-
- const handleCheckedLeft = () => {
- setLeftList([...leftList, ...rightListChecked].sort(compareFn));
- setRightList(difference(rightList, rightListChecked).sort(compareFn));
- setCheckedList(difference(checkedList, rightListChecked));
- };
-
- return (
- <Stack spacing={2} direction="row" alignItems="center" position="relative">
- <ItemList
- items={leftList}
- checkedItems={leftListChecked}
- label={allItemsLabel}
- handleToggleAll={handleToggleAll}
- handleToggle={handleToggle}
- />
- <ItemList
- items={rightList}
- checkedItems={rightListChecked}
- label={selectedItemsLabel}
- handleToggleAll={handleToggleAll}
- handleToggle={handleToggle}
- />
- <Stack
- spacing={1}
- position="absolute"
- margin="0 !important"
- left="50%"
- sx={{ transform: "translateX(-50%)" }}
- >
- <IconButton
- color="secondary"
- size="small"
- onClick={handleCheckedRight}
- disabled={leftListChecked.length === 0}
- >
- <ChevronRight />
- </IconButton>
- <IconButton
- color="secondary"
- size="small"
- onClick={handleCheckedLeft}
- disabled={rightListChecked.length === 0}
- >
- <ChevronLeft />
- </IconButton>
- </Stack>
- </Stack>
- );
- };
-
- export default TransferList;
|