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,
};