diff --git a/src/components/App.tsx b/src/components/App.tsx index 655b531..27473bd 100755 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -20,7 +20,7 @@ const kPluginName = "NOAA Weather Station Data"; const kVersion = "0014"; const kInitialDimensions = { width: 360, - height: 495 + height: 650 }; export const App = () => { diff --git a/src/components/attribute-filter.scss b/src/components/attribute-filter.scss index c60dd2a..4b2d083 100644 --- a/src/components/attribute-filter.scss +++ b/src/components/attribute-filter.scss @@ -208,20 +208,24 @@ table tr:nth-child(even) { } .filter-operator-selection-container { - width: 148px; - height: 190px; - padding: 9px; + width: 165px; + max-height: 190px; + padding: 9px 12px 5px 9px; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.5); background-color: #fff; position: relative; - right: -22px; + right: -5px; top: -50px; box-sizing: border-box; + overflow-y: auto; + overflow-x: hidden; .operator-selection { border: none; width: 100%; + height: 176px; outline: none; + box-sizing: border-box; option { font-family: "Montserrat", sans-serif; @@ -229,6 +233,8 @@ table tr:nth-child(even) { font-weight: 500; color: #000; padding: 2px 0; + height: 12px; + &:hover { background-color: $teal-medium; } diff --git a/src/components/attribute-filter.tsx b/src/components/attribute-filter.tsx index 303c445..f63002c 100644 --- a/src/components/attribute-filter.tsx +++ b/src/components/attribute-filter.tsx @@ -23,6 +23,7 @@ export const AttributeFilter = () => { const attrMap = selectedFrequency === "hourly" ? hourlyAttrMap : dailyMonthlyAttrMap; const [hasFilter, setHasFilter] = useState(false); const [filteringIndex, setFilteringIndex] = useState(undefined); + const [targetFilterBottom, setTargetFilterBottom] = useState(0); const [filterModalPosition, setFilterModalPosition] = useState({ top: 0 }); const [showFilterModal, setShowFilterModal] = useState(false); const [attributeToFilter, setAttributeToFilter] = useState(undefined); @@ -44,7 +45,6 @@ export const AttributeFilter = () => { }); if (hasFilters) { - setHasFilter(true); } },[frequencies, selectedAttrMap, selectedFrequency]); @@ -53,17 +53,18 @@ export const AttributeFilter = () => { const rect = e.currentTarget.getBoundingClientRect(); const top = rect.bottom + window.scrollY; + setTargetFilterBottom(rect.bottom); setFilterModalPosition({top}); setShowFilterModal(true); setAttributeToFilter(frequencies[selectedFrequency].attrs[index]); setFilteringIndex(index); }; - const handleUnitsToggle = () => { - setState(draft => { - draft.units = draft.units === "standard" ? "metric" : "standard"; - }); - }; + const handleUnitsToggle = () => { + setState(draft => { + draft.units = draft.units === "standard" ? "metric" : "standard"; + }); + }; if (selectedAttrMap && selectedAttrMap.length > 0) { return ( @@ -88,7 +89,7 @@ export const AttributeFilter = () => { : filterAboveOrBelowMean ? `${operatorTextMap[attrFilter.operator]}` : attrFilter.operator === "between" - ? `${attrFilter.lowerValue} - ${attrFilter.upperValue} ${attr.unit[units]}` + ? `${attrFilter.lowerValue} ${attr.unit[units]} - ${attrFilter.upperValue} ${attr.unit[units]}` : attrFilter.operator === "top" || attrFilter.operator === "bottom" ? `${operatorTextMap[attrFilter.operator]} ${attrFilter.value}` :`${operatorSymbolMap[attrFilter.operator]} ${attrFilter.value} ${attr.unit[units]}`; @@ -115,7 +116,10 @@ export const AttributeFilter = () => { {(attributeToFilter && showFilterModal) && - } + } ); } else { @@ -126,10 +130,12 @@ export const AttributeFilter = () => { interface IFilterModalProps { attr: AttrType; position: {top: number}; + targetFilterBottom?: number; setShowFilterModal: (show: boolean) => void + setFilterModalPosition: (position: {top: number}) => void; } -const FilterModal = ({attr, position, setShowFilterModal}: IFilterModalProps) => { +const FilterModal = ({attr, position, targetFilterBottom, setShowFilterModal, setFilterModalPosition}: IFilterModalProps) => { const {state, setState} = useStateContext(); const {frequencies, units, selectedFrequency} = state; const currentAttr = frequencies[selectedFrequency].attrs.find(a => a.name === attr.name); @@ -148,23 +154,56 @@ const FilterModal = ({attr, position, setShowFilterModal}: IFilterModalProps) => const filterLowerValueInputElRef = useRef(null); const filterUpperValueInputElRef = useRef(null); const operatorSelectionModalRef = useRef(null); + const [operatorSelectionListHeight, setOperatorSelectionListHeight] = useState({height: 190}); + const [windowHeight, setWindowHeight] = useState(window.innerHeight); + const dropdownRect = operatorSelectionModalRef.current?.getBoundingClientRect(); + const dropdownBottom = dropdownRect?.bottom; -useEffect(() => { - function handleClickOutside(event: MouseEvent) { - if (event.target) { - if (operatorSelectionModalRef.current && !operatorSelectionModalRef.current.contains(event.target as Node)) { - setShowOperatorSelectionModal(false); + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (event.target) { + if (operatorSelectionModalRef.current && !operatorSelectionModalRef.current.contains(event.target as Node)) { + setShowOperatorSelectionModal(false); + } } } - } + const handleResize = () => { + setWindowHeight(window.innerHeight); + }; - // Bind the event listener - document.addEventListener("mousedown", handleClickOutside); - return () => { - // Unbind the event listener on clean up - document.removeEventListener("mousedown", handleClickOutside); - }; -}, []); + window.addEventListener("resize", handleResize); + document.addEventListener("mousedown", handleClickOutside); + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + window.removeEventListener("resize", handleResize); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Move modal to bottom of window if window is too short + useEffect(() => { + const modalHeight = 89; + if (position.top + modalHeight > windowHeight) { + setFilterModalPosition({top: windowHeight - modalHeight}); + } else { + setFilterModalPosition({top: targetFilterBottom || 0}); + } + // Adding the other dependencies causes the modal to jump around + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [windowHeight]); + + // Change filter-operator-selection-container height if window is shorter than dropdown + useEffect(() => { + if (showOperatorSelectionModal) { + if (dropdownBottom && dropdownBottom > windowHeight) { + const cutOffAmount = dropdownBottom - windowHeight; + setOperatorSelectionListHeight({height: 190 - cutOffAmount - 3}); + } else { + setOperatorSelectionListHeight({height: 190}); + } + } + },[dropdownBottom, showOperatorSelectionModal, windowHeight]); const handleReset = () => { setOperator(currentAttrFilter?.operator || "equals"); @@ -237,7 +276,7 @@ useEffect(() => { // key attribute forces inputs to rerender when operator changes if (operator === "between") { return ( -
+
@@ -250,31 +289,36 @@ useEffect(() => { } else if (operator === "aboveMean" || operator === "belowMean") { return null; } else if (operator === "top" || operator === "bottom") { - return ; + return ; } else { - return ; + return ; } }; const handleChangeFilterOperator = (e: React.MouseEvent) => { + e.stopPropagation(); setShowOperatorSelectionModal(true); }; - const handleSelectFilterOperator = (newOperator: TOperators) => { + const handleSelectFilterOperator = (e: React.ChangeEvent) => { + const newOperator = e.currentTarget.value as TOperators; + e.stopPropagation(); setOperator(newOperator); setShowOperatorSelectionModal(false); }; + const wideModal = !["equals", "top", "bottom"].includes(operator); + return (
-
-
+
+
{operatorTextMap[operator] || "equals"}
- {!noValueFilter && renderFilterInputs() } + {renderFilterInputs()} {(operator === "top" || operator === "bottom") && {` results`}}
@@ -282,8 +326,8 @@ useEffect(() => {
{showOperatorSelectionModal && -
- diff --git a/src/types.ts b/src/types.ts index a0e02f2..59c5162 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,8 +10,8 @@ export interface AttrType { export type TOperators = "equals" | "doesNotEqual" | "greaterThan" | "greaterThanOrEqualTo" | "lessThan" | "lessThanOrEqualTo" | "between" | "top" | "bottom" | "aboveMean" | "belowMean"; -export const operatorTextMap = {equals: "equals", doesNotEqual: "does not equal", greaterThan: "greater than", greaterThanOrEqualTo: "great than or equal to", -lessThan: "less than", lessThanOrEqualTo: "less than or equal to", between: "between", top: "top", bottom: "bottom", +export const operatorTextMap = {equals: "equals", doesNotEqual: "does not equal", greaterThan: "is greater than", greaterThanOrEqualTo: "is greater than or equal to", +lessThan: "is less than", lessThanOrEqualTo: "is less than or equal to", between: "between", top: "top", bottom: "bottom", aboveMean: "above mean", belowMean: "below mean"}; export const operatorSymbolMap = {equals: "=", doesNotEqual: "≠", greaterThan: ">", greaterThanOrEqualTo: ">=", lessThan: "<", lessThanOrEqualTo: "<="};