pages/ValuePartnership/GlobePage/ValueCheckboxPanel.jsxjavascript
import { useState } from "react";
import PropTypes from "prop-types";
import { UIMCButton } from "@ui-marcom/react/button";
import { UIMCIcon } from "@ui-marcom/react/icon";
import { UIMCControlButton } from "@ui-marcom/react/control-button";
import { UIMCSearchField } from "@ui-marcom/react/search-field";
import { UIMCToggleSwitch } from "@ui-marcom/react/toggle-switch";
import { UIMCAccordion } from "@ui-marcom/react/accordion";
import { UIMCCollapsible } from "@ui-marcom/react/collapsible";
import { UIMCCheckbox } from "@ui-marcom/react/checkbox";
import { useAnalytics } from "../../../services/analytics/analytics";
import { useScreensaver } from "../../../services/screensaver";
import { MARKER_ICONS } from "../../../services/config/globeDefaults";
import "./ValueCheckboxPanel.scss";
/**
* Slide-in filter panel for the globe's markers, ported from the old
* value-checkbox-panel Lit component. Controlled component for filtering:
* `filters` is owned by GlobePage (see its isPointVisible-equivalent
* useMemo), this just renders it and reports changes via `onChange`. The
* open/collapsed state of the drawer itself is local UI state - it doesn't
* affect which markers are visible, so it has no reason to live in GlobePage.
*
* @param {Object} props
* @param {Object} props.filters - `{ search, impactCasesActive, outcomeCasesActive, checkedImpactCases, checkedOutcomeCases, checkedStrategyCases }`.
* @param {function(Object): void} props.onChange - Called with the next filters object.
*/
export const ValueCheckboxPanel = ({ filters, onChange }) => {
const [isOpen, setIsOpen] = useState(true);
const { trackEvent } = useAnalytics();
const { showNow } = useScreensaver();
const {
search,
impactCasesActive,
outcomeCasesActive,
checkedImpactCases,
checkedOutcomeCases,
checkedStrategyCases,
} = filters;
const toggleActive = (key) => onChange({ ...filters, [key]: !filters[key] });
const toggleOutcomeGroup = (groupIndex) => {
const allChecked = checkedOutcomeCases[groupIndex].items.every((item) => item.checked);
const nextGroups = checkedOutcomeCases.map((group, idx) =>
idx === groupIndex
? { ...group, items: group.items.map((item) => ({ ...item, checked: !allChecked })) }
: group
);
onChange({ ...filters, checkedOutcomeCases: nextGroups });
};
const toggleOutcomeItem = (groupIndex, itemIndex) => {
const nextGroups = checkedOutcomeCases.map((group, idx) =>
idx === groupIndex
? {
...group,
items: group.items.map((item, i) =>
i === itemIndex ? { ...item, checked: !item.checked } : item
),
}
: group
);
onChange({ ...filters, checkedOutcomeCases: nextGroups });
};
const toggleOutcomeSelectAll = () => {
const allChecked = checkedOutcomeCases.every((group) =>
group.items.every((item) => item.checked)
);
const nextGroups = checkedOutcomeCases.map((group) => ({
...group,
items: group.items.map((item) => ({ ...item, checked: !allChecked })),
}));
onChange({ ...filters, checkedOutcomeCases: nextGroups });
};
const toggleImpactItem = (index) => {
const nextItems = checkedImpactCases.map((item, i) =>
i === index ? { ...item, checked: !item.checked } : item
);
onChange({ ...filters, checkedImpactCases: nextItems });
};
const toggleStrategyItem = (index) => {
const nextItems = checkedStrategyCases.map((item, i) =>
i === index ? { ...item, checked: !item.checked } : item
);
onChange({ ...filters, checkedStrategyCases: nextItems });
};
const toggleStrategySelectAll = () => {
const allChecked = checkedStrategyCases.every((item) => item.checked);
onChange({
...filters,
checkedStrategyCases: checkedStrategyCases.map((item) => ({ ...item, checked: !allChecked })),
});
};
const toggleImpactSelectAll = () => {
const allChecked = checkedImpactCases.every((item) => item.checked);
onChange({
...filters,
checkedImpactCases: checkedImpactCases.map((item) => ({ ...item, checked: !allChecked })),
});
};
return (
<div className={`value-checkbox-panel${isOpen ? "" : " value-checkbox-panel--collapsed"}`}>
<UIMCControlButton
className="value-checkbox-panel__toggle"
icon={isOpen ? "arrow-left-large" : "arrow-right-large"}
title={isOpen ? "Collapse filters" : "Expand filters"}
onClick={() => setIsOpen((open) => !open)}
/>
<div className="value-checkbox-panel__scroll">
<h2>Browse our cases</h2>
<UIMCSearchField
className="value-checkbox-panel__search"
placeholder="Open search"
size="large"
value={search}
onInput={(e) => onChange({ ...filters, search: e.target.value })}
/>
<div className="value-checkbox-panel__case-filter">
<UIMCToggleSwitch
isChecked={outcomeCasesActive}
onChange={() => toggleActive("outcomeCasesActive")}
/>
<span>Outcome Cases</span>
<img
className="value-checkbox-panel__case-filter-pin"
src={MARKER_ICONS.outcome}
alt=""
/>
</div>
<div className="value-checkbox-panel__case-filter">
<UIMCToggleSwitch
isChecked={impactCasesActive}
onChange={() => toggleActive("impactCasesActive")}
/>
<span>Impact Cases</span>
<img className="value-checkbox-panel__case-filter-pin" src={MARKER_ICONS.impact} alt="" />
</div>
<UIMCAccordion independent>
<UIMCCollapsible open={true} className="value-checkbox-panel__collapsible">
<span slot="headline">Filter Outcome Cases by Strategy</span>
<UIMCButton
size="small"
variant="standard"
className="value-checkbox-panel__button"
onClick={toggleStrategySelectAll}>
{checkedStrategyCases.every((item) => item.checked) ? "Deselect all" : "Select all"}
</UIMCButton>
<div className="value-checkbox-panel__checkboxes">
{checkedStrategyCases.map((item, index) => (
<div key={item.name} className="value-checkbox-panel__group">
<UIMCCheckbox
className="value-checkbox-panel__checkbox-item"
size="medium"
key={item.value}
checked={item.checked}
onChange={() => toggleStrategyItem(index)}>
{item.name}
</UIMCCheckbox>
</div>
))}
</div>
</UIMCCollapsible>
<UIMCCollapsible open={outcomeCasesActive} className="value-checkbox-panel__collapsible">
<span slot="headline">Filter Outcome Cases by Drivers</span>
<UIMCButton
size="small"
variant="standard"
className="value-checkbox-panel__button"
onClick={toggleOutcomeSelectAll}>
{checkedOutcomeCases.every((g) => g.items.every((i) => i.checked))
? "Deselect all"
: "Select all"}
</UIMCButton>
<div className="value-checkbox-panel__checkboxes">
{checkedOutcomeCases.map((group, groupIndex) => (
<div key={group.name} className="value-checkbox-panel__group">
<UIMCCheckbox
className="value-checkbox-panel__checkbox-item"
checked={group.items.every((item) => item.checked)}
onChange={() => toggleOutcomeGroup(groupIndex)}>
{group.name}
</UIMCCheckbox>
<div className="value-checkbox-panel__subitems">
{group.items.map((item, itemIndex) => (
<UIMCCheckbox
className="value-checkbox-panel__checkbox-item"
key={item.value}
checked={item.checked}
onChange={() => toggleOutcomeItem(groupIndex, itemIndex)}>
{item.name}
</UIMCCheckbox>
))}
</div>
</div>
))}
</div>
</UIMCCollapsible>
<UIMCCollapsible open={impactCasesActive} className="value-checkbox-panel__collapsible">
<span slot="headline">Filter Impact Cases</span>
<UIMCButton
size="small"
variant="standard"
className="value-checkbox-panel__button"
onClick={toggleImpactSelectAll}>
{checkedImpactCases.every((i) => i.checked) ? "Deselect all" : "Select all"}
</UIMCButton>
<div className="value-checkbox-panel__checkboxes">
{checkedImpactCases.map((item, index) => (
<UIMCCheckbox
className="value-checkbox-panel__checkbox-item"
key={item.value}
checked={item.checked}
onChange={() => toggleImpactItem(index)}>
{item.name}
</UIMCCheckbox>
))}
</div>
</UIMCCollapsible>
</UIMCAccordion>
</div>
<div className="value-checkbox-panel__footer">
<UIMCButton
className="value-checkbox-panel__footer-button"
variant="standard"
title="Screensaver"
onClick={() => {
trackEvent?.("navigation_click", { element: "Screensaver" });
showNow();
}}>
<UIMCIcon slot="prefix" icon="play" />
Screensaver
</UIMCButton>
</div>
</div>
);
};
ValueCheckboxPanel.propTypes = {
filters: PropTypes.shape({
search: PropTypes.string.isRequired,
impactCasesActive: PropTypes.bool.isRequired,
outcomeCasesActive: PropTypes.bool.isRequired,
checkedImpactCases: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
checked: PropTypes.bool.isRequired,
})
).isRequired,
checkedOutcomeCases: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
items: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
checked: PropTypes.bool.isRequired,
})
).isRequired,
})
).isRequired,
}).isRequired,
onChange: PropTypes.func.isRequired,
};