diff --git a/src/components/controls/panelChevron.tsx b/src/components/controls/panelChevron.tsx
new file mode 100644
index 000000000..520a99fce
--- /dev/null
+++ b/src/components/controls/panelChevron.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+import styled from 'styled-components';
+import { FaChevronRight, FaChevronDown } from "react-icons/fa";
+
+const Container = styled.span`
+ padding-right: 6px;
+ color: ${(props) => props.theme.color};
+`
+
+type Props = {
+ show: boolean
+}
+
+/**
+ * An interactive chevron to show/hide a panel's options.
+ */
+export const PanelChevron = ({ show }: Props) => {
+ const icon = show ? :
+
+ return (
+
+ {icon}
+
+ )
+}
diff --git a/src/components/controls/panelHeader.tsx b/src/components/controls/panelHeader.tsx
index d156269b8..9d16c4ed5 100644
--- a/src/components/controls/panelHeader.tsx
+++ b/src/components/controls/panelHeader.tsx
@@ -1,9 +1,10 @@
import React from "react";
import { useAppDispatch } from "../../hooks";
import { togglePanelDisplay } from "../../actions/panelDisplay";
-import { HeaderContainer } from "./styles";
+import { PanelSectionHeaderContainer } from "./styles";
import Toggle from "./toggle";
import { AnnotatedTitle, Title, Tooltip } from "./annotatedTitle";
+import { PanelChevron } from "./panelChevron";
/** Panel identifier used internally. */
export type PanelId = string;
@@ -15,28 +16,46 @@ type Props = {
/** Indicates panel visibility. */
panelIsVisible: boolean
+
+ /** Indicates whether there are options for the panel. */
+ hasOptions: boolean
+
+ /** Indicates options visibility. */
+ optionsAreVisible: boolean
+
+ /** Update options visibility. */
+ setOptionsAreVisible: React.Dispatch>
}
/**
* A header used by all panel controls, containing an interactive title.
*/
-export const PanelHeader = ({ panel, title, tooltip, panelIsVisible }: Props) => {
+export const PanelHeader = ({ panel, title, tooltip, panelIsVisible, hasOptions, optionsAreVisible, setOptionsAreVisible }: Props) => {
const dispatch = useAppDispatch();
function togglePanelVisibility() {
dispatch(togglePanelDisplay(panel))
}
+ function toggleOptionsVisibility() {
+ setOptionsAreVisible(!optionsAreVisible);
+ }
+
return (
-
-
+
+
+ {hasOptions &&
+ }
+
+
-
+
);
};
diff --git a/src/components/controls/panelSection.tsx b/src/components/controls/panelSection.tsx
index 7dc6594b8..de22a67a9 100644
--- a/src/components/controls/panelSection.tsx
+++ b/src/components/controls/panelSection.tsx
@@ -23,6 +23,14 @@ export const PanelSection = ({ panel, title, tooltip, options=undefined }: Props
const panelIsVisible = panelsToDisplay.includes(panel)
+ // Initially, panel visibility determines options visibility.
+ const [optionsAreVisible, setOptionsAreVisible] = React.useState(panelIsVisible);
+
+ // Subsequent panel visibility updates also determines options visibility.
+ React.useEffect(() => {
+ setOptionsAreVisible(panelIsVisible)
+ }, [panelIsVisible])
+
return (
- {panelIsVisible && options}
+ {optionsAreVisible && options}
);
};
diff --git a/src/components/controls/styles.js b/src/components/controls/styles.js
index 668adba3e..26137e4da 100644
--- a/src/components/controls/styles.js
+++ b/src/components/controls/styles.js
@@ -30,6 +30,10 @@ export const HeaderContainer = styled.div`
margin-bottom: 5px;
`;
+export const PanelSectionHeaderContainer = styled(HeaderContainer)`
+ cursor: pointer;
+`
+
export const PanelSectionContainer = styled.div`
// Less padding is necessary on the top because there is already some space
// from HeaderContainer's top margin.
@@ -48,7 +52,7 @@ export const PanelSectionContainer = styled.div`
`;
export const TitleAndIconContainer = styled.span`
- display: flex;
+ display: inline-flex;
align-items: center;
`;