Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@astrojs/sitemap": "^3.3.0",
"@astrojs/tailwind": "^5.1.4",
"@fontsource-variable/inter": "^5.1.1",
"@fortawesome/fontawesome-free": "^6.7.2",
"@tailwindcss/typography": "^0.5.16",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.1",
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 0 additions & 9 deletions src/components/BaseHead.astro
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
---
// Import the global.css file here so that it is included on
// all pages through the use of the <BaseHead /> component.
import "../styles/global.css";
import "@fontsource-variable/inter";

interface Props {
title: string;
Expand Down Expand Up @@ -58,8 +54,3 @@ const { title, description, image = "/social-card.png" } = Astro.props;
is:inline
data-domain="ep2025.europython.eu"
src="https://plausible.io/js/script.js"></script>

<script
is:inline
src="https://kit.fontawesome.com/14a4971ab3.js"
crossorigin="anonymous"></script>
2 changes: 2 additions & 0 deletions src/components/footer.astro → src/components/Footer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const buildTimestamp = import.meta.env.TIMESTAMP;
const gitVersion = import.meta.env.GIT_VERSION;
---

<div class="mt-auto">
<Fullbleed className="bg-primary text-white">
<footer
class="max-w-4xl lg:max-w-6xl mx-auto py-16 lg:grid grid-cols-2 px-6 gap-60"
Expand Down Expand Up @@ -106,3 +107,4 @@ const gitVersion = import.meta.env.GIT_VERSION;
</article>
</footer>
</Fullbleed>
</div>
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
import { promises as fs } from "fs";
import { NavItems } from "../nav-items";
import HeaderActions from "./header-actions.astro";
import HeaderLogo from "./header-logo.astro";
import { NavItems } from "@components/nav-items";
import HeaderActions from "@components/header/header-actions.astro";
import HeaderLogo from "@components/header/header-logo.astro";

const links = JSON.parse(await fs.readFile("./src/data/links.json", "utf-8"));
import links from "../data/links.json";
---

<header class="p-6 flex items-center justify-between relative z-40">
Expand All @@ -27,7 +27,7 @@ const links = JSON.parse(await fs.readFile("./src/data/links.json", "utf-8"));
<div
class="fixed bg-body-background top-0 left-0 w-screen h-screen overflow-scroll hidden peer-checked:block xl:peer-checked:hidden z-50 p-6"
>
<div class="flex items-center">
<div class="flex items-center justify-between">
<HeaderLogo />
<HeaderActions mobile />
</div>
Expand Down
115 changes: 115 additions & 0 deletions src/components/Modal.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
import Button from '@ui/Button.astro';
const { id = 'modal', open = false, closeOnOutsideClick = false } = Astro.props;
---
<div
id={id}
class={`fixed inset-0 z-50 flex items-center justify-center transition-opacity duration-300 ${
open ? 'opacity-100 visible bg-black/50' : 'opacity-0 invisible'
}`}
data-modal-wrapper
data-close-on-outside-click={closeOnOutsideClick.toString()}
>
<div
class="relative bg-white dark:bg-gray-800 p-6 lg:rounded-2xl lg:shadow-xl lg:max-w-4xl lg:mx-4 h-full lg:h-[80vh] w-full lg:w-[70vw] max-h-full overflow-y-auto"
data-modal-content
>
<Button
id="search-close"
clear
icon="close"
iconSize="fa-xl"
data-close-modal
class="w-[3em] h-[3em] absolute top-4 right-4 text-primary hover:text-black "
/>
<slot />
</div>
</div>
<script is:inline>
document.addEventListener('DOMContentLoaded', () => {
// Function to toggle body scroll
const toggleBodyScroll = (disable) => {
if (disable) {
// Save the current scroll position
const scrollY = window.scrollY;
document.body.style.position = 'fixed';
document.body.style.top = `-${scrollY}px`;
document.body.style.width = '100%';
document.body.dataset.scrollPosition = scrollY.toString();
} else {
// Restore scroll position
const scrollY = parseInt(document.body.dataset.scrollPosition || '0');
document.body.style.position = '';
document.body.style.top = '';
document.body.style.width = '';
window.scrollTo(0, scrollY);
delete document.body.dataset.scrollPosition;
}
};

// Function to open modal
const openModal = (modal) => {
if (modal) {
// Disable body scroll
toggleBodyScroll(true);
// Show modal
modal.classList.remove('opacity-0', 'invisible');
modal.classList.add('opacity-100', 'visible');
}
};

// Function to close modal
const closeModal = (modal) => {
if (modal) {
// Enable body scroll
toggleBodyScroll(false);
// Hide modal
modal.classList.remove('opacity-100', 'visible');
modal.classList.add('opacity-0', 'invisible');
}
};

// Open modal buttons
document.querySelectorAll('[data-open-modal]').forEach(button => {
button.addEventListener('click', () => {
const modal = document.getElementById(button.dataset.openModal);
openModal(modal);
});
});

// Close modal buttons
document.querySelectorAll('[data-close-modal]').forEach(button => {
button.addEventListener('click', () => {
const modal = button.closest('[data-modal-wrapper]');
closeModal(modal);
});
});

// Close when clicking outside the modal content - only if enabled
document.querySelectorAll('[data-modal-wrapper]').forEach(modal => {
const closeOnOutsideClick = modal.dataset.closeOnOutsideClick === 'true';

if (closeOnOutsideClick) {
modal.addEventListener('click', (event) => {
// Check if the click was on the wrapper but not on the content
if (event.target === modal) {
closeModal(modal);
}
});
}

// Initialize any modal that's set to open by default
if (modal.classList.contains('opacity-100') && modal.classList.contains('visible')) {
toggleBodyScroll(true);
}
});

// Handle escape key to close all modals
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
const visibleModals = document.querySelectorAll('[data-modal-wrapper].visible, [data-modal-wrapper].opacity-100');
visibleModals.forEach(modal => closeModal(modal));
}
});
});
</script>
143 changes: 143 additions & 0 deletions src/components/Search.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
---
import SearchComponent from "astro-pagefind/components/Search";
import Button from "@ui/Button.astro";
import Modal from "@components/Modal.astro";

