Update active sidebar item dynamically on scroll (#79)

This commit is contained in:
Szymon Kin 2022-11-07 21:16:39 +01:00 committed by GitHub
parent 599c8bc479
commit 08ab03a2d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 84 additions and 11 deletions

View file

@ -1,4 +1,4 @@
import { useHash } from 'react-use'; import { useLocation } from 'react-use';
import { MAIN_SECTION } from '@/constants/section'; import { MAIN_SECTION } from '@/constants/section';
import type { Section } from '@/types/data'; import type { Section } from '@/types/data';
@ -12,7 +12,7 @@ export interface Props {
} }
const SidebarItem = ({ section, icon }: Props) => { const SidebarItem = ({ section, icon }: Props) => {
const [hash] = useHash(); const { hash } = useLocation();
const href = `#${section}`; const href = `#${section}`;
const active = hash === '' ? section === MAIN_SECTION : hash === href; const active = hash === '' ? section === MAIN_SECTION : hash === href;

View file

@ -1,8 +1,18 @@
--- ---
import SectionCard from '@/atoms/section-card.astro'; import SectionCard from '@/atoms/section-card.astro';
import Typography from '@/atoms/typography.astro';
import type { Section } from '@/types/data';
import type { ExperienceSection } from '@/types/experience-section'; import type { ExperienceSection } from '@/types/experience-section';
export interface Props extends ExperienceSection {} export interface Props extends ExperienceSection {}
const {
config: { title },
} = Astro.props;
const section: Section = 'experience';
--- ---
<SectionCard section="experience">Experience section</SectionCard> <SectionCard section={section}
><Typography variant="section-title" id={`${section}-heading`}>{title}</Typography>
</SectionCard>

View file

@ -1,8 +1,18 @@
--- ---
import SectionCard from '@/atoms/section-card.astro'; import SectionCard from '@/atoms/section-card.astro';
import Typography from '@/atoms/typography.astro';
import type { Section } from '@/types/data';
import type { FavoritesSection } from '@/types/favorites-section'; import type { FavoritesSection } from '@/types/favorites-section';
export interface Props extends FavoritesSection {} export interface Props extends FavoritesSection {}
const {
config: { title },
} = Astro.props;
const section: Section = 'favorites';
--- ---
<SectionCard section="favorites">Favorites section</SectionCard> <SectionCard section={section}
><Typography variant="section-title" id={`${section}-heading`}>{title}</Typography>
</SectionCard>

View file

@ -6,6 +6,7 @@ import IconButton from '@/atoms/icon-button.astro';
import SectionCard from '@/atoms/section-card.astro'; import SectionCard from '@/atoms/section-card.astro';
import Tag from '@/atoms/tag.astro'; import Tag from '@/atoms/tag.astro';
import Typography from '@/atoms/typography.astro'; import Typography from '@/atoms/typography.astro';
import type { Section } from '@/types/data';
import type { MainSection } from '@/types/main-section'; import type { MainSection } from '@/types/main-section';
export interface Props extends MainSection {} export interface Props extends MainSection {}
@ -20,9 +21,11 @@ const {
action: { label, url, downloadedFileName }, action: { label, url, downloadedFileName },
tags, tags,
} = Astro.props; } = Astro.props;
const section: Section = 'main';
--- ---
<SectionCard section="main"> <SectionCard section={section}>
<div class:list={['flex', 'gap-6', 'flex-col', 'sm:flex-row', 'items-start']}> <div class:list={['flex', 'gap-6', 'flex-col', 'sm:flex-row', 'items-start']}>
<div class:list={['flex', 'sm:flex-col', 'gap-4', 'items-center']}> <div class:list={['flex', 'sm:flex-col', 'gap-4', 'items-center']}>
<Image <Image
@ -35,7 +38,7 @@ const {
<div class:list={['w-full', 'flex', 'flex-col', 'gap-5']}> <div class:list={['w-full', 'flex', 'flex-col', 'gap-5']}>
<div class:list={['w-full', 'flex', 'flex-col', 'sm:flex-row', 'justify-between', 'gap-2']}> <div class:list={['w-full', 'flex', 'flex-col', 'sm:flex-row', 'justify-between', 'gap-2']}>
<div class:list={['w-full']}> <div class:list={['w-full']}>
<Typography variant="main-title">{fullName}</Typography> <Typography variant="main-title" id={`${section}-heading`}>{fullName}</Typography>
<Typography variant="main-subtitle">{role}</Typography> <Typography variant="main-subtitle">{role}</Typography>
</div> </div>
{ {

View file

@ -1,8 +1,18 @@
--- ---
import SectionCard from '@/atoms/section-card.astro'; import SectionCard from '@/atoms/section-card.astro';
import Typography from '@/atoms/typography.astro';
import type { Section } from '@/types/data';
import type { PortfolioSection } from '@/types/portfolio-section'; import type { PortfolioSection } from '@/types/portfolio-section';
export interface Props extends PortfolioSection {} export interface Props extends PortfolioSection {}
const {
config: { title },
} = Astro.props;
const section: Section = 'portfolio';
--- ---
<SectionCard section="portfolio">Portfolio section</SectionCard> <SectionCard section={section}
><Typography variant="section-title" id={`${section}-heading`}>{title}</Typography>
</SectionCard>

View file

@ -2,6 +2,7 @@
import SectionCard from '@/atoms/section-card.astro'; import SectionCard from '@/atoms/section-card.astro';
import Typography from '@/atoms/typography.astro'; import Typography from '@/atoms/typography.astro';
import SkillSubsection from '@/organisms/skill-subsection.astro'; import SkillSubsection from '@/organisms/skill-subsection.astro';
import type { Section } from '@/types/data';
import type { SkillsSection } from '@/types/skills-section'; import type { SkillsSection } from '@/types/skills-section';
export interface Props extends SkillsSection {} export interface Props extends SkillsSection {}
@ -10,10 +11,12 @@ const {
config: { title }, config: { title },
skillSets, skillSets,
} = Astro.props; } = Astro.props;
const section: Section = 'skills';
--- ---
<SectionCard section="skills"> <SectionCard section={section}>
<Typography variant="section-title">{title}</Typography> <Typography variant="section-title" id={`${section}-heading`}>{title}</Typography>
<div class:list={['flex', 'flex-col', 'gap-10']}> <div class:list={['flex', 'flex-col', 'gap-10']}>
{skillSets.map((skillSet) => <SkillSubsection skillSet={skillSet} />)} {skillSets.map((skillSet) => <SkillSubsection skillSet={skillSet} />)}
</div> </div>

View file

@ -3,6 +3,7 @@ import Divider from '@/atoms/divider.astro';
import SectionCard from '@/atoms/section-card.astro'; import SectionCard from '@/atoms/section-card.astro';
import Typography from '@/atoms/typography.astro'; import Typography from '@/atoms/typography.astro';
import Testimonial from '@/organisms/testimonial.astro'; import Testimonial from '@/organisms/testimonial.astro';
import type { Section } from '@/types/data';
import type { TestimonialsSection } from '@/types/testimonials-section'; import type { TestimonialsSection } from '@/types/testimonials-section';
export interface Props extends TestimonialsSection {} export interface Props extends TestimonialsSection {}
@ -11,10 +12,12 @@ const {
testimonials, testimonials,
config: { title }, config: { title },
} = Astro.props; } = Astro.props;
const section: Section = 'testimonials';
--- ---
<SectionCard section="testimonials"> <SectionCard section={section}>
<Typography variant="section-title">{title}</Typography> <Typography variant="section-title" id={`${section}-heading`}>{title}</Typography>
<div class:list={['flex', 'flex-col']}> <div class:list={['flex', 'flex-col']}>
{ {
testimonials.map((testimonial, index) => ( testimonials.map((testimonial, index) => (

View file

@ -46,4 +46,10 @@ const { seo, ...dataWithoutSeo } = data;
</main> </main>
</div> </div>
</body> </body>
<script>
import updateHash from '../scripts/updateHash';
import data from '../data';
document.addEventListener('scroll', () => updateHash(data));
</script>
</html> </html>

28
src/scripts/updateHash.ts Normal file
View file

@ -0,0 +1,28 @@
import type { Data } from '@/types/data';
const updateHash = (data: Data) => {
const { seo, ...dataWithoutSeo } = data;
const distancesToHeadingBottom = Object.keys(dataWithoutSeo)
.flatMap((section) => {
const sectionWrapper = document.getElementById(`${section}-heading`);
if (!sectionWrapper) return [];
const { bottom } = sectionWrapper.getBoundingClientRect();
return {
section,
bottom,
};
})
.filter((section) => section.bottom > 0);
const currentSection = distancesToHeadingBottom.reduce((previous, current) =>
previous.bottom < current.bottom ? previous : current
);
window.history.pushState({}, '', `${window.location.pathname}#${currentSection.section}`);
};
export default updateHash;