adds filtering
This commit is contained in:
parent
7e37987c70
commit
011432dd7a
2 changed files with 186 additions and 8 deletions
|
@ -1,3 +1,166 @@
|
||||||
---
|
---
|
||||||
|
const { filters, currentFilter } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
---
|
<div class="inline-flex border border-white/20 rounded-full p-1 relative">
|
||||||
|
<div
|
||||||
|
id="slider"
|
||||||
|
class="absolute h-[calc(100%-8px)] bg-[#FFB81C] rounded-full transition-none"
|
||||||
|
style="left: 4px;"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
filters.map((filter) => (
|
||||||
|
<button
|
||||||
|
data-filter={filter}
|
||||||
|
class={`px-6 py-2 rounded-full transition-all relative z-10 ${
|
||||||
|
currentFilter === filter
|
||||||
|
? "text-black"
|
||||||
|
: "text-white hover:bg-white/10 hover:bg-opacity-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{filter}
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const buttons = document.querySelectorAll("[data-filter]");
|
||||||
|
const officers = document.querySelectorAll("[data-officer]");
|
||||||
|
const container = officers[0]?.parentElement;
|
||||||
|
const slider = document.getElementById("slider");
|
||||||
|
|
||||||
|
// Define type order for consistent sorting
|
||||||
|
const typeOrder = ["Executives", "Internal", "Events", "Projects"];
|
||||||
|
|
||||||
|
function getTypeWeight(type) {
|
||||||
|
const index = typeOrder.indexOf(type);
|
||||||
|
return index === -1 ? typeOrder.length : index;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortOfficersByType() {
|
||||||
|
const officerArray = Array.from(officers);
|
||||||
|
officerArray.sort((a, b) => {
|
||||||
|
const aTypes = JSON.parse(a.getAttribute("data-types"));
|
||||||
|
const bTypes = JSON.parse(b.getAttribute("data-types"));
|
||||||
|
return getTypeWeight(aTypes[0]) - getTypeWeight(bTypes[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
officerArray.forEach((officer) => {
|
||||||
|
container.appendChild(officer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveSlider(button) {
|
||||||
|
if (!slider) return;
|
||||||
|
const buttonRect = button.getBoundingClientRect();
|
||||||
|
const containerRect = button.parentElement.getBoundingClientRect();
|
||||||
|
|
||||||
|
slider.style.width = `${buttonRect.width}px`;
|
||||||
|
slider.style.left = `${buttonRect.left - containerRect.left}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFilter(selectedFilter, clickedButton) {
|
||||||
|
// Update button styles
|
||||||
|
buttons.forEach((btn) => {
|
||||||
|
const isSelected =
|
||||||
|
btn.getAttribute("data-filter") === selectedFilter;
|
||||||
|
btn.classList.toggle("text-black", isSelected);
|
||||||
|
btn.classList.toggle("text-white", !isSelected);
|
||||||
|
btn.classList.toggle("hover:bg-white/10", !isSelected);
|
||||||
|
btn.classList.toggle("hover:bg-opacity-50", !isSelected);
|
||||||
|
});
|
||||||
|
|
||||||
|
// move slider
|
||||||
|
moveSlider(clickedButton);
|
||||||
|
|
||||||
|
// fades out all officers
|
||||||
|
officers.forEach((officer) => {
|
||||||
|
officer.style.opacity = "0";
|
||||||
|
officer.style.transition = "opacity 300ms ease-out";
|
||||||
|
});
|
||||||
|
|
||||||
|
// waits, then removes and re-adds officers
|
||||||
|
setTimeout(() => {
|
||||||
|
// removes all officers from container
|
||||||
|
officers.forEach((officer) => {
|
||||||
|
officer.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
// filters officers and prepares them for re-insertion
|
||||||
|
const officersToShow = Array.from(officers).filter((officer) => {
|
||||||
|
const types = JSON.parse(
|
||||||
|
officer.getAttribute("data-types") || "[]"
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
selectedFilter === "All" || types.includes(selectedFilter)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// sorts if needed
|
||||||
|
if (selectedFilter === "All") {
|
||||||
|
officersToShow.sort((a, b) => {
|
||||||
|
const aTypes = JSON.parse(a.getAttribute("data-types"));
|
||||||
|
const bTypes = JSON.parse(b.getAttribute("data-types"));
|
||||||
|
return getTypeWeight(aTypes[0]) - getTypeWeight(bTypes[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// sets initial opacity to 0 for fade in
|
||||||
|
officersToShow.forEach((officer) => {
|
||||||
|
officer.style.opacity = "0";
|
||||||
|
officer.style.display = "";
|
||||||
|
container.appendChild(officer);
|
||||||
|
});
|
||||||
|
|
||||||
|
// triggers reflow and fades in
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
officersToShow.forEach((officer) => {
|
||||||
|
officer.style.opacity = "1";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, 300); // matches fade-out duration
|
||||||
|
}
|
||||||
|
|
||||||
|
sortOfficersByType();
|
||||||
|
|
||||||
|
// init
|
||||||
|
const initialButton = Array.from(buttons).find(
|
||||||
|
(btn) => btn.getAttribute("data-filter") === "All"
|
||||||
|
);
|
||||||
|
|
||||||
|
// init
|
||||||
|
if (initialButton && slider) {
|
||||||
|
const buttonRect = initialButton.getBoundingClientRect();
|
||||||
|
slider.style.width = `${buttonRect.width}px`;
|
||||||
|
// turns on transitions
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
slider.classList.remove("transition-none");
|
||||||
|
slider.classList.add(
|
||||||
|
"transition-all",
|
||||||
|
"duration-300",
|
||||||
|
"ease-in-out"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// reveals officers after sorting with animation
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
officers.forEach((officer) => {
|
||||||
|
officer.style.transition = "opacity 300ms ease-out";
|
||||||
|
officer.style.visibility = "visible";
|
||||||
|
// triggers reflow
|
||||||
|
officer.offsetHeight;
|
||||||
|
officer.style.opacity = "1";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// addss click handlers
|
||||||
|
buttons.forEach((button) => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
const filterValue = button.getAttribute("data-filter");
|
||||||
|
updateFilter(filterValue, button);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
|
@ -4,7 +4,14 @@ import { Image } from "astro:assets";
|
||||||
import neko from "../../images/neko.png";
|
import neko from "../../images/neko.png";
|
||||||
import { LiaDotCircle } from "react-icons/lia";
|
import { LiaDotCircle } from "react-icons/lia";
|
||||||
import Officer from "../board/Officer.astro";
|
import Officer from "../board/Officer.astro";
|
||||||
|
import Filter from "../board/Filter.astro";
|
||||||
import officers from "../../data/officers.json";
|
import officers from "../../data/officers.json";
|
||||||
|
|
||||||
|
// Get all unique types and add 'All' option
|
||||||
|
const typeOrder = ["Executives", "Internal", "Events", "Projects"];
|
||||||
|
const types = ["All", ...typeOrder];
|
||||||
|
|
||||||
|
const currentFilter = "All";
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="text-white flex flex-col items-center mt-[15vh] mb-[10vh]">
|
<div class="text-white flex flex-col items-center mt-[15vh] mb-[10vh]">
|
||||||
|
@ -28,15 +35,23 @@ import officers from "../../data/officers.json";
|
||||||
nibh vivamus tempus molestie tristique quis
|
nibh vivamus tempus molestie tristique quis
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="grid gap-[3vw] grid-cols-3 mt-[10vh]">
|
<Filter filters={types} currentFilter={currentFilter} />
|
||||||
|
|
||||||
|
<div class="grid gap-[3vw] grid-cols-3 mt-[4vh]">
|
||||||
{
|
{
|
||||||
officers.map((officer) => (
|
officers.map((officer) => (
|
||||||
<Officer
|
<div
|
||||||
name={officer.name}
|
data-officer
|
||||||
position={officer.position}
|
data-types={JSON.stringify(officer.type)}
|
||||||
picture={officer.picture}
|
style="opacity: 0; visibility: hidden"
|
||||||
email={officer.email}
|
>
|
||||||
/>
|
<Officer
|
||||||
|
name={officer.name}
|
||||||
|
position={officer.position}
|
||||||
|
picture={officer.picture}
|
||||||
|
email={officer.email}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue