pages/ValuePartnership/GlobePage/GlobeBottomNav.jsxjavascript
import PropTypes from "prop-types";
import { UIMCControlButton } from "@ui-marcom/react/control-button";
import { UIMCPagination } from "@ui-marcom/react/pagination";
import { UIMCPrimaryNavigation } from "@ui-marcom/react/primary-navigation";
// ui-marcom-pagination's own "compact" template renders a <ui-marcom-dropdown>
// for the page counter but never imports @ui-marcom/dropdown itself (likely a
// packaging oversight) - register it ourselves or the dropdown just sits
// inert/undefined.
import "@ui-marcom/dropdown";
import "./GlobeBottomNav.scss";
/**
* @typedef {Object} GlobeLocation
* @property {string|number} id
* @property {string} [customer]
* @property {string} [country]
*/
/**
* Fixed bar for stepping through the currently visible globe markers without
* having to hunt for pins on the globe - Home/Prev/Next + a "N of total"
* jump-to-index select, plus the previous/current/next marker by name. Only
* rendered while a marker is selected (preview or full details open), at the
* same screen position as {@link ValuePartnershipNav}'s own bottom bar - it
* overlays/replaces that bar for the duration of the selection rather than
* stacking alongside it.
*
* @param {Object} props
* @param {Array<GlobeLocation>} props.locations
* - Currently filtered/visible points, in display order.
* @param {string|number|null} props.selectedId
* @param {Object} props.labels - `content.valuePartnership.labels`.
* @param {function(Object): void} props.onSelect - Called with the newly selected location.
* @param {function(): void} props.onHome - Clears the selection (closes the detail card).
*/
export const GlobeBottomNav = ({ locations, selectedId, labels, onSelect, onHome }) => {
if (!selectedId || locations.length === 0) return null;
const selectedIndex = locations.findIndex((l) => l.id === selectedId);
if (selectedIndex === -1) return null;
const total = locations.length;
const prevLocation = selectedIndex > 0 ? locations[selectedIndex - 1] : null;
const currentLocation = locations[selectedIndex];
const nextLocation = selectedIndex < total - 1 ? locations[selectedIndex + 1] : null;
const hospitalLabel = (loc) => loc.customer || loc.country;
return (
<div className="globe-bottom-nav">
<div className="globe-bottom-nav__controls">
<UIMCControlButton
className="globe-bottom-nav__controls--home"
icon="home"
title={labels.backToOverview}
onClick={onHome}
/>
<UIMCPagination
className="globe-bottom-nav__pagination"
variant="compact"
totalItems={total}
itemsPerPage={1}
currentPage={selectedIndex + 1}
i18n={{
prev: labels.previous,
next: labels.next,
prevLabel: labels.previous,
nextLabel: labels.next,
separatorLabel: "of",
}}
onChange={(page) => onSelect(locations[page - 1])}
/>
</div>
<div className="globe-bottom-nav__hospitals">
<UIMCPrimaryNavigation>
{[prevLocation, currentLocation, nextLocation].map(
(loc) =>
loc && (
<button
key={loc.id}
type="button"
// Deliberately NOT named "is-active": that exact class name
// is what ui-marcom-primary-navigation's own
// ::slotted(*.is-active) rule (!important, unbeatable from
// here - see GlobeBottomNav.scss) hooks to render its
// orange underline, which we don't want. "is-current"
// drives only our own styling below.
className={
loc.id === selectedId
? "globe-bottom-nav__hospital is-current"
: "globe-bottom-nav__hospital"
}
title={hospitalLabel(loc)}
onClick={(e) => {
// ui-marcom-primary-navigation's ::slotted(*:focus) rule
// forces a visible focus ring (border+outline,
// !important) that a light-DOM style can't override -
// blur right after activating so the ring doesn't stick
// around on the newly active tab.
e.currentTarget.blur();
onSelect(loc);
}}>
{/* text color lives on this inner span instead - the
::slotted(*) rule above can't reach descendants, so
this sidesteps its !important color entirely (see
GlobeBottomNav.scss). */}
<span
className={
loc.id === selectedId
? "globe-bottom-nav__hospital-inner is-current"
: "globe-bottom-nav__hospital-inner"
}>
<ui-marcom-icon icon="hospital"></ui-marcom-icon>
{hospitalLabel(loc)}
</span>
</button>
)
)}
</UIMCPrimaryNavigation>
</div>
</div>
);
};
GlobeBottomNav.propTypes = {
locations: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
customer: PropTypes.string,
country: PropTypes.string,
})
).isRequired,
selectedId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
labels: PropTypes.object.isRequired,
onSelect: PropTypes.func.isRequired,
onHome: PropTypes.func.isRequired,
};