a3s-flow 1.1.0

Durable workflow engine and Rust SDK for A3S
Documentation
import { useLocation, useSite, useVersion } from '@rspress/core/runtime';
import {
  HomeLayout as OriginalHomeLayout,
  Layout as OriginalLayout,
  type LayoutProps,
} from '@rspress/core/theme-original';
import { useEffect } from 'react';
import { HomeLayout as FlowHomeLayout } from './HomeLayout';

const FOCUSABLE_SELECTOR = [
  'a[href]',
  'button:not([disabled])',
  'input:not([disabled])',
  'select:not([disabled])',
  'textarea:not([disabled])',
  '[contenteditable="true"]',
  '[tabindex]',
].join(',');

const PREVIOUS_TABINDEX = 'data-flow-previous-tabindex';
const NO_TABINDEX = '__none__';

function setSidebarAvailable(sidebar: HTMLElement, available: boolean) {
  sidebar.toggleAttribute('inert', !available);
  if (available) {
    sidebar.removeAttribute('aria-hidden');
  } else {
    sidebar.setAttribute('aria-hidden', 'true');
  }

  sidebar
    .querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)
    .forEach((element) => {
      if (!available) {
        if (!element.hasAttribute(PREVIOUS_TABINDEX)) {
          element.setAttribute(
            PREVIOUS_TABINDEX,
            element.getAttribute('tabindex') ?? NO_TABINDEX,
          );
        }
        element.tabIndex = -1;
        return;
      }

      const previous = element.getAttribute(PREVIOUS_TABINDEX);
      if (previous === null) return;
      if (previous === NO_TABINDEX) {
        element.removeAttribute('tabindex');
      } else {
        element.setAttribute('tabindex', previous);
      }
      element.removeAttribute(PREVIOUS_TABINDEX);
    });
}

function useAccessibleMobileSidebar() {
  const { pathname } = useLocation();

  useEffect(() => {
    const mobile = window.matchMedia('(max-width: 768px)');
    const sync = () => {
      document
        .querySelectorAll<HTMLElement>('.rp-doc-layout__sidebar')
        .forEach((sidebar) => {
          const open = sidebar.classList.contains(
            'rp-doc-layout__sidebar--open',
          );
          setSidebarAvailable(sidebar, !mobile.matches || open);
        });
    };

    const observer = new MutationObserver(sync);
    observer.observe(document.body, {
      attributeFilter: ['class'],
      attributes: true,
      childList: true,
      subtree: true,
    });
    mobile.addEventListener('change', sync);
    sync();

    return () => {
      observer.disconnect();
      mobile.removeEventListener('change', sync);
      document
        .querySelectorAll<HTMLElement>('.rp-doc-layout__sidebar')
        .forEach((sidebar) => setSidebarAvailable(sidebar, true));
    };
  }, [pathname]);
}

export function Layout(props: LayoutProps) {
  useAccessibleMobileSidebar();
  const { site } = useSite();
  const version = useVersion();
  const defaultVersion = site.multiVersion.default ?? version;
  const HomeLayout =
    version === defaultVersion ? FlowHomeLayout : OriginalHomeLayout;
  return <OriginalLayout {...props} HomeLayout={HomeLayout} />;
}