---


<Modal id="modal-search" closeOnOutsideClick=true>
<h2 class="text-xl font-bold mb-2 py-2">Search</h2>
<SearchComponent
id="search"
className="pagefind-ui"
uiOptions={{
showImages: false,
translations: {
zero_results: "Couldn't find [SEARCH_TERM]",
},
}}
/>
</div>
</div>
</Modal>


<script>
document.addEventListener("DOMContentLoaded", function () {
const searchContainer = document.querySelector(".pagefind-ui") as HTMLElement | null;
const searchInput = searchContainer?.querySelector("input") as HTMLInputElement | null;
const searchButton = document.getElementById("searchButton") as HTMLElement | null;
const openModal = document.querySelector('[data-open-modal="modal-search"]') as HTMLElement;
const closeButton = document.querySelector('#search-close') as HTMLElement | null;

let selectedIndex = -1;

function openSearch() {
console.log("open");
openModal.click();
if (searchInput) {
searchInput.value = "Tips";
const inputEvent = new Event("input", { bubbles: true });
searchInput.dispatchEvent(inputEvent);
setTimeout(() => {
searchInput.value = "";
searchInput.placeholder = "Tips for You";
}, 100);
searchInput.focus();
}
}

function closeSearch() {
closeButton?.click();
}

function updateSelection() {
const results = searchContainer?.querySelectorAll(".pagefind-ui__result");
if (!results) return;

results.forEach((result, index) => {
if (result instanceof HTMLElement) {
if (index === selectedIndex) {
result.classList.add("selected");
result.scrollIntoView({ block: "nearest", behavior: "smooth" });
} else {
result.classList.remove("selected");
}
}
});
}

function makeResultsClickable() {
const results = searchContainer?.querySelectorAll(".pagefind-ui__result");
if (!results) return;

results.forEach((result) => {
const link = result.querySelector(".pagefind-ui__result-link") as HTMLAnchorElement | null;
if (link && !result.hasAttribute("data-clickable")) {
if (result instanceof HTMLElement) {
result.style.cursor = "pointer";
result.addEventListener("click", (e) => {
if (!(e.target instanceof HTMLElement) || !e.target.closest("a")) {
link.click();
}
});
result.setAttribute("data-clickable", "true");
}
}
});
}

if (searchButton) {
searchButton.addEventListener("click", openSearch);
}

document.addEventListener("keydown", function (event) {
if (!searchContainer || !searchInput) return;

const results = searchContainer.querySelectorAll(".pagefind-ui__result");

if (event.ctrlKey && event.key === "k") {
event.preventDefault();
openSearch();
} else if (event.key === "Escape") {
closeSearch();
} else if (document.activeElement === searchInput) {
if (event.key === "ArrowDown") {
event.preventDefault();
selectedIndex = (selectedIndex + 1) % results.length;
updateSelection();
} else if (event.key === "ArrowUp") {
event.preventDefault();
selectedIndex = (selectedIndex - 1 + results.length) % results.length;
updateSelection();
} else if (event.key === "Enter") {
event.preventDefault();
if (results.length > 0) {
const indexToOpen = selectedIndex === -1 ? 0 : selectedIndex;
const link = results[indexToOpen]?.querySelector("a") as HTMLAnchorElement | null;
link?.click();
}
}
}
});

const resultsObserver = new MutationObserver(() => {
const results = searchContainer?.querySelectorAll(".pagefind-ui__result");
if (results && results.length > 0) {
selectedIndex = 0;
updateSelection();
makeResultsClickable();
}
});

if (searchContainer) {
resultsObserver.observe(searchContainer, {
childList: true,
subtree: true,
});
}

closeButton?.addEventListener("click", closeSearch);
});
</script>
Loading