From e8c7a73fdb570f2faf461b90bf0c32e6801472b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Fri, 5 Apr 2024 09:19:02 +0100 Subject: [PATCH] chore: Refactor `SelectMultiple` (#2990) --- .../components/FileUploadAndLabel/Modal.tsx | 233 +----------------- .../FileUploadAndLabel/SelectMultiple.tsx | 233 ++++++++++++++++++ 2 files changed, 236 insertions(+), 230 deletions(-) create mode 100644 editor.planx.uk/src/@planx/components/FileUploadAndLabel/SelectMultiple.tsx diff --git a/editor.planx.uk/src/@planx/components/FileUploadAndLabel/Modal.tsx b/editor.planx.uk/src/@planx/components/FileUploadAndLabel/Modal.tsx index 9ac7b83b5a..225440f7cf 100644 --- a/editor.planx.uk/src/@planx/components/FileUploadAndLabel/Modal.tsx +++ b/editor.planx.uk/src/@planx/components/FileUploadAndLabel/Modal.tsx @@ -1,35 +1,18 @@ -import ArrowIcon from "@mui/icons-material/KeyboardArrowDown"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; -import Checkbox from "@mui/material/Checkbox"; -import Chip from "@mui/material/Chip"; import Dialog from "@mui/material/Dialog"; import DialogActions from "@mui/material/DialogActions"; import DialogContent from "@mui/material/DialogContent"; -import FormControl from "@mui/material/FormControl"; -import Input from "@mui/material/Input"; -import InputLabel from "@mui/material/InputLabel"; -import ListItemText from "@mui/material/ListItemText"; -import ListSubheader from "@mui/material/ListSubheader"; -import MenuItem from "@mui/material/MenuItem"; -import Select, { SelectChangeEvent, SelectProps } from "@mui/material/Select"; -import { styled } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; -import capitalize from "lodash/capitalize"; -import React, { useEffect, useState } from "react"; -import { usePrevious } from "react-use"; +import React, { useState } from "react"; import ErrorWrapper from "ui/shared/ErrorWrapper"; import { ValidationError } from "yup"; import { FileUploadSlot } from "../FileUpload/Public"; import { UploadedFileCard } from "../shared/PrivateFileUpload/UploadedFileCard"; -import { - addOrAppendSlots, - FileList, - getTagsForSlot, - removeSlots, -} from "./model"; +import { FileList } from "./model"; import { fileLabelSchema, formatFileLabelSchemaErrors } from "./schema"; +import { SelectMultiple } from "./SelectMultiple"; interface FileTaggingModalProps { uploadedFiles: FileUploadSlot[]; @@ -39,16 +22,6 @@ interface FileTaggingModalProps { removeFile: (slot: FileUploadSlot) => void; } -const ListHeader = styled(Box)(({ theme }) => ({ - display: "flex", - justifyContent: "space-between", - alignItems: "center", - padding: theme.spacing(1, 1.5), - background: theme.palette.grey[200], - // Offset default padding of MuiList - margin: "-8px 0 8px", -})); - export const FileTaggingModal = ({ uploadedFiles, fileList, @@ -150,203 +123,3 @@ export const FileTaggingModal = ({ ); }; - -interface SelectMultipleProps extends SelectProps { - uploadedFile: FileUploadSlot; - fileList: FileList; - setFileList: (value: React.SetStateAction) => void; -} - -const SelectMultiple = (props: SelectMultipleProps) => { - const { uploadedFile, fileList, setFileList } = props; - - const initialTags = getTagsForSlot(uploadedFile.id, fileList); - const [tags, setTags] = useState(initialTags); - const previousTags = usePrevious(tags) || initialTags; - const [open, setOpen] = React.useState(false); - - const handleChange = (event: SelectChangeEvent) => { - const { - target: { value }, - } = event; - setTags( - // On autofill we get a stringified value. - typeof value === "string" ? value.split(",") : value, - ); - }; - const handleClose = () => { - setOpen(false); - }; - const handleOpen = () => { - setOpen(true); - }; - - const updateFileListWithTags = ( - previousTags: string[] | undefined, - tags: string[], - ) => { - const updatedTags = tags.filter((tag) => !previousTags?.includes(tag)); - const removedTags = previousTags?.filter((tag) => !tags?.includes(tag)); - - if (updatedTags.length > 0) { - const updatedFileList = addOrAppendSlots( - updatedTags, - uploadedFile, - fileList, - ); - setFileList(updatedFileList); - } - - if (removedTags && removedTags.length > 0) { - const updatedFileList = removeSlots(removedTags, uploadedFile, fileList); - setFileList(updatedFileList); - } - }; - - useEffect(() => { - updateFileListWithTags(previousTags, tags); - }, [tags]); - - return ( - - theme.palette.link.main, - "&[data-shrink=true]": { - textDecoration: "none", - color: (theme) => theme.palette.text.primary, - top: "0", - transform: "translate(14px, -5px) scale(0.85)", - }, - }} - > - What does this file show? - - - - ); -}; diff --git a/editor.planx.uk/src/@planx/components/FileUploadAndLabel/SelectMultiple.tsx b/editor.planx.uk/src/@planx/components/FileUploadAndLabel/SelectMultiple.tsx new file mode 100644 index 0000000000..0d45ef7bdf --- /dev/null +++ b/editor.planx.uk/src/@planx/components/FileUploadAndLabel/SelectMultiple.tsx @@ -0,0 +1,233 @@ +import ArrowIcon from "@mui/icons-material/KeyboardArrowDown"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Checkbox from "@mui/material/Checkbox"; +import Chip from "@mui/material/Chip"; +import FormControl from "@mui/material/FormControl"; +import Input from "@mui/material/Input"; +import InputLabel from "@mui/material/InputLabel"; +import ListItemText from "@mui/material/ListItemText"; +import ListSubheader from "@mui/material/ListSubheader"; +import MenuItem from "@mui/material/MenuItem"; +import Select, { SelectChangeEvent, SelectProps } from "@mui/material/Select"; +import { styled } from "@mui/material/styles"; +import Typography from "@mui/material/Typography"; +import capitalize from "lodash/capitalize"; +import React, { useEffect, useState } from "react"; +import { usePrevious } from "react-use"; + +import { FileUploadSlot } from "../FileUpload/Public"; +import { + addOrAppendSlots, + FileList, + getTagsForSlot, + removeSlots, +} from "./model"; + +interface SelectMultipleProps extends SelectProps { + uploadedFile: FileUploadSlot; + fileList: FileList; + setFileList: (value: React.SetStateAction) => void; +} + +const ListHeader = styled(Box)(({ theme }) => ({ + display: "flex", + justifyContent: "space-between", + alignItems: "center", + padding: theme.spacing(1, 1.5), + background: theme.palette.grey[200], + // Offset default padding of MuiList + margin: "-8px 0 8px", +})); + +const StyledInputLabel = styled(InputLabel)(({ theme }) => ({ + top: "16%", + textDecoration: "underline", + color: theme.palette.link.main, + "&[data-shrink=true]": { + textDecoration: "none", + color: theme.palette.text.primary, + top: "0", + transform: "translate(14px, -5px) scale(0.85)", + }, +})); + +const StyledSelect = styled(Select)(({ theme }) => ({ + border: `1px solid ${theme.palette.border.main}`, + background: theme.palette.background.paper, + "& > div": { + minHeight: "50px", + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1), + }, + "& > div:focus": { + background: theme.palette.action.focus, + }, + "& > svg": { + color: theme.palette.primary.main, + width: "1.25em", + height: "1.25em", + top: "unset", + }, +})); + +export const SelectMultiple = (props: SelectMultipleProps) => { + const { uploadedFile, fileList, setFileList } = props; + + const initialTags = getTagsForSlot(uploadedFile.id, fileList); + const [tags, setTags] = useState(initialTags); + const previousTags = usePrevious(tags) || initialTags; + const [open, setOpen] = React.useState(false); + + const handleChange = (event: SelectChangeEvent) => { + const { + target: { value }, + } = event; + setTags( + // On autofill we get a stringified value. + typeof value === "string" ? value.split(",") : value, + ); + }; + const handleClose = () => setOpen(false); + const handleOpen = () => setOpen(true); + + const updateFileListWithTags = ( + previousTags: string[] | undefined, + tags: string[], + ) => { + const updatedTags = tags.filter((tag) => !previousTags?.includes(tag)); + const removedTags = previousTags?.filter((tag) => !tags?.includes(tag)); + + if (updatedTags.length > 0) { + const updatedFileList = addOrAppendSlots( + updatedTags, + uploadedFile, + fileList, + ); + setFileList(updatedFileList); + } + + if (removedTags && removedTags.length > 0) { + const updatedFileList = removeSlots(removedTags, uploadedFile, fileList); + setFileList(updatedFileList); + } + }; + + useEffect(() => { + updateFileListWithTags(previousTags, tags); + }, [tags]); + + return ( + + + What does this file show? + + } + inputProps={{ + name: uploadedFile.id, + "data-testid": "select", + "aria-labelledby": `select-multiple-file-tags-label-${uploadedFile.id}`, + }} + renderValue={(selected) => ( + + {selected.map((value) => ( + + ))} + + )} + MenuProps={{ + anchorOrigin: { + vertical: "bottom", + horizontal: "center", + }, + }} + > + + + + Select all that apply + + + + + {(Object.keys(fileList) as Array) + .filter((fileListCategory) => fileList[fileListCategory].length > 0) + .map((fileListCategory) => { + return [ + + + {`${capitalize(fileListCategory)} files`} + + , + ...fileList[fileListCategory].map((fileType) => { + return [ + + -1} + data-testid="select-checkbox" + inputProps={{ + "aria-label": `${fileType.name}`, + }} + /> + + , + ]; + }), + ]; + })} + + + ); +};