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 type { Section } from '@/types/data';
|
||||
|
|
@ -12,7 +12,7 @@ export interface Props {
|
|||
}
|
||||
|
||||
const SidebarItem = ({ section, icon }: Props) => {
|
||||
const [hash] = useHash();
|
||||
const { hash } = useLocation();
|
||||
const href = `#${section}`;
|
||||
|
||||
const active = hash === '' ? section === MAIN_SECTION : hash === href;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,18 @@
|
|||
---
|
||||
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';
|
||||
|
||||
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 Typography from '@/atoms/typography.astro';
|
||||
import type { Section } from '@/types/data';
|
||||
import type { FavoritesSection } from '@/types/favorites-section';
|
||||
|
||||
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 Tag from '@/atoms/tag.astro';
|
||||
import Typography from '@/atoms/typography.astro';
|
||||
import type { Section } from '@/types/data';
|
||||
import type { MainSection } from '@/types/main-section';
|
||||
|
||||
export interface Props extends MainSection {}
|
||||
|
|
@ -20,9 +21,11 @@ const {
|
|||
action: { label, url, downloadedFileName },
|
||||
tags,
|
||||
} = 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', 'sm:flex-col', 'gap-4', 'items-center']}>
|
||||
<Image
|
||||
|
|
@ -35,7 +38,7 @@ const {
|
|||
<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']}>
|
||||
<Typography variant="main-title">{fullName}</Typography>
|
||||
<Typography variant="main-title" id={`${section}-heading`}>{fullName}</Typography>
|
||||
<Typography variant="main-subtitle">{role}</Typography>
|
||||
</div>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,18 @@
|
|||
---
|
||||
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';
|
||||
|
||||
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 Typography from '@/atoms/typography.astro';
|
||||
import SkillSubsection from '@/organisms/skill-subsection.astro';
|
||||
import type { Section } from '@/types/data';
|
||||
import type { SkillsSection } from '@/types/skills-section';
|
||||
|
||||
export interface Props extends SkillsSection {}
|
||||
|
|
@ -10,10 +11,12 @@ const {
|
|||
config: { title },
|
||||
skillSets,
|
||||
} = Astro.props;
|
||||
|
||||
const section: Section = 'skills';
|
||||
---
|
||||
|
||||
<SectionCard section="skills">
|
||||
<Typography variant="section-title">{title}</Typography>
|
||||
<SectionCard section={section}>
|
||||
<Typography variant="section-title" id={`${section}-heading`}>{title}</Typography>
|
||||
<div class:list={['flex', 'flex-col', 'gap-10']}>
|
||||
{skillSets.map((skillSet) => <SkillSubsection skillSet={skillSet} />)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import Divider from '@/atoms/divider.astro';
|
|||
import SectionCard from '@/atoms/section-card.astro';
|
||||
import Typography from '@/atoms/typography.astro';
|
||||
import Testimonial from '@/organisms/testimonial.astro';
|
||||
import type { Section } from '@/types/data';
|
||||
import type { TestimonialsSection } from '@/types/testimonials-section';
|
||||
|
||||
export interface Props extends TestimonialsSection {}
|
||||
|
|
@ -11,10 +12,12 @@ const {
|
|||
testimonials,
|
||||
config: { title },
|
||||
} = Astro.props;
|
||||
|
||||
const section: Section = 'testimonials';
|
||||
---
|
||||
|
||||
<SectionCard section="testimonials">
|
||||
<Typography variant="section-title">{title}</Typography>
|
||||
<SectionCard section={section}>
|
||||
<Typography variant="section-title" id={`${section}-heading`}>{title}</Typography>
|
||||
<div class:list={['flex', 'flex-col']}>
|
||||
{
|
||||
testimonials.map((testimonial, index) => (
|
||||
|
|
|
|||
|
|
@ -46,4 +46,10 @@ const { seo, ...dataWithoutSeo } = data;
|
|||
</main>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
import updateHash from '../scripts/updateHash';
|
||||
import data from '../data';
|
||||
|
||||
document.addEventListener('scroll', () => updateHash(data));
|
||||
</script>
|
||||
</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