Last active
February 9, 2026 16:51
-
-
Save thisisgurkaran/4e07fd9cffc62db41abe5a1c5a8ca61f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useState } from "react"; | |
| import Box from "@mui/material/Box"; | |
| import Button from "@mui/material/Button"; | |
| import Card from "@mui/material/Card"; | |
| import CardContent from "@mui/material/CardContent"; | |
| import Checkbox from "@mui/material/Checkbox"; | |
| import List from "@mui/material/List"; | |
| import ListItem from "@mui/material/ListItem"; | |
| import ListItemIcon from "@mui/material/ListItemIcon"; | |
| import ListItemText from "@mui/material/ListItemText"; | |
| import TextField from "@mui/material/TextField"; | |
| import Typography from "@mui/material/Typography"; | |
| import SearchIcon from "@mui/icons-material/Search"; | |
| import ChevronRightIcon from "@mui/icons-material/ChevronRight"; | |
| import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; | |
| import InputAdornment from "@mui/material/InputAdornment"; | |
| import { filterByQuery } from "./transferListUtils"; | |
| export type TransferListProps<T> = { | |
| availableItems: T[]; | |
| selectedItems: T[]; | |
| checkedInAvailable: T[]; | |
| checkedInSelected: T[]; | |
| onToggle: (item: T) => () => void; | |
| onMoveRight: () => void; | |
| onMoveLeft: () => void; | |
| getKey: (item: T) => string; | |
| getLabel: (item: T) => string; | |
| getSecondary: (item: T) => string; | |
| availableTitle?: string; | |
| selectedTitle?: string; | |
| searchAvailablePlaceholder?: string; | |
| searchSelectedPlaceholder?: string; | |
| }; | |
| export function TransferList<T>({ | |
| availableItems, | |
| selectedItems, | |
| checkedInAvailable, | |
| checkedInSelected, | |
| onToggle, | |
| onMoveRight, | |
| onMoveLeft, | |
| getKey, | |
| getLabel, | |
| getSecondary, | |
| availableTitle = "Available", | |
| selectedTitle = "Selected", | |
| searchAvailablePlaceholder = "Search available...", | |
| searchSelectedPlaceholder = "Search selected...", | |
| }: TransferListProps<T>) { | |
| console.log(availableItems, "availabelItms"); | |
| const [searchAvailable, setSearchAvailable] = useState(""); | |
| const [searchSelected, setSearchSelected] = useState(""); | |
| const availableFiltered = filterByQuery( | |
| availableItems, | |
| searchAvailable, | |
| getLabel, | |
| getSecondary | |
| ); | |
| const selectedFiltered = filterByQuery( | |
| selectedItems, | |
| searchSelected, | |
| getLabel, | |
| getSecondary | |
| ); | |
| const renderList = ( | |
| title: string, | |
| totalCount: number, | |
| items: T[], | |
| checked: T[] | |
| ) => ( | |
| <Card variant="outlined" sx={{ minWidth: 220, maxHeight: 320 }}> | |
| <CardContent sx={{ py: 1, "&:last-child": { pb: 1 } }}> | |
| <Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}> | |
| {title} ({totalCount}) | |
| </Typography> | |
| <List | |
| dense | |
| component="div" | |
| role="list" | |
| sx={{ overflow: "auto", maxHeight: 240 }} | |
| > | |
| {items.map((item) => { | |
| const key = getKey(item); | |
| const label = getLabel(item); | |
| const isChecked = checked.some((c) => getKey(c) === key); | |
| return ( | |
| <ListItem | |
| key={key} | |
| role="listitem" | |
| component="button" | |
| onClick={onToggle(item)} | |
| sx={{ textAlign: "left", width: "100%", cursor: "pointer" }} | |
| > | |
| <ListItemIcon> | |
| <Checkbox checked={isChecked} disableRipple /> | |
| </ListItemIcon> | |
| <ListItemText primary={label} secondary={getSecondary(item)} /> | |
| </ListItem> | |
| ); | |
| })} | |
| </List> | |
| </CardContent> | |
| </Card> | |
| ); | |
| return ( | |
| <Box | |
| sx={{ | |
| display: "flex", | |
| alignItems: "flex-start", | |
| gap: 2, | |
| flexWrap: "wrap", | |
| }} | |
| > | |
| <Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | |
| <TextField | |
| placeholder={searchAvailablePlaceholder} | |
| value={searchAvailable} | |
| onChange={(e) => setSearchAvailable(e.target.value)} | |
| onKeyDown={(e) => e.key === "Enter" && e.preventDefault()} | |
| size="small" | |
| sx={{ minWidth: 220 }} | |
| InputProps={{ | |
| startAdornment: ( | |
| <InputAdornment position="start"> | |
| <SearchIcon fontSize="small" /> | |
| </InputAdornment> | |
| ), | |
| }} | |
| /> | |
| {renderList( | |
| availableTitle, | |
| availableItems.length, | |
| availableFiltered, | |
| checkedInAvailable | |
| )} | |
| </Box> | |
| <Box | |
| sx={{ | |
| display: "flex", | |
| flexDirection: "column", | |
| justifyContent: "center", | |
| gap: 1, | |
| }} | |
| > | |
| <Button | |
| variant="outlined" | |
| size="small" | |
| onClick={onMoveRight} | |
| disabled={checkedInAvailable.length === 0} | |
| aria-label="move selected right" | |
| > | |
| <ChevronRightIcon /> | |
| </Button> | |
| <Button | |
| variant="outlined" | |
| size="small" | |
| onClick={onMoveLeft} | |
| disabled={checkedInSelected.length === 0} | |
| aria-label="move selected left" | |
| > | |
| <ChevronLeftIcon /> | |
| </Button> | |
| </Box> | |
| <Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | |
| <TextField | |
| placeholder={searchSelectedPlaceholder} | |
| value={searchSelected} | |
| onChange={(e) => setSearchSelected(e.target.value)} | |
| onKeyDown={(e) => e.key === "Enter" && e.preventDefault()} | |
| size="small" | |
| sx={{ minWidth: 220 }} | |
| InputProps={{ | |
| startAdornment: ( | |
| <InputAdornment position="start"> | |
| <SearchIcon fontSize="small" /> | |
| </InputAdornment> | |
| ), | |
| }} | |
| /> | |
| {renderList( | |
| selectedTitle, | |
| selectedItems.length, | |
| selectedFiltered, | |
| checkedInSelected | |
| )} | |
| </Box> | |
| </Box> | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment