diff --git a/app/plant-page/my-garden/[userPlantId]/page.tsx b/app/plant-page/my-garden/[userPlantId]/page.tsx index e26c845..d02f676 100644 --- a/app/plant-page/my-garden/[userPlantId]/page.tsx +++ b/app/plant-page/my-garden/[userPlantId]/page.tsx @@ -114,7 +114,6 @@ export default function UserPlantPage() {

Planting Timeline

- {/*add SeasonalColorKey here */} [] >([]); - const [selectedUsState, setSelectedUsState] = useState(''); + const [selectedUsState, setSelectedUsState] = + useState | null>(null); const [searchTerm, setSearchTerm] = useState(''); const clearFilters = () => { @@ -50,7 +54,10 @@ export default function SeasonalPlantingGuide() { useEffect(() => { if (profileReady && profileData) { - setSelectedUsState(profileData.us_state); + setSelectedUsState({ + label: toTitleCase(profileData.us_state), + value: profileData.us_state, + }); } }, [profileData, profileReady]); @@ -65,14 +72,17 @@ export default function SeasonalPlantingGuide() { + {/* vertical bar to separate state and other filters */} + + - - + + Clear Filters + {!selectedUsState ? (

Choose Your State

) : ( - + (''); const [selectedPlants, setSelectedPlants] = useState([]); const [ownedPlants, setOwnedPlants] = useState([]); + const [isCardKeyOpen, setIsCardKeyOpen] = useState(false); + const cardKeyRef = useRef(null); + const infoButtonRef = useRef(null); const userState = profileData?.us_state ?? null; const profileAndAuthReady = profileReady && !authLoading; @@ -378,12 +383,52 @@ export default function Page() { const plantPluralityString = selectedPlants.length > 1 ? 'Plants' : 'Plant'; + // close plant card key when clicking outside, even on info button + const handleClickOutside = (event: MouseEvent) => { + if ( + cardKeyRef.current && + !cardKeyRef.current.contains(event.target as Node) && + infoButtonRef.current && + !infoButtonRef.current.contains(event.target as Node) + ) { + setIsCardKeyOpen(false); + } + }; + + // handle clicking outside PlantCardKey to close it if open + useEffect(() => { + if (isCardKeyOpen) { + document.addEventListener('mousedown', handleClickOutside); + } else { + document.removeEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isCardKeyOpen]); + return (
-

- View Plants -

+ +

+ View Plants +

+
+ setIsCardKeyOpen(!isCardKeyOpen)} + ref={infoButtonRef} + > + + + {isCardKeyOpen && ( +
+ +
+ )} +
+
-// >(({ children, ...props }, ref) => { -// return ( -// -// ); -// }); -// Button.displayName = 'Button'; - -interface SmallRoundedButtonProps { - $primaryColor?: string; - $secondaryColor: string; -} - -export const SmallRoundedButton = styled.button` - font-family: inherit; - padding: 10px 20px; - border-radius: 15px; - box-shadow: 1px 1px 1px 0px rgba(0, 0, 0, 0.05); - border: 0.5px solid ${({ $secondaryColor }) => $secondaryColor}; - background-color: ${({ $primaryColor }) => - $primaryColor ? $primaryColor : 'white'}; - color: ${({ $primaryColor, $secondaryColor }) => - $primaryColor ? 'white' : $secondaryColor}; - font-size: 16px; - cursor: pointer; - transition: - background-color 0.3s ease, - border-color 0.3s ease; - - &:hover { - background-color: ${({ $primaryColor, $secondaryColor }) => - $primaryColor ? $primaryColor : $secondaryColor}; - color: ${({ $primaryColor, $secondaryColor }) => - $primaryColor ? $secondaryColor : 'white'}; - border-color: ${({ $secondaryColor }) => $secondaryColor}; - } -`; diff --git a/components/Buttons.tsx b/components/Buttons.tsx index 46f37fc..aa07141 100644 --- a/components/Buttons.tsx +++ b/components/Buttons.tsx @@ -79,6 +79,6 @@ export const SmallButton = styled(P3).attrs({ as: 'button' })` // Unique to Small Button border-radius: 20px; min-width: 60px; - height: 24px; + min-height: 24px; // to prevent Clear Filters text overflow padding: 4px 10px; `; diff --git a/components/FilterDropdownMultiple/index.tsx b/components/FilterDropdownMultiple/index.tsx index e0077c3..f6f6e66 100644 --- a/components/FilterDropdownMultiple/index.tsx +++ b/components/FilterDropdownMultiple/index.tsx @@ -1,6 +1,14 @@ import React from 'react'; +import Select, { + components, + GroupBase, + MultiValue, + MultiValueProps, + OptionProps, +} from 'react-select'; +import { P3 } from '@/styles/text'; import { DropdownOption } from '@/types/schema'; -import { StyledMultiSelect } from './styles'; +import { customSelectStyles } from './styles'; interface FilterDropdownProps { value: DropdownOption[]; @@ -17,16 +25,79 @@ export default function FilterDropdownMultiple({ placeholder, disabled = false, }: FilterDropdownProps) { + const handleChange = (selectedOptions: MultiValue>) => { + setStateAction(selectedOptions as DropdownOption[]); + }; + + // overrides the default MultiValue to display custom text + // displays first selected value followed by + n if more than 1 selected + // CustomMultiValue appears for each selected option, so if more than 1 is selected, + // the rest of the selected options are not shown, instead the + n is shown as part of the first option + const CustomMultiValue = ({ + ...props + }: MultiValueProps< + DropdownOption, + true, + GroupBase> + >) => { + const { selectProps, data, index } = props; + if (Array.isArray(selectProps.value)) { + const isFirst = index === 0; + // find number of remaining selected options + const additionalCount = selectProps.value.length - 1; + + return ( + + {/* display label of first selected option */} + {isFirst ? ( + <> + {data.label} + {/* display additional count only if more than one option is selected*/} + {additionalCount > 0 && ` +${additionalCount}`} + + ) : // don't display anything if not the first selected option + null} + + ); + } + + // nothing is selected yet + return null; + }; + + // overrides the default Options to display a checkbox that ticks when option selected + const CustomOption = ( + props: OptionProps, true, GroupBase>>, + ) => { + return ( + + null} //no-op + style={{ marginRight: 8 }} // spacing between checkbox and text + /> + {props.label} + + ); + }; + return ( - ()} + isSearchable={false} + hideSelectedOptions={false} + // use custom styled components instead of default components + components={{ MultiValue: CustomMultiValue, Option: CustomOption }} + menuPosition="fixed" + instanceId="dropdown-multiple" /> ); } diff --git a/components/FilterDropdownMultiple/styles.ts b/components/FilterDropdownMultiple/styles.ts index 82284ae..edd2cb2 100644 --- a/components/FilterDropdownMultiple/styles.ts +++ b/components/FilterDropdownMultiple/styles.ts @@ -1,24 +1,80 @@ -import { MultiSelect } from 'react-multi-select-component'; -import styled from 'styled-components'; +import { StylesConfig } from 'react-select'; +import COLORS from '@/styles/colors'; +import { DropdownOption } from '@/types/schema'; -export const StyledMultiSelect = styled(MultiSelect)` - .dropdown-container { - border-radius: 60px; !important - padding: 8px 14px 8px 14px; !important - align-items: center; - justify-content: center; - justify-items: center; - gap: 2px; - background-color: #1f5a2a; !important - border: 0.5px solid #888; !important - color: #fff; - position: relative; - } - - .dropdown-content { - display: block; !important - position: absolute; !important - z-index: 10000; !important - top: 100%; - } -`; +// custom styles for react-select component +// Option type is DropdownOption and isMulti is true +export const customSelectStyles = (): StylesConfig< + DropdownOption, + true +> => ({ + // container + control: (baseStyles, state) => ({ + ...baseStyles, + borderRadius: '56px', + height: '30px', + border: `0.5px solid ${COLORS.midgray}`, + backgroundColor: state.isDisabled ? COLORS.lightgray : '#fff', + padding: '8px 14px', + color: COLORS.midgray, + minWidth: '138px', + width: 'max-content', // prevent collapse on scroll + }), + // placeholder text + placeholder: baseStyles => ({ + ...baseStyles, + color: COLORS.midgray, + fontSize: '0.75rem', + fontWeight: 400, + padding: '0px', + margin: 'auto', + }), + // hide vertical bar between arrow and text + indicatorSeparator: baseStyles => ({ + ...baseStyles, + display: 'none', + }), + // 'x' to clear selected option(s) + clearIndicator: baseStyles => ({ + ...baseStyles, + padding: '0px', + }), + // dropdown arrow + dropdownIndicator: baseStyles => ({ + ...baseStyles, + padding: '0px', + marginLeft: '-4px', // move the dropdown indicator to the left, cant override text styles + color: COLORS.midgray, + }), + menu: baseStyles => ({ + ...baseStyles, + minWidth: 'max-content', + }), + // container for selected multi option + multiValue: baseStyles => ({ + ...baseStyles, + backgroundColor: '#fff', + border: '0px', + padding: '0px', + margin: '0px', + }), + // The following styles aren't used (see CustomMultiValue) + // multi option display text + // multiValueLabel: baseStyles => ({ + // ...baseStyles, + // fontSize: '0.75rem', + // color: `${COLORS.black}`, + // padding: '0px', + // }), + // // hide 'x' next to each multi option + // multiValueRemove: baseStyles => ({ + // ...baseStyles, + // display: 'none', + // }), + option: baseStyles => ({ + ...baseStyles, + // style as a P3 with fontWeight 400 + fontSize: '0.75rem', + fontWeight: 400, + }), +}); diff --git a/components/FilterDropdownSingle/index.tsx b/components/FilterDropdownSingle/index.tsx index 261fecb..88648c0 100644 --- a/components/FilterDropdownSingle/index.tsx +++ b/components/FilterDropdownSingle/index.tsx @@ -1,57 +1,52 @@ -import React, { useState } from 'react'; +import React from 'react'; +import Select, { MultiValue, SingleValue } from 'react-select'; import { DropdownOption } from '@/types/schema'; -import { FilterDropdownInput } from './styles'; +import { customSelectStyles } from './styles'; -interface FilterDropdownProps { - name?: string; - id?: string; - value: string; - setStateAction: React.Dispatch>; - options: DropdownOption[]; +interface FilterDropdownProps { + value: DropdownOption | null; + setStateAction: React.Dispatch< + React.SetStateAction | null> + >; + options: DropdownOption[]; placeholder: string; disabled?: boolean; + // for custom styling since initial dropdown to select user's state + // is a different size to a normal single dropdown + small?: boolean; } -export default function FilterDropdownSingle({ - name, - id, +export default function FilterDropdownSingle({ value, setStateAction, options, placeholder, disabled, -}: FilterDropdownProps) { - const [isOpen, setIsOpen] = useState(false); - - const handleChange = (event: React.ChangeEvent) => { - setStateAction(event.target.value); - setIsOpen(false); - }; - - const handleToggle = () => { - setIsOpen(!isOpen); + small = false, +}: FilterDropdownProps) { + const handleChange = ( + selectedOptions: + | SingleValue> + | MultiValue>, + ) => { + if (!Array.isArray(selectedOptions)) { + setStateAction(selectedOptions as DropdownOption); + } }; return ( - setIsOpen(false)} +