adds filtering

This commit is contained in:
chark1es 2025-01-18 04:06:19 -08:00
parent 7e37987c70
commit 011432dd7a
2 changed files with 186 additions and 8 deletions

View file

@ -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>

View file

@ -4,7 +4,14 @@ import { Image } from "astro:assets";
import neko from "../../images/neko.png";
import { LiaDotCircle } from "react-icons/lia";
import Officer from "../board/Officer.astro";
import Filter from "../board/Filter.astro";
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]">
@ -28,15 +35,23 @@ import officers from "../../data/officers.json";
nibh vivamus tempus molestie tristique quis
</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) => (
<Officer
name={officer.name}
position={officer.position}
picture={officer.picture}
email={officer.email}
/>
<div
data-officer
data-types={JSON.stringify(officer.type)}
style="opacity: 0; visibility: hidden"
>
<Officer
name={officer.name}
position={officer.position}
picture={officer.picture}
email={officer.email}
/>
</div>
))
}
</div>