@import "../../resources/scss/util/variables";
@import "../../resources/scss/util/mixins";
@import "../../resources/scss/vendor/bootstrap/vendor/rfs";
.block-resource-grid{
@include padding-top(rem-calc(60));
@include padding-bottom(rem-calc(60));
position: relative;
&:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
background-color: $white;
z-index: 10;
transition: opacity 0.25s ease-in-out;
pointer-events: none;
}
&__griditems{
display: grid;
gap: rem-calc(10);
grid-template-columns: 1fr;
@include margin-bottom(rem-calc(40));
@include bp($md) {
grid-template-columns: 1fr 1fr;
@include margin-bottom(rem-calc(60));
}
}
&.loading {
&:before {
opacity: 0.75;
pointer-events: all;
}
&:after {
content: '';
position: absolute;
top: 20%;
left: calc(50% - 50px);
width: 100px;
height: 100px;
animation: spin 1s linear infinite;
border: 8px solid;
border-color: color-mix(in srgb, var(--primary) 10%, transparent);
border-top-color: var(--primary);
border-radius: 50%;
z-index: 20;
}
}
.resource-grid-card{
border-radius: 20px;
overflow: hidden;
text-decoration: none;
display: flex;
flex-direction: column;
justify-content: flex-end;
position: relative;
aspect-ratio: 352/485;
@include padding(rem-calc(30 8.61 8.58 8.61));
transition: 0.3s ease-in all;
&:hover{
box-shadow: 0px 16px 34px 0px rgba(0,0,0,0.25);
transform: translateY(-5px);
}
@include bp($md){
aspect-ratio: 775/525;
}
&__info{
@include padding(rem-calc(22 28));
background-color: rgba(255,255,255,0.4);
border: 1px solid rgba(255,255,255,0.2);
border-radius: 13px;
max-width: 384px;
text-decoration: none;
backdrop-filter: blur(50px);
&_title{
margin-bottom: rem-calc(10);
line-height: 1.2;
}
&_date{
display: block;
font-size: rem-calc(16);
letter-spacing: -4;
line-height: 1.5;
@include margin-bottom(rem-calc(10));
}
svg{
@include margin-top(rem-calc(5));
}
}
&__style--light{
.resource-grid-card__info{
color: $white;
}
.resource-grid-card__info_title{
color: $white;
}
.post-card__category{
border-color: $white;
color: $white;
&:first-child{
background-color: $white;
border-color: $black;
color: $black;
}
}
}
&__style--dark{
.resource-grid-card__info{
color: $black;
}
.post-card__category{
border-color: $black;
color: $black;
&:first-child{
background-color: $black;
border-color: $black;
color: $white;
}
}
}
}
.sort-by-wrap{
width: 100%;
}
select[data-sort-by],
select[data-filter-by]{
border-radius: 60px;
border: 1px solid rgba(0,0,0,0.2);
width: 100%;
min-height: rem-calc(44);
background-size: rem-calc(8);
font-size: rem-calc(16);
cursor: pointer;
padding:rem-calc(10) rem-calc(40) rem-calc(10) rem-calc(20);
@include bp($md){
width: rem-calc(200);
}
@include bp($lg){
font-size: rem-calc(18);
}
}
.mobile-filter{
@include bp($lg){
display: none;
}
}
.filter-wrapper{
display: block;
position: relative;
max-width: 100%;
overflow-x: auto;
white-space: nowrap;
@include bp($lg){
padding: 0 2px;
&::-webkit-scrollbar {
height: 5px;
}
&::-webkit-scrollbar-track {
background: transparent;
margin: 10px 20px 0;
}
&::-webkit-scrollbar-thumb {
background: #bbb;
border-radius: 10px;
}
&::-webkit-scrollbar-thumb:hover {
background: #999;
}
}
}
.filter-container{
display: none;
@include bp($lg){
border-radius: 60px;
border: 1px solid rgba(0,0,0,0.2);
display: inline-flex;
gap: 8px;
.filter{
border-radius: 60px;
font-size: rem-calc(18px);
line-height: 1.5;
background-color: $white;
@include padding(rem-calc(8 18));
text-decoration: none;
flex-shrink: 0;
&.active{
background-color: $grey-primary;
}
}
}
}
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
class ResourceGridBlock {
/**
* Create and initialise objects of this class
* @param {HTMLElement} block
*/
constructor(block) {
this.block = block;
this.postsContainer = this.block.querySelector('.results');
this.countElement = this.block.querySelector('.resource-grid-count');
this.sortBy = this.block.querySelector('select[data-sort-by]');
this.postType = this.block.dataset.postType;
this.perPage = parseInt(this.block.dataset.perPage);
this.taxonomy = this.block.dataset.taxonomy;
this.filterCategory = false;
this.orderBy = 'date';
this.sortOrder='desc';
this.currentPage = 1;
this.init();
}
init() {
this.setupPagination();
this.setupSorting();
this.setupFilters();
}
setupPagination() {
const pagination = this.block.querySelector('.pagination');
if (!pagination) {
return;
}
const paginationLinks = pagination.querySelectorAll('a');
paginationLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
// Get page
let page = link.innerText;
if (link.dataset.page) {
page = link.dataset.page;
}
if (!page) {
return;
}
if (page && page !== this.currentPage) {
this.currentPage = page;
this.fetchResults();
}
});
});
}
setupFilters() {
const filters = this.block.querySelectorAll('select[data-filter-by]');
const filtersLinks = this.block.querySelectorAll('.filter');
if (filters) {
filters.forEach(filter => {
filter.addEventListener('change', (e) => {
const filterValue = filter.value;
if (!filterValue || filterValue === 'all') {
this.filterCategory = false;
// delete this.feedFilters[filterName];
} else {
this.filterCategory = filterValue;
}
this.currentPage = 1;
this.fetchResults();
});
});
}
if (filtersLinks) {
filtersLinks.forEach(filterItem => {
filterItem.addEventListener('click', (e) => {
if(!filterItem.classList.contains('active')){
filtersLinks.forEach(fitem=>{
fitem.classList.remove('active');
});
}
const filterValue = filterItem.dataset.value;
filterItem.classList.add('active');
if (!filterValue || filterValue === 'all') {
this.filterCategory = false;
// delete this.feedFilters[filterName];
} else {
this.filterCategory = filterValue;
}
this.currentPage = 1;
this.fetchResults();
});
});
}
}
setupSorting() {
const sortSelect = this.sortBy;
if (!sortSelect) return;
sortSelect.addEventListener('change', () => {
const val = sortSelect.value;
if (!val) {
this.feedSorting = {};
} else {
const [orderby, order] = val.split('_');
this.orderBy=orderby;
this.sortOrder=order;
}
this.fetchResults();
});
}
async fetchResults() {
this.block.classList.add('loading');
try {
const formData = new FormData();
formData.append('action', 'filter_custom_posts');
formData.append('nonce', resourceGrid.nonce);
formData.append('post_type', this.postType);
formData.append('posts_per_page', this.perPage);
formData.append('paged', this.currentPage);
formData.append('orderby', this.orderBy);
formData.append('order', this.sortOrder);
formData.append('origin',resourceGrid.baseURL);
// Add category filter if selected
if (this.filterCategory) {
formData.append('taxonomy', this.taxonomy);
formData.append('category', this.filterCategory);
}
const response = await fetch(resourceGrid.ajaxurl, {
method: 'POST',
credentials: 'same-origin',
body: formData,
});
const data = await response.json();
if (data.success) {
this.totalPages = data.data.total_pages;
this.updateResults(data.data);
this.updatePagination(data.data.pagination);
this.updateCount(data.data.found_posts);
} else {
this.updatePagination(false);
this.showError(data.data.message || 'Error loading posts');
}
} catch (error) {
this.showError('Error loading posts. Please try again.');
} finally {
this.block.classList.remove('loading');
}
}
updateResults(data){
if (!this.postsContainer) return;
this.postsContainer.innerHTML = data.results;
}
updateCount(count){
// Re-query element if not found (in case DOM was updated)
if (!this.countElement) {
this.countElement = this.block.querySelector('.resource-grid-count');
}
if (!this.countElement) {
console.warn('ResourceGridBlock: Count element not found');
return;
}
if (count === undefined || count === null) {
console.warn('ResourceGridBlock: Count value is undefined', count);
return;
}
const countText = count === 1 ? 'Resource' : 'Resources';
this.countElement.textContent = `${count} ${countText}`;
}
updatePagination(pagination){
const paginationElem = this.block.querySelector('.pagination');
if (!paginationElem) {
return;
}
// if no pagination, remove pagination
if (!pagination) {
paginationElem.innerHTML = '';
return;
}
// replace pagination
paginationElem.outerHTML = pagination;
// Reregister events
this.setupPagination();
}
showError(message = 'Sorry, there was an error loading posts. Please try again.') {
if (!this.postsContainer) return;
this.postsContainer.innerHTML = `
${message}
`;
}
}
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.block-resource-grid').forEach(block => new ResourceGridBlock(block));
});
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "strategiq/resource-grid",
"title": "Resource Grid",
"description": "Resource Grid block",
"category": "strategiq",
"icon": "strategiq",
"acf": {
"mode": "preview",
"renderTemplate": "block-resource-grid.php"
},
"supports": {
"anchor": true,
"align": false,
"color": {
"background": true,
"text": false,
"gradients": false
},
"spacing": {
"padding": [
"top",
"bottom"
],
"margin": [
"top",
"bottom"
]
}
},
"style": "file:../../assets/css/resource-grid/block-resource-grid.css"
}