components/SlidesOverlay/SlidesOverlay.jsxjavascript
import { useState } from "react";
import { UIMCButton } from "@ui-marcom/react/button";
import { UIMCToggleButtonSwitch } from "@ui-marcom/react/toggle-button-switch";
import { useContent } from "../../services/content";
import "./SlidesOverlay.scss";

const ALL_TAB_ID = "all";

const SlideThumb = ({ id, view, isCurrent, onSelect }) => {
  const blocks = view.elements || [];
  const label =
    blocks.find((block) => block.type === "headline")?.text ||
    blocks.find((block) => block.type === "storyTitle")?.title ||
    id;

  return (
    <button
      type="button"
      className={`slides-overlay__thumb${isCurrent ? " slides-overlay__thumb--current" : ""}`}
      aria-current={isCurrent || undefined}
      onClick={() => onSelect(id)}>
      {view.thumbnail ? (
        <img
          className="slides-overlay__thumb-image"
          src={view.thumbnail}
          alt={label}
          loading="lazy"
          decoding="async"
          width="270"
          height="480"
        />
      ) : (
        <span className="slides-overlay__thumb-label">{label}</span>
      )}
    </button>
  );
};

/**
 * Full-screen overlay listing every slide of the loaded content file as a
 * clickable thumbnail (from the view's optional `thumbnail` field, with a
 * text placeholder as fallback), grouped by story. A toggle switch selects
 * "All Slides" or a single story; it opens on the current view's story.
 * Rendered by SlideNavigation in its "slides" mode.
 *
 * @param {Object} props
 * @param {string} props.currentViewId - View to highlight and to derive the initial story tab from.
 * @param {function(string): void} props.onSelectSlide - Called with the clicked slide's view id.
 */
export const SlidesOverlay = ({ currentViewId, onSelectSlide }) => {
  const { views, stories } = useContent();
  const storyIds = Object.keys(stories);

  const [activeTabId, setActiveTabId] = useState(
    () => views.get(currentViewId)?.story || ALL_TAB_ID
  );

  // a story's views in authoring order: root slides and their sub views all
  // carry an explicit `story` field
  const getStoryViewIds = (storyId) =>
    [...views.entries()].filter(([, view]) => view.story === storyId).map(([viewId]) => viewId);

  const tabs = [
    { id: ALL_TAB_ID, label: "All Slides" },
    ...storyIds.map((id) => ({ id, label: stories[id].name })),
  ];
  const activeTabIndex = Math.max(
    tabs.findIndex(({ id }) => id === activeTabId),
    0
  );

  const renderGrid = (storyId) => (
    <div className="slides-overlay__grid">
      {getStoryViewIds(storyId).map((id) => (
        <SlideThumb
          key={id}
          id={id}
          view={views.get(id)}
          isCurrent={id === currentViewId}
          onSelect={onSelectSlide}
        />
      ))}
    </div>
  );

  const renderStorySwitch = () => (
    <div className="slides-overlay__switch">
      <UIMCToggleButtonSwitch
        size="small"
        current={activeTabIndex}
        onChange={(index) => setActiveTabId(tabs[index].id)}>
        {tabs.map(({ id, label }) => (
          <UIMCButton key={id}>{label}</UIMCButton>
        ))}
      </UIMCToggleButtonSwitch>
    </div>
  );

  // the nav bar (incl. the story switch, via renderBar) is a sibling of the
  // backdrop, not a child: the backdrop's backdrop-filter creates a stacking
  // context, so children could never render above the page's other fixed UI
  return (
    <>
      <div className="slides-overlay" role="dialog" aria-modal="true" aria-label="Slides">
        <div className="slides-overlay__content">
          {activeTabId === ALL_TAB_ID
            ? storyIds.map((storyId) => (
                <section key={storyId} className="slides-overlay__story-section">
                  <h3 className="slides-overlay__story-name">{stories[storyId].name}</h3>
                  {renderGrid(storyId)}
                </section>
              ))
            : renderGrid(activeTabId)}
          {renderStorySwitch()}
        </div>
      </div>
    </>
  );
};