Compare commits
2 commits
17c49a339e
...
65a8ea8ab8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65a8ea8ab8 | ||
|
|
29c1252633 |
17
package-lock.json
generated
17
package-lock.json
generated
|
|
@ -36,6 +36,7 @@
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-leaflet": "^5.0.0-rc.2",
|
"react-leaflet": "^5.0.0-rc.2",
|
||||||
|
"react-scrollspy-navigation": "^2.0.6",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
|
|
@ -9808,6 +9809,22 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-scrollspy-navigation": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-scrollspy-navigation/-/react-scrollspy-navigation-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-BBnIEI9BsCPIMnp3z/OIEJ6mRSlDgGYf8wty5IjR8nOIhIeRhQp6eDUAKFMyHRoM2RUInA2NDu5DutIFTphoKQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/toviszsolt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paypal",
|
||||||
|
"url": "https://www.paypal.com/paypalme/toviszsolt"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/react-style-singleton": {
|
"node_modules/react-style-singleton": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-leaflet": "^5.0.0-rc.2",
|
"react-leaflet": "^5.0.0-rc.2",
|
||||||
|
"react-scrollspy-navigation": "^2.0.6",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
|
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
|
||||||
import { useColorSections } from "@/hooks/color-sections"
|
|
||||||
import type { Sections, translations } from "@/i18n/translations"
|
import type { Sections, translations } from "@/i18n/translations"
|
||||||
import { Menu } from "lucide-react"
|
import { Menu } from "lucide-react"
|
||||||
import { useRef } from "react"
|
import ScrollSpy from "react-scrollspy-navigation"
|
||||||
import { LanguageSelector } from "./ui/language-selector"
|
import { LanguageSelector } from "./ui/language-selector"
|
||||||
|
|
||||||
function NavContent({
|
function NavContent({
|
||||||
|
|
@ -15,15 +14,16 @@ function NavContent({
|
||||||
t: typeof translations.pl,
|
t: typeof translations.pl,
|
||||||
linksOrder: Array<Sections>
|
linksOrder: Array<Sections>
|
||||||
}) {
|
}) {
|
||||||
const parent = useRef<HTMLDivElement>(null);
|
|
||||||
useColorSections(parent);
|
|
||||||
return (
|
return (
|
||||||
<nav className="flex flex-col gap-4 mt-8" ref={parent}>
|
<nav className="flex flex-col gap-4 mt-8">
|
||||||
|
<ScrollSpy activeClass="nav-active">
|
||||||
|
|
||||||
{linksOrder.map((value) => (
|
{linksOrder.map((value) => (
|
||||||
<a key={value} href={`#${value}`} className="text-lg hover:text-primary transition-colors">
|
<a key={value} href={`#${value}`} className="text-lg hover:text-primary transition-colors">
|
||||||
{t.nav[value]}
|
{t.nav[value]}
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
|
</ScrollSpy>
|
||||||
<LanguageSelector />
|
<LanguageSelector />
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
"use client"
|
"use client"
|
||||||
import { useColorSections } from "@/hooks/color-sections"
|
|
||||||
import { type translations } from "@/i18n/translations"
|
import { type translations } from "@/i18n/translations"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { useRef } from "react"
|
import ScrollSpy from 'react-scrollspy-navigation'
|
||||||
import { MobileNav } from "./mobile-nav"
|
import { MobileNav } from "./mobile-nav"
|
||||||
import { NavContainer } from "./nav-container"
|
import { NavContainer } from "./nav-container"
|
||||||
|
|
||||||
|
|
||||||
const linksOrder: Array<keyof (typeof translations.pl)["nav"]> = [
|
const linksOrder: Array<keyof (typeof translations.pl)["nav"]> = [
|
||||||
'about',
|
'about',
|
||||||
'tickets',
|
'tickets',
|
||||||
|
|
@ -19,13 +19,12 @@ export function MainpageNav({
|
||||||
}: {
|
}: {
|
||||||
t: typeof translations.pl
|
t: typeof translations.pl
|
||||||
}) {
|
}) {
|
||||||
const parent = useRef<HTMLDivElement>(null);
|
|
||||||
useColorSections(parent);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavContainer title={t.nav.title}>
|
<NavContainer title={t.nav.title}>
|
||||||
<>
|
<>
|
||||||
<div className="hidden md:flex md:items-center md:gap-4 lg:gap-8" ref={parent}>
|
<div className="hidden md:flex md:items-center md:gap-4 lg:gap-8">
|
||||||
|
<ScrollSpy activeClass="nav-active">
|
||||||
|
|
||||||
{linksOrder.map((value) => (
|
{linksOrder.map((value) => (
|
||||||
<a
|
<a
|
||||||
|
|
@ -38,6 +37,7 @@ export function MainpageNav({
|
||||||
})} />
|
})} />
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
|
</ScrollSpy>
|
||||||
</div>
|
</div>
|
||||||
<div className="md:hidden ml-2">
|
<div className="md:hidden ml-2">
|
||||||
<MobileNav t={t} linksOrder={linksOrder} />
|
<MobileNav t={t} linksOrder={linksOrder} />
|
||||||
|
|
|
||||||
|
|
@ -159,3 +159,11 @@
|
||||||
section {
|
section {
|
||||||
scroll-margin-top: calc(var(--spacing) * 16 + var(--spacing) * 4);
|
scroll-margin-top: calc(var(--spacing) * 16 + var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-active {
|
||||||
|
color: var(--color-primary) !important;
|
||||||
|
& span {
|
||||||
|
--tw-scale-x: 100%;
|
||||||
|
scale: var(--tw-scale-x) var(--tw-scale-y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
import { Sections } from "@/i18n/translations";
|
|
||||||
import { useLayoutEffect, useRef } from "react";
|
|
||||||
|
|
||||||
export const linksOrder: Array<Sections> = [
|
|
||||||
"about",
|
|
||||||
"tickets",
|
|
||||||
"cfp",
|
|
||||||
"details",
|
|
||||||
"contact"
|
|
||||||
]
|
|
||||||
|
|
||||||
export function useColorSections(parent: React.RefObject<HTMLDivElement | null>) {
|
|
||||||
const previous = useRef<Sections>(linksOrder[0])
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
if (parent.current === null) return;
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
root: null,
|
|
||||||
rootMargin: "-10px",
|
|
||||||
threshold: 0.5, // Adjust the visibility threshold as needed
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const sections = linksOrder.map(value => document.getElementById(value));
|
|
||||||
const subs = linksOrder.reduce((acc, value) => {
|
|
||||||
acc[value] = parent.current!.querySelector('[data-sub="' + value + '"]')!;
|
|
||||||
return acc;
|
|
||||||
}, {} as Record<Sections, HTMLAnchorElement>);
|
|
||||||
const links = linksOrder.reduce((acc, value) => {
|
|
||||||
acc[value] = parent.current!.querySelector('[href="#' + value + '"]')!;
|
|
||||||
return acc;
|
|
||||||
}, {} as Record<Sections, HTMLAnchorElement>);
|
|
||||||
|
|
||||||
console.log(links)
|
|
||||||
|
|
||||||
const observer = new IntersectionObserver((entries) => {
|
|
||||||
|
|
||||||
for (const entry of entries) {
|
|
||||||
const target = entry.target.id as Sections
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
// FIXME: This seems to be VERY broken on firefox.
|
|
||||||
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1250972
|
|
||||||
// It basically spikes up CPU usage to some enormous values just to update the hash, like WTF firefox.
|
|
||||||
// if (history.replaceState) {
|
|
||||||
// timeout = setTimeout(() => {
|
|
||||||
// history.replaceState(null, "", `#${target}`)
|
|
||||||
// }, 150)
|
|
||||||
// }
|
|
||||||
subs[previous.current]?.classList.remove('scale-x-100');
|
|
||||||
links[previous.current]?.classList.remove('text-primary');
|
|
||||||
previous.current = target;
|
|
||||||
|
|
||||||
subs[previous.current]?.classList.add('scale-x-100');
|
|
||||||
links[previous.current]?.classList.add('text-primary');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
sections.forEach(section => {
|
|
||||||
if (section) {
|
|
||||||
observer.observe(section);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
observer.disconnect()
|
|
||||||
};
|
|
||||||
}, [parent]);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -44,7 +44,7 @@ const pl = {
|
||||||
tickets: {
|
tickets: {
|
||||||
title: "Bilety",
|
title: "Bilety",
|
||||||
status: `Rusza sprzedaż pierwszej puli biletów na Cebula Camp 2025: Reaktywacja!
|
status: `Rusza sprzedaż pierwszej puli biletów na Cebula Camp 2025: Reaktywacja!
|
||||||
Od 20 kwietnia 2025 r. będzie można kupić bilet. Każdy z sześciu polskich Hackerspaceów ma voucher, który pozwala mu kupić 10 biletów. Skontaktuj się więc z najbliższym HS i zarezerwuj swój bilet jak najszybciej!
|
Od 20 kwietnia 2025 r. będzie można kupić bilet. Każdy z sześciu polskich Hackerspaceów ma voucher, który pozwala na zakup biletów. Skontaktuj się więc z najbliższym HS i zarezerwuj swój bilet jak najszybciej!
|
||||||
|
|
||||||
W miejscu, gdzie będzie nasz Camp, mamy do dyspozycji przestrzeń, którą wykorzystamy jako małe pole namiotowe. Jeżeli chcesz nocować w swoim namiocie, przy zakupie biletu dodaj „nocleg na polu namiotowym” jako dodatek do biletu. Nie pobieramy za to żadnych dodatkowych opłat, ale liczba miejsc jest ograniczona, a pula wspólna dla wszystkich.`,
|
W miejscu, gdzie będzie nasz Camp, mamy do dyspozycji przestrzeń, którą wykorzystamy jako małe pole namiotowe. Jeżeli chcesz nocować w swoim namiocie, przy zakupie biletu dodaj „nocleg na polu namiotowym” jako dodatek do biletu. Nie pobieramy za to żadnych dodatkowych opłat, ale liczba miejsc jest ograniczona, a pula wspólna dla wszystkich.`,
|
||||||
link: "Kup bilet tutaj",
|
link: "Kup bilet tutaj",
|
||||||
|
|
@ -144,7 +144,7 @@ const en = {
|
||||||
title: "Tickets",
|
title: "Tickets",
|
||||||
status: `The sale of the first batch of tickets for Cebula Camp 2025: Reactivation is starting!
|
status: `The sale of the first batch of tickets for Cebula Camp 2025: Reactivation is starting!
|
||||||
|
|
||||||
You will be able to buy a ticket from April 20, 2025. Each of the six Polish Hackerspaces has a voucher that allows them to buy 10 tickets. Contact your nearest hackerspace and book your ticket as soon as possible!
|
You will be able to buy a ticket from April 20, 2025. Each of the six Polish Hackerspaces has a voucher that allows them to buy tickets. Contact your nearest hackerspace and book your ticket as soon as possible!
|
||||||
|
|
||||||
We have a small camping ground next to the venue for our use. If you want to bring a tent and stay in it overnight, when buying a ticket, add "overnight stay at the camping site" as an addition to the ticket. We do not charge any additional fees for this, but the number of places is limited and the pool is shared by everyone.`,
|
We have a small camping ground next to the venue for our use. If you want to bring a tent and stay in it overnight, when buying a ticket, add "overnight stay at the camping site" as an addition to the ticket. We do not charge any additional fees for this, but the number of places is limited and the pool is shared by everyone.`,
|
||||||
link: "Get your ticket here!",
|
link: "Get your ticket here!",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue