From 71ca716171dbd2ec75139c2ba0dcf68dc25cd074 Mon Sep 17 00:00:00 2001 From: Nipuna Gunathilake Date: Sat, 21 Oct 2023 22:21:00 +0800 Subject: [PATCH 1/2] update map header item styles --- .../home/UserInput/Location/index.tsx | 13 +- client/components/map/Buttons.tsx | 22 ++- client/components/map/HeaderButtons.tsx | 37 ---- client/components/map/index.tsx | 1 - client/spa-pages/components/MapPage.tsx | 186 ++++++++++++++---- 5 files changed, 173 insertions(+), 86 deletions(-) delete mode 100644 client/components/map/HeaderButtons.tsx diff --git a/client/components/home/UserInput/Location/index.tsx b/client/components/home/UserInput/Location/index.tsx index 9441f34..c608074 100644 --- a/client/components/home/UserInput/Location/index.tsx +++ b/client/components/home/UserInput/Location/index.tsx @@ -1,4 +1,4 @@ -import { Text } from "@chakra-ui/react"; +import { Box, Flex, Text } from "@chakra-ui/react"; import { fetchAddresses } from "api/onemap"; import { useUserInputs } from "hooks/useUserSelection"; import debounce from "lodash/debounce"; @@ -10,9 +10,10 @@ import { AddressOption } from "app-context/UserSelectionContext/types"; interface LocationProps { handleBlur?: () => void; showText: boolean; + containerStyle?: React.CSSProperties; } -export const Location = ({ handleBlur, showText }: LocationProps) => { +export const Location = ({ handleBlur, showText, containerStyle = {} }: LocationProps) => { const [showEmptyWarning, setShowEmptyWarning] = useState(false); const { address, setAddress } = useUserInputs(); const { value: addressValue } = address; @@ -46,7 +47,7 @@ export const Location = ({ handleBlur, showText }: LocationProps) => { }, [addressValue]); return ( -
+ {showText && ( Where are you at? @@ -66,6 +67,10 @@ export const Location = ({ handleBlur, showText }: LocationProps) => { setShowEmptyWarning(false); }} styles={{ + container: (base) => ({ + ...base, + ...containerStyle, + }), control: (base) => ({ ...base, overflow: "hidden", @@ -89,6 +94,6 @@ export const Location = ({ handleBlur, showText }: LocationProps) => { handleShowError(); }} /> -
+ ); }; diff --git a/client/components/map/Buttons.tsx b/client/components/map/Buttons.tsx index 67562c4..649d194 100644 --- a/client/components/map/Buttons.tsx +++ b/client/components/map/Buttons.tsx @@ -1,22 +1,34 @@ -import { HamburgerIcon, Search2Icon, CloseIcon } from "@chakra-ui/icons"; +import { Search2Icon, CloseIcon } from "@chakra-ui/icons"; import { MouseEventHandler } from "react"; -import { IconButton } from "@chakra-ui/react"; +import { Icon, IconButton } from "@chakra-ui/react"; import { COLORS } from "theme"; interface ButtonProps { onClick: MouseEventHandler; + height?: string; } // Button Design for the filter button -export const FilterButton = ({ onClick }: ButtonProps) => { +export const FilterButton = ({ onClick, height }: ButtonProps) => { return ( } + icon={ + + + + } onClick={onClick} + height={height} /> ); }; diff --git a/client/components/map/HeaderButtons.tsx b/client/components/map/HeaderButtons.tsx deleted file mode 100644 index 985d0c2..0000000 --- a/client/components/map/HeaderButtons.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Flex, Button, Spacer } from "@chakra-ui/react"; -import { COLORS } from "theme"; -import { Dispatch, SetStateAction } from "react"; -import { Pages } from "spa-pages/pageEnums"; -import { ArrowBackIcon, HamburgerIcon } from "@chakra-ui/icons"; -type Props = { - setPage: Dispatch>; -}; - -export const HeaderButtons = ({ setPage }: Props) => { - return ( - <> - - - - - - - ); -}; diff --git a/client/components/map/index.tsx b/client/components/map/index.tsx index 29d86ee..17ad44b 100644 --- a/client/components/map/index.tsx +++ b/client/components/map/index.tsx @@ -1,6 +1,5 @@ export * from "./FacilityCard"; export * from "./FilterPanel"; -export * from "./HeaderButtons"; export * from "./MapContextProvider"; export * from "./NearbyFacilitiesPanel"; // export * from "./PullUpTab"; diff --git a/client/spa-pages/components/MapPage.tsx b/client/spa-pages/components/MapPage.tsx index 270aad7..579620e 100644 --- a/client/spa-pages/components/MapPage.tsx +++ b/client/spa-pages/components/MapPage.tsx @@ -1,6 +1,6 @@ // General Imports import { BasePage } from "layouts/BasePage"; -import { Flex, VStack, Box } from "@chakra-ui/react"; +import { Flex, VStack, Box, IconButton } from "@chakra-ui/react"; import { Dispatch, SetStateAction, useState } from "react"; import { Pages } from "spa-pages/pageEnums"; import { useUserInputs } from "hooks/useUserSelection"; @@ -22,7 +22,7 @@ import GeneralIcon from "components/map/Marker/icons/GeneralIcon"; import ClusterIcon from "components/map/Marker/icons/ClusterIcon"; // import NearbyFacilitiesPanel from "components/map/NearbyFacilitiesPanel"; // import PullUpTab from "components/map/PullUpTab"; -import { HeaderButtons, FacilityCard, FilterPanel, MapContextProvider } from "components/map"; +import { FacilityCard, FilterPanel, MapContextProvider } from "components/map"; import { FilterButton } from "components/map/Buttons"; // Leaflet Imports @@ -32,6 +32,7 @@ import useMapContext from "../../hooks/useMapContext"; import useLeafletWindow from "../../hooks/useLeafletWindow"; import { useResizeDetector } from "react-resize-detector"; import NonRecyclableModal from "components/common/NonRecyclableModal"; +import { ArrowBackIcon } from "@chakra-ui/icons"; // Reference page: https://github.com/richard-unterberg/next-leaflet-starter-typescript/blob/master/src/components/Map/ui/LocateButton.tsx // Next.js requires dynamic imports for Leaflet.js compatibility @@ -350,43 +351,15 @@ const MapInner = ({ setPage }: Props) => { - - {/* Header Buttons */} - - - handleChangedLocation(itemState)} - /> - {/* setFilterShow(false)} /> */} - setFilterShow(true)} /> - - ({ + ...base, + flex: 1, + border: "none", + minWidth: 0, + }), + control: (base) => ({ + ...base, + border: "none", + overflow: "auto", + }), + input: (base) => ({ + ...base, + border: "none", + }), + dropdownIndicator: () => ({ + display: "none", + }), + indicatorSeparator: () => ({ + display: "none", + }), + valueContainer: (base) => ({ + ...base, + flexWrap: "nowrap", + overflow: "auto", + marginRight: 0, + paddingRight: 0, + }), + clearIndicator: () => ({ + display: "none", + }), + multiValue: (base) => ({ + ...base, + background: "#E0F0EF", + borderRadius: "42px", + minWidth: "fit-content", + padding: "5px 10px", + }), + }} + /> + ); +} + export default MapPage; From 497fc087475ce1c8147388e57371499540a8f608 Mon Sep 17 00:00:00 2001 From: Nipuna Gunathilake Date: Sun, 22 Oct 2023 04:00:30 +0800 Subject: [PATCH 2/2] convert item filter into a modal --- client/components/map/FilterPanel.tsx | 266 ++++++++++++++++++------ client/spa-pages/components/MapPage.tsx | 51 +++-- 2 files changed, 230 insertions(+), 87 deletions(-) diff --git a/client/components/map/FilterPanel.tsx b/client/components/map/FilterPanel.tsx index 3b7bf5d..e4f8ac1 100644 --- a/client/components/map/FilterPanel.tsx +++ b/client/components/map/FilterPanel.tsx @@ -1,117 +1,245 @@ import { Box, - VStack, HStack, - Spacer, - Button, Text, Slider, SliderTrack, SliderFilledTrack, SliderThumb, + Modal, + ModalContent, + ModalOverlay, Divider, - CheckboxGroup, - Checkbox, - Stack, + Button, + Flex, + useCheckbox, + chakra, + CheckboxProps, + useCheckboxGroup, + Spacer, + useRadio, + UseRadioProps, + useRadioGroup, } from "@chakra-ui/react"; -import { XButton } from "./Buttons"; import { COLORS } from "theme"; import { TItemSelection, TEmptyItem } from "app-context/SheetyContext/types"; import { OptionType } from "spa-pages"; -import { ChangeEvent } from "react"; +import React, { ChangeEvent, PropsWithChildren } from "react"; +import { Methods } from "api/sheety/enums"; type FilterProps = { - isMobile: boolean | undefined; - setFilterShow: () => void; + isOpen: boolean; filterApply: () => void; handleSliderChange: (val: number) => void; range: number; itemState: (TItemSelection | TEmptyItem)[]; selectOptions: OptionType[]; handleCheckboxChange: (e: ChangeEvent) => void; + selectAllItems: () => void; }; export const FilterPanel = ({ - isMobile, - setFilterShow, + isOpen, filterApply, handleSliderChange, range, itemState, selectOptions, handleCheckboxChange, + selectAllItems, }: FilterProps) => { + const selectedOptionsWithCheckedState = selectOptions.map((option) => { + const isChecked = itemState.some( + (item) => item.name === option.value && item.method === option.method, + ); + return { ...option, isChecked }; + }); + return ( - - - - - - - - - - - Distance - - + undefined}> + + + + Apply + + } + > + + + + + + + + + 3km 10km handleSliderChange(val)} > - + - + - - - - - Items - - item.name)} - > - - {selectOptions.map((item) => ( - handleCheckboxChange(e)} - key={item.idx} - data-key={item.idx} - value={item.value} - name={item.method} - > - {item.value} - - ))} - - - - + + + + ); +}; + +function FilterSection({ + title, + hideDivider, + button, + children, +}: React.PropsWithChildren<{ title: string; hideDivider?: boolean; button?: JSX.Element }>) { + return ( + + + {title} + {button} + + {children} + {hideDivider ? ( + + ) : ( + + )} + + ); +} + +const CheckboxGroup = ({ + items, + onChange, + onSelectAll, +}: { + items: Array<{ + value: string; + method?: Methods; + isChecked: boolean; + }>; + onChange: (item: any) => void; + onSelectAll: () => void; +}) => { + const { getCheckboxProps } = useCheckboxGroup({ + value: items.filter((item) => item.isChecked).map((item) => item.value), + }); + + return ( + + {items.map((item) => ( + + ))} + + Select All + + + ); +}; + +const ChipCheckbox = (props: CheckboxProps) => { + const { state, getInputProps, htmlProps } = useCheckbox(props); + + return ( + + + {props.value} + + ); +}; + +const ChipRadioGroup = ({ items }: { items: Array }) => { + const { getRootProps, getRadioProps } = useRadioGroup({ + name: "sortBy", + defaultValue: "Nearest", + }); + + const group = getRootProps(); + + return ( + + {items.map((value) => { + const radio = getRadioProps({ value }); + return ( + + {value} + + ); + })} + + ); +}; + +const ChipRadio = (props: PropsWithChildren) => { + const { getInputProps, getRadioProps, state } = useRadio(props); + + const input = getInputProps(); + + return ( + + + + {props.children} + ); }; + +const Chip = ({ + children, + isChecked, + darkBackground, +}: React.PropsWithChildren<{ isChecked: boolean; darkBackground?: boolean }>) => { + const selectedColor = darkBackground ? "teal.500" : "teal.50"; + const selectedTextColor = darkBackground ? "white" : "black"; + + return ( + + {children} + + ); +}; diff --git a/client/spa-pages/components/MapPage.tsx b/client/spa-pages/components/MapPage.tsx index 579620e..160e371 100644 --- a/client/spa-pages/components/MapPage.tsx +++ b/client/spa-pages/components/MapPage.tsx @@ -1,6 +1,6 @@ // General Imports import { BasePage } from "layouts/BasePage"; -import { Flex, VStack, Box, IconButton } from "@chakra-ui/react"; +import { Flex, VStack, Box, IconButton, useDisclosure } from "@chakra-ui/react"; import { Dispatch, SetStateAction, useState } from "react"; import { Pages } from "spa-pages/pageEnums"; import { useUserInputs } from "hooks/useUserSelection"; @@ -78,7 +78,7 @@ const MapInner = ({ setPage }: Props) => { ////// States ////// // Filters - const [filterShow, setFilterShow] = useState(false); + const { isOpen: isFilterOpen, onOpen: onFilterOpen, onClose: onFilterClose } = useDisclosure(); const [range, setRange] = useState(60); // const [isExpanded, setIsExpanded] = useState(false); @@ -279,6 +279,23 @@ const MapInner = ({ setPage }: Props) => { setItemState(updatedItemState); }; + const selectAllItems = () => { + const selectOptions: OptionType[] = items.map((item) => ({ + value: item.name, + label: item.name, + method: item.method, + idx: index++, + })); + const itemState = items.map((item) => ({ + name: item.name, + method: item.method, + })); + + handleChangedLocation(itemState); + setItemState(itemState); + setSelectedOptions(selectOptions); + }; + // Handle the changes in distance selected in Filter panel const handleSliderChange = (val: number) => { const dist = val / 10; @@ -353,7 +370,7 @@ const MapInner = ({ setPage }: Props) => { { getMatchingFacility={getMatchingFacility} /> )} */} - {filterShow && ( - setFilterShow(true)} - filterApply={() => setFilterShow(false)} - handleSliderChange={handleSliderChange} - range={range} - itemState={itemState} - selectOptions={selectOptions} - handleCheckboxChange={handleCheckboxChange} - /> - )} + onFilterClose()} + handleSliderChange={handleSliderChange} + range={range} + itemState={itemState} + selectOptions={selectOptions} + selectAllItems={selectAllItems} + handleCheckboxChange={handleCheckboxChange} + /> ); }; @@ -423,15 +438,14 @@ export const MapPage = ({ setPage }: Props) => ( export const MapHeaderButtons = ({ setPage, - setFilterShow, selectedOptions, selectOptions, handleMultiselectOnChange, itemState, handleChangedLocation, + onFilterOpen, }: { setPage: Dispatch>; - setFilterShow: Dispatch>; selectedOptions: OptionType[]; selectOptions: OptionType[]; handleMultiselectOnChange: ( @@ -440,6 +454,7 @@ export const MapHeaderButtons = ({ ) => void; itemState: (TItemSelection | TEmptyItem)[]; handleChangedLocation: (itemEntry: (TItemSelection | TEmptyItem)[]) => void; + onFilterOpen: () => void; }) => { return ( - setFilterShow(true)} height="44px" /> + );