All files / src/components/Sidebar Sidebar.js

100% Statements 20/20
100% Branches 4/4
100% Functions 7/7
100% Lines 18/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84                        2x                 2x 7x 25x 25x   2x                   2x 17x 17x   17x 12x   7x 7x   12x 12x     17x                           2x               2x                      
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import {
  SidebarContainer,
  NameTitle,
  FooterMessage,
  LegalButton,
} from './SidebarStyles';
import SidebarMenu from './SidebarMenu';
import SidebarSocial from './SidebarSocial';
 
// Ordered bottom-to-top so the first match is always the deepest section currently in view
const SECTIONS = ['Legal', 'RepoDocs', 'Experience', 'Projects', 'Education'];
 
/**
 * Returns the id of the section currently most visible in the viewport.
 * Iterates from bottom to top so the deepest section the user has scrolled past wins.
 *
 * @param {number} scrollPosition - window.scrollY plus half the viewport height
 * @returns {string} Section id, defaulting to 'About' when scroll is near the top
 */
const getActiveSection = (scrollPosition) => {
  for (const id of SECTIONS) {
    const el = document.getElementById(id);
    if (el && scrollPosition >= el.offsetTop) return id;
  }
  return 'About';
};
 
/**
 * Fixed left-hand navigation sidebar.
 * Tracks the active section via scroll position and exposes language switching,
 * CV download, and links to German legal pages.
 *
 * @returns {JSX.Element}
 */
const Sidebar = () => {
  const { t } = useTranslation();
  const [activeSection, setActiveSection] = useState('About');
 
  useEffect(() => {
    const handleScroll = () => {
      // Midpoint of the viewport keeps the active item stable as the user scrolls through sections
      const scrollPosition = window.scrollY + window.innerHeight / 2;
      setActiveSection(getActiveSection(scrollPosition));
    };
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);
 
  return (
    <SidebarContainer>
      <NameTitle>
        <h1>{t('name')}</h1>
        <h2>{t('title')}</h2>
      </NameTitle>
      <SidebarMenu activeSection={activeSection} />
      <SidebarSocial />
      <FooterMessage>
        <p>{t('footerMessage')}</p>
        <div style={{ marginTop: '1rem', display: 'flex', gap: '0.75rem', justifyContent: 'center', flexWrap: 'wrap' }}>
          <LegalButton
            type="button"
            aria-label="Jump to Impressum section"
            onClick={() => document.getElementById('Impressum')?.scrollIntoView({ behavior: 'smooth' })}
          >
            {t('impressumLink')}
          </LegalButton>
          <span style={{ color: '#8892b0' }}>|</span>
          <LegalButton
            type="button"
            aria-label="Jump to privacy policy section"
            onClick={() => document.getElementById('Datenschutz')?.scrollIntoView({ behavior: 'smooth' })}
          >
            {t('datenschutzLink')}
          </LegalButton>
        </div>
      </FooterMessage>
    </SidebarContainer>
  );
};
 
export default Sidebar;