Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 

215 rindas
6.2 KiB

  1. "use client";
  2. import * as React from "react";
  3. import List from "@mui/material/List";
  4. import ListItem from "@mui/material/ListItem";
  5. import ListItemText from "@mui/material/ListItemText";
  6. import ListItemIcon from "@mui/material/ListItemIcon";
  7. import Checkbox from "@mui/material/Checkbox";
  8. import IconButton from "@mui/material/Fab";
  9. import Divider from "@mui/material/Divider";
  10. import ChevronLeft from "@mui/icons-material/ChevronLeft";
  11. import ChevronRight from "@mui/icons-material/ChevronRight";
  12. import intersection from "lodash/intersection";
  13. import difference from "lodash/difference";
  14. import Stack from "@mui/material/Stack";
  15. import Paper from "@mui/material/Paper";
  16. import Typography from "@mui/material/Typography";
  17. import ListSubheader from "@mui/material/ListSubheader";
  18. export interface LabelWithId {
  19. id: string;
  20. label: string;
  21. }
  22. export interface TransferListProps {
  23. allItems: LabelWithId[];
  24. initiallySelectedItems: LabelWithId[];
  25. onChange: () => void;
  26. allItemsLabel: string;
  27. selectedItemsLabel: string;
  28. }
  29. interface ItemListProps {
  30. items: LabelWithId[];
  31. checkedItems: LabelWithId[];
  32. label: string;
  33. handleToggleAll: (
  34. items: LabelWithId[],
  35. checkedItems: LabelWithId[],
  36. ) => React.MouseEventHandler;
  37. handleToggle: (item: LabelWithId) => React.MouseEventHandler;
  38. }
  39. const ItemList: React.FC<ItemListProps> = ({
  40. items,
  41. checkedItems,
  42. label,
  43. handleToggle,
  44. handleToggleAll,
  45. }) => {
  46. return (
  47. <Paper sx={{ width: "100%" }} variant="outlined">
  48. <List
  49. sx={{
  50. height: 400,
  51. bgcolor: "background.paper",
  52. overflow: "auto",
  53. }}
  54. disablePadding
  55. dense
  56. component="ul"
  57. subheader={
  58. <ListSubheader
  59. disableGutters
  60. component="li"
  61. onClick={handleToggleAll(items, checkedItems)}
  62. >
  63. <Stack direction="row" paddingY={1} paddingX={2}>
  64. <ListItemIcon>
  65. <Checkbox
  66. checked={
  67. checkedItems.length === items.length && items.length !== 0
  68. }
  69. indeterminate={
  70. checkedItems.length !== items.length &&
  71. checkedItems.length !== 0
  72. }
  73. disabled={items.length === 0}
  74. />
  75. </ListItemIcon>
  76. <Stack>
  77. <Typography variant="subtitle2">{label}</Typography>
  78. <Typography variant="caption">{`${checkedItems.length}/${items.length} selected`}</Typography>
  79. </Stack>
  80. </Stack>
  81. <Divider />
  82. </ListSubheader>
  83. }
  84. >
  85. {items.map((item) => {
  86. return (
  87. <ListItem key={item.id} onClick={handleToggle(item)}>
  88. <ListItemIcon>
  89. <Checkbox checked={checkedItems.includes(item)} tabIndex={-1} />
  90. </ListItemIcon>
  91. <ListItemText primary={item.label} />
  92. </ListItem>
  93. );
  94. })}
  95. </List>
  96. </Paper>
  97. );
  98. };
  99. const TransferList: React.FC<TransferListProps> = ({
  100. allItems,
  101. initiallySelectedItems,
  102. allItemsLabel,
  103. selectedItemsLabel,
  104. onChange,
  105. }) => {
  106. // Keep a map for the original order of items
  107. const sortMap = React.useMemo(() => {
  108. return allItems.reduce<{ [id: string]: number }>(
  109. (acc, item, index) => ({ ...acc, [item.id]: index }),
  110. {},
  111. );
  112. }, [allItems]);
  113. const compareFn = React.useCallback(
  114. (a: LabelWithId, b: LabelWithId) => sortMap[a.id] - sortMap[b.id],
  115. [sortMap],
  116. );
  117. const [checkedList, setCheckedList] = React.useState<LabelWithId[]>([]);
  118. const [leftList, setLeftList] = React.useState<LabelWithId[]>(
  119. difference(allItems, initiallySelectedItems),
  120. );
  121. const [rightList, setRightList] = React.useState<LabelWithId[]>(
  122. initiallySelectedItems,
  123. );
  124. const leftListChecked = intersection(checkedList, leftList);
  125. const rightListChecked = intersection(checkedList, rightList);
  126. const handleToggle = React.useCallback(
  127. (value: LabelWithId) => () => {
  128. const isChecked = checkedList.includes(value);
  129. const newCheckedList = isChecked
  130. ? difference(checkedList, [value])
  131. : [...checkedList, value];
  132. setCheckedList(newCheckedList);
  133. },
  134. [checkedList],
  135. );
  136. const handleToggleAll = React.useCallback(
  137. (items: LabelWithId[], checkedItems: LabelWithId[]) => () => {
  138. if (checkedItems.length === items.length) {
  139. setCheckedList(difference(checkedList, checkedItems));
  140. } else {
  141. setCheckedList([...checkedList, ...items]);
  142. }
  143. },
  144. [checkedList],
  145. );
  146. const handleCheckedRight = () => {
  147. setRightList([...rightList, ...leftListChecked].sort(compareFn));
  148. setLeftList(difference(leftList, leftListChecked).sort(compareFn));
  149. setCheckedList(difference(checkedList, leftListChecked));
  150. };
  151. const handleCheckedLeft = () => {
  152. setLeftList([...leftList, ...rightListChecked].sort(compareFn));
  153. setRightList(difference(rightList, rightListChecked).sort(compareFn));
  154. setCheckedList(difference(checkedList, rightListChecked));
  155. };
  156. return (
  157. <Stack spacing={2} direction="row" alignItems="center" position="relative">
  158. <ItemList
  159. items={leftList}
  160. checkedItems={leftListChecked}
  161. label={allItemsLabel}
  162. handleToggleAll={handleToggleAll}
  163. handleToggle={handleToggle}
  164. />
  165. <ItemList
  166. items={rightList}
  167. checkedItems={rightListChecked}
  168. label={selectedItemsLabel}
  169. handleToggleAll={handleToggleAll}
  170. handleToggle={handleToggle}
  171. />
  172. <Stack
  173. spacing={1}
  174. position="absolute"
  175. margin="0 !important"
  176. left="50%"
  177. sx={{ transform: "translateX(-50%)" }}
  178. >
  179. <IconButton
  180. color="secondary"
  181. size="small"
  182. onClick={handleCheckedRight}
  183. disabled={leftListChecked.length === 0}
  184. >
  185. <ChevronRight />
  186. </IconButton>
  187. <IconButton
  188. color="secondary"
  189. size="small"
  190. onClick={handleCheckedLeft}
  191. disabled={rightListChecked.length === 0}
  192. >
  193. <ChevronLeft />
  194. </IconButton>
  195. </Stack>
  196. </Stack>
  197. );
  198. };
  199. export default TransferList;