Update active sidebar item dynamically on scroll (#79)
This commit is contained in:
parent
599c8bc479
commit
08ab03a2d5
9 changed files with 84 additions and 11 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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) => (
|
||||||
|
|
|
||||||
|
|
@ -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
28
src/scripts/updateHash.ts
Normal 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;
|
||||||
Loading…
Reference in a new issue