@@ -37,7 +38,7 @@ export const BlogCard = () => {
{ SITE.publications.slice(-5).reverse().map(blog => ) }
);
};
+
+export { BlogCard, BlogCardComponent }
\ No newline at end of file
diff --git a/src/components/Blogs.jsx b/src/components/Blogs.jsx
new file mode 100644
index 000000000000..a13c3cf33fe4
--- /dev/null
+++ b/src/components/Blogs.jsx
@@ -0,0 +1,236 @@
+import React, { useState, useEffect, useCallback } from 'react';
+import '@cscfi/csc-ui-react/css/theme.css';
+import {
+ CPagination, CCheckbox, CSelect, CButton, CModal, CCard,
+ CCardTitle, CCardContent, CCardActions
+} from '@cscfi/csc-ui-react';
+import { BlogCardComponent } from './BlogCards';
+
+const BlogFilters = ({ filters, handleFilterChange }) => {
+ const handleCheckboxChange = useCallback((category, option) => {
+ handleFilterChange({
+ ...filters,
+ [category]: { ...filters[category], [option]: !filters[category][option] } //toggle the state
+ });
+ }, [filters, handleFilterChange]);
+
+ const handleChangeTheme = useCallback(selectedTheme => {
+ handleFilterChange({ ...filters, Theme: selectedTheme.detail || '' }); //update theme or set to ''
+ }, [filters, handleFilterChange]);
+
+ return (
+
+ {Object.entries(filters).slice(0, -1).map(([category, options]) => ( //slice(0,-1) to exclude Theme
+
+ ))}
+
+
+ );
+};
+
+//Checkbox filters
+const FilterCategory = ({ category, options, handleCheckboxChange }) => (
+
+
{category}
+ {Object.keys(options).map(option => ( //generate a chekcbox for each filter category
+
handleCheckboxChange(category, option)}
+ >
+ {option}
+
+ ))}
+
+);
+
+//Theme filter
+const FilterTheme = ({ selectedTheme, handleChangeTheme }) => (
+
+);
+
+//Modal filter for mobile
+const FilterModal = ({ isModalOpen, setIsModalOpen, filters, handleFilterChange }) => {
+ return (
+
setIsModalOpen(event.detail)}
+ >
+
+ Filters
+
+
+
+
+ setIsModalOpen(false)} text>Close
+
+
+
+ );
+};
+
+//List blogs in a grid with pagination
+const BlogsList = ({ title, blogs, paginationOptions, handlePageChange, showFilters, onOpenDialog }) => (
+
+
+
{title}
+ {showFilters && //to not show the button on every EventsList instance
+ onOpenDialog()}
+ >
+ Filters
+
+ }
+
+ {blogs.length ? (
+ <>
+
+ {blogs.slice(
+ (paginationOptions.currentPage - 1) * paginationOptions.itemsPerPage,
+ (paginationOptions.currentPage - 1) * paginationOptions.itemsPerPage + paginationOptions.itemsPerPage
+ ).map(blog => (
+
+ ))}
+
+
+ >
+ ) : (
+
No {title.toLowerCase()}.
+ )}
+
+);
+
+//Full blogs component
+export const Blogs = () => {
+ const blogs_dict = SITE.publications; //get blogs
+ const [isModalOpen, setIsModalOpen] = useState(false); //modal control
+ const [filters, setFilters] = useState({
+ "Skill level": { "Advanced": false, "Beginner": false },
+ "Type": { "Blog": false, "Instructions": false, "News": false },
+ "Theme": "",
+ }); //filter state
+
+ const [options, setOptions] = useState({
+ itemCount: blogs_dict.length,
+ itemsPerPage: 8,
+ currentPage: 1,
+ pageSizes: [5, 10, 15, 25, 50]
+ }); //pagination control
+
+ const [filteredBlogs, setFilteredBlogs] = useState(blogs_dict);
+
+ useEffect(() => {
+ document.body.classList.add("min-w-fit");
+ }, []);
+
+ useEffect(() => {
+ document.body.style.overflow = isModalOpen ? 'hidden' : 'visible';
+ return () => {
+ document.body.style.overflow = 'visible';
+ };
+ }, [isModalOpen]);
+
+ useEffect(() => { //set filteredBlogs everytime filters changes
+ const applyFilters = (blog) => {
+ if (filters.Theme && blog?.filters?.Theme !== filters.Theme) {
+ return false;
+ }
+ // For every other filter category...
+ return Object.entries(filters).every(([category, options]) => {
+ // Skip the "Theme" category here
+ if (category === "Theme") return true;
+
+ // Create an array of only the options that are checked (active)
+ const activeOptions = Object.entries(options).filter(([_, checked]) => checked);
+
+ // If no options are active in this category, do not filter out the event:
+ if (activeOptions.length === 0) return true;
+
+ // Otherwise, require that at least one active option is true in the event:
+ return activeOptions.some(([option]) => blog?.filters?.[category]?.[option]);
+ });
+ };
+
+ //apply filter
+ const filtered = blogs_dict.filter(applyFilters);
+ setFilteredBlogs(filtered);
+
+ //also update item count in pagination options
+ setOptions(prev => ({ ...prev, itemCount: filtered.length }));
+ }, [filters]);
+
+ const onOpenDialog = () => { //modal control
+ setIsModalOpen(true);
+ };
+
+ const handlePageChange = (setOptions) => (blog) => {
+ // blog.detail.currentPage should be the new page number.
+ setOptions(prev => ({ ...prev, currentPage: blog.detail.currentPage }));
+ };
+
+ const handleFilterChange = (newFilters) => {
+ setFilters(newFilters);
+ setOptions(prev => ({ ...prev, currentPage: 1 }));
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/Events.jsx b/src/components/Events.jsx
index a6f7c5e231b1..cdf071040add 100644
--- a/src/components/Events.jsx
+++ b/src/components/Events.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useMemo, useCallback } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
import '@cscfi/csc-ui-react/css/theme.css';
import {
CPagination, CCheckbox, CSelect, CButton, CModal, CCard,
@@ -53,6 +53,7 @@ const FilterCategory = ({ category, options, handleCheckboxChange }) => (
{category}
{Object.keys(options).map(option => ( //generate a chekcbox for each filter category
(
Theme
setIsModalOpen(event.detail)}
>
-
+
Filters
- )
+ );
};
//List events in a grid with pagination
@@ -129,12 +132,12 @@ const EventsList = ({ title, events, paginationOptions, handlePageChange, showFi
{events.length ? (
<>
- {events.slice((paginationOptions.currentPage - 1) *
- paginationOptions.itemsPerPage, (paginationOptions.currentPage - 1) *
- paginationOptions.itemsPerPage + paginationOptions.itemsPerPage)
- .map(event =>
-
- )}
+ {events.slice(
+ (paginationOptions.currentPage - 1) * paginationOptions.itemsPerPage,
+ (paginationOptions.currentPage - 1) * paginationOptions.itemsPerPage + paginationOptions.itemsPerPage
+ ).map(event => (
+
+ ))}
{
const events_dict = SplitEvents(); //get events
- //console.log(events_dict)
const [isModalOpen, setIsModalOpen] = useState(false); //modal control
const [filters, setFilters] = useState({
"Availability": { "Open to anyone": false, "Registration needed": false },
@@ -180,16 +182,11 @@ export const Events = () => {
const [filteredEvents, setFilteredEvents] = useState(events_dict);
useEffect(() => {
- document.body.classList.add("min-w-fit")
- })
+ document.body.classList.add("min-w-fit");
+ }, []);
useEffect(() => {
- if (isModalOpen) {
- document.body.style.overflow = 'hidden';
- } else {
- document.body.style.overflow = 'visible';
- }
- // Always clean up on unmount
+ document.body.style.overflow = isModalOpen ? 'hidden' : 'visible';
return () => {
document.body.style.overflow = 'visible';
};
@@ -235,7 +232,7 @@ export const Events = () => {
};
const handlePageChange = (setOptions) => (event) => {
- // event.detail should be the new page number.
+ // event.detail.currentPage should be the new page number.
setOptions(prev => ({ ...prev, currentPage: event.detail.currentPage }));
};
@@ -248,7 +245,7 @@ export const Events = () => {
return (
-
+
@@ -257,7 +254,8 @@ export const Events = () => {
events={[...filteredEvents.upcoming].reverse()}
paginationOptions={optionsUpcoming}
handlePageChange={handlePageChange(setOptionsUpcoming)}
- showFilters={true} onOpenDialog={onOpenDialog}
+ showFilters={true}
+ onOpenDialog={onOpenDialog}
/>
{
isModalOpen={isModalOpen}
setIsModalOpen={setIsModalOpen}
filters={filters}
- handleFilterChange={handleFilterChange} />
+ handleFilterChange={handleFilterChange}
+ />
);
};
diff --git a/src/components/Home.jsx b/src/components/Home.jsx
index 23df788c0c07..0d0652d374f6 100644
--- a/src/components/Home.jsx
+++ b/src/components/Home.jsx
@@ -35,7 +35,7 @@ const ContentButton = ({ text, href, icon=mdiArrowDown }) => {
export const Home = () => {
return (
-
+
diff --git a/src/roots/blogs.jsx b/src/roots/blogs.jsx
new file mode 100644
index 000000000000..86ab2982f8e3
--- /dev/null
+++ b/src/roots/blogs.jsx
@@ -0,0 +1,13 @@
+import React from 'react'
+import { Blogs } from '../components/Blogs'
+import { Banner } from '../components/Banner'
+import { injectComponent } from '../utils/root'
+
+const component = (
+ <>
+
+
+ >)
+const rootId = 'blogs'
+
+injectComponent(component, rootId)
diff --git a/tailwind.config.js b/tailwind.config.js
index 9a9ce4de33f9..b4a5d5a3c026 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -5,7 +5,12 @@ module.exports = {
files: ["./src/**/*.{js,jsx}", "./content/**/*.{md,html}"],
},
theme: {
- extend: {},
+ extend: {
+ backgroundImage: {
+ // You can choose any key name; here, we use 'hero'
+ 'lumi': "url('/assets/images/LUMI.jpg')",
+ },
+ },
},
plugins: [require("@tailwindcss/typography")],
